JS 的字符串是怎么分配內存的?
可能大家都知道,字符串存在字符串常量池中,被棧或堆上的變量引用。如果變量的值是字符串字面量,則在棧上的變量直接引用字符串常量池中的字符串;如果是字符串是 new String 創建的,則會在堆上創建 String 對象,指向字符串常量池中的字符串,棧上變量指向堆中的 String 對象。
這個結論是對的么?
今天我們用 Chrome Devtools 的 Memory 工具證明下:
Memory 工具證明 String 的內存分配方式
我們準備這樣一段代碼:
- <!DOCTYPE html>
- <html lang="en">
- <head>
- <meta charset="UTF-8">
- </head>
- <body>
- <script>
- const arr = [];
- setTimeout(() => {
- for(let i = 0;i< 10000;i++) {
- arr.push('guang');
- }
- }, 3000);
- const arr2 = [];
- setTimeout(() => {
- for(let i = 0;i< 10000;i++) {
- arr2.push(new String('guang'));
- }
- }, 5000);
- </script>
- </body>
- </html>
3s 的時候創建了一個 10000 個元素的數組 arr,數組元素是字符串常量 "guang"。
5s 的時候創建了一個 10000 個元素的數組 arr2,數組元素是 new String("guang")。
按照理論來說,arr 中的元素是直接引用字符串常量池的字符串,arr2 中的則是引用堆上的 String 對象,String 對象再引用字符串常量池的字符串。
我們用 Memory 工具來驗證下。
Chrome Devtools 提供了 Memory 工具用于分析內存中的對象:
一共有三種內存分析工具:
- Snapshot:某個時間點的堆內存快照
- TimeLine:實時的按照時間線顯示的內存分配情況
- Sampling:采樣的方式收集內存分配情況
我們想要看到按照時間線的實時分配情況,所以用第二種工具:TimeLine。
加載頁面,點擊錄制,右邊就會實時展示內存分配情況:
我們錄到 6s 點擊停止。
可以看到有兩條豎線,分別代表了兩次內存分配。
點擊第一次內存分配,可以看到詳情:
可以看到,這個時間點創建了 string 和 array 兩種對象:
"guang" 這個 string 的內存地址是 @169541。
Array 的元素指向的也都是 @169541
這就驗證了字符串常量池的存在,以及字符串字面量直接指向常量池中的字符串。
再來看下第二種內存分配方式:
可以看到,創建了 String 的對象、array 變量(system 是 JS 引擎內部分配的一些對象,不用關心):
String 對象引用了字符串常量池中的 @169541 的字符串 "guang"
而 Array 中的元素則是指向了不同的 String 對象的地址:
這再一次驗證了字符串常量池的存在,以及 String 對象是在堆上分配內存,然后指向字符串常量池的字符串。
證明完畢,確實如前面的結論所說:字符串存儲在字符串常量池中,字符串字面量直接指向常量池的字符串地址,String 對象會先在堆上分配空間,然后指向字符串常量池的字符串地址。
我們從始至終只創建了一次 "guang" 這個字符串,字符串常量池的好處顯而易見了:
而且,還可以得出一個結論,創建 String 對象的方式內存開銷大很多,建議用字符串字面量的方式:
從圖中可以直觀的對比出兩種方式的占用內存的差別。
文中的測試代碼上傳到了 github: https://github.com/QuarkGluonPlasma/chrome-devtools-exercise
總結
Chrome Devtools 提供了 Memory 工具用于分析內存,包括 Snapshot、TimeLine、Sample 三種工具,我們用其中的 TimeLine 工具實時分析了字符串的內存分配,證明了字符串常量池的存在,以及字符串字面量、new String 兩種創建字符串方式的內存上的差別。
建議盡量用字符串字面量,少用 new String 的方式創建字符串,在占據的內存大小上還是有差距的。
證明過程中,我們也可以直觀的感受到字符串常量池的巨大好處。
原文鏈接:https://mp.weixin.qq.com/s/VucSMXz8tnNf_LyrXZLHsg