project中的堆疊記憶體,記憶體地址引用,gc相關問題
專案中遇到了關於js記憶體,引用型別,gc機制相關的問題,記錄下來。
首先復現一下程式碼:
const option = {
yAxis: [{
type: 'value',
......
}]
};
option.yAxis.push(option.yAxis[0]);
option.yAxis[0].min = 1;
option.yAxis[1].min = 2;
發現option.yAxis下面的min的值都是2;第一反應是以為之前的程式碼寫的有問題,所以打斷點,打日誌,發現都正常,然後想到了有可能是記憶體指標導致的,然後可以做以下兩種修改。
const option = {
yAxis: [{
type: 'value',
......
}]
};
option.yAxis.push(JSON.parse(JSON.stringify(option.yAxis[0])));
option.yAxis[0].min = 1;
option.yAxis[1].min = 2;
或者
const option = {
yAxis: [{
type: 'value',
......
}]
};
option.yAxis.push(option.yAxis[0]);
option.yAxis[0] = {min: 1};
option.yAxis[1] = {min: 2};
以上兩種寫法都可以規避記憶體指標引用導致的資料問題。
剖析以下問題的本質吧:
- JS基本資料型別:Null,Undefined,Number,Boolean,Symbol,String
- JS複雜資料型別:Object Array (也算是object)
當我們在程式碼裡面:
var num = 123;
var obj = { name: 'obj' };
var obj_copy = obj;
此時會開闢兩塊記憶體空間,一個儲存123,一個儲存 { name: 'obj' }; 其中是num的指標會直接指向棧記憶體中的123,obj的指標會指向堆記憶體中的 { name: 'obj' },obj_copy的引用指標會存放在棧記憶體中;
如果我們編輯以下程式碼:
var num = 123;
var obj = { name: 'obj' };
var obj_copy = obj;
obj.name = 'xxx';
那麼我們會發現obj_copy的name也會變成xxx,其實就是因為產生了指標以引用,指向的是同一塊記憶體空間;
如果編輯以下程式碼:
var num = 123;
var obj = { name: 'obj' };
var obj_copy = obj;
obj = {name: 'xxx'};
那麼我們會發現obj_copy的name還是obj,這是因為obj = {name: 'xxx'};會產生一塊新的記憶體空間,然後obj會產生一次引用,obj_copy的引用跟該引用沒有任何毛線關係。那麼我們專案中的問題就迎刃而解了。
寫到這裡,突然想到了es6 的const 變數申明:
const object = { name: 'object'};
const num = 5;
object.name = 'new_object';
num = 4;
我們會發現object的name確實變成了字串new_object,但是num=4會報錯,為什麼呢?其實這個問題跟堆疊記憶體沒有關係,還是跟引用資料型別有關;
看下面的程式碼:
let numA = 1;
let numB = numA;
numA = 2;
console.log(numB );
我們發現numB的值仍然是1,其實基本資料型別也是存在引用的,只是基本資料型別無法像object一樣去更改某個key的值而已,就比如一座房子和一把板凳,如果你改變了房子或者凳子,那麼他就是實實在在地改變了,如果你只是改變了一座房子內部的一部分,它仍然是房子。
說到這裡,不得不提一下es6的 WeekMap了(也是借用了阮大的例子吧,哈哈哈!)
const wm = new WeakMap();
const element = document.getElementById('example');
wm.set(element, 'some information');
wm.get(element) // "some information"
WeekMap 是一種弱引用,也就是說,該節點的引用計數是1,如果element設定為null,Weakmap 儲存的這個鍵值對,也會自動消失。
說到這裡,就要說一下javascript的垃圾回收機制了,分為兩種型別吧,一種是標記清除 ,一種是引用計數。
let fn = function (){
let a = 1;
return a;
}
fn();
在執行fn函式時,變數a被標記為進入環境,在函式沒有被執行結束之前,是不能釋放該變數所指向的記憶體的,當函式執行完之後,變數會被標記為離開環境,則會被gc回收
let fn = function (){
let a = 1;
return a;
}
let back = fn();
如果程式碼寫成這樣的話,變數a所佔用的記憶體是不會被gc的,因為在外部存在了引用,雖然a已經離開了fn的執行環境,但是a的引用計數是2,所以不會被gc回收清除。
function problem(){
var objectA = new Object();
var objectB = new Object();
objectA.someOtherObject = objectB;
objectB.anotherObject = objectA;
}
上述情況也是objectA 和B都離開了函式環境,但是因為存在迴圈引用,所以引用計數都不為0,所以記憶體就不會得到回收,在個別情況下,需要手動回收。
另外,gc時,會阻塞主執行緒,所以平常寫程式碼的時候一定要注意相關問題,務必規避記憶體洩漏的相關問題。
相關文章
- 記憶體堆疊記憶體
- Java 堆疊記憶體分配Java記憶體
- JS中堆疊記憶體的練習JS記憶體
- JS中的棧記憶體、堆記憶體JS記憶體
- 堆疊和記憶體的關係 細說記憶體
- jvm堆記憶體和GC簡介JVM記憶體GC
- javascript堆疊記憶體分配的區別JavaScript記憶體
- JVM堆外記憶體問題排查JVM記憶體
- windows核心程式設計--記憶體堆疊Windows程式設計記憶體
- 直接記憶體和堆記憶體誰快記憶體
- iOS 記憶體管理相關面試題iOS記憶體面試題
- java棧記憶體和堆記憶體的詮釋Java記憶體
- JAVA的堆疊和記憶體、垃圾回收解說Java記憶體
- Java堆記憶體Heap與非堆記憶體Non-HeapJava記憶體
- [轉載] Java直接記憶體與堆記憶體Java記憶體
- C++中“記憶體重疊”C++記憶體
- 關於記憶體中棧和堆的區別記憶體
- 記憶體優化相關記憶體優化
- iOS記憶體管理相關iOS記憶體
- JVM記憶體-GC策略JVM記憶體GC
- 小計:引用型別記憶體分配問題型別記憶體
- 堆記憶體和棧記憶體詳解(轉載)記憶體
- JavaScript變數,資料和記憶體的相關問題JavaScript變數記憶體
- JVM中記憶體和GC的介紹JVM記憶體GC
- 關於java吃記憶體的問題Java記憶體
- golang的記憶體相關內容Golang記憶體
- 共享記憶體相關(ipcs/ipcrm)記憶體
- oracle記憶體調整相關Oracle記憶體
- 記憶體管理中關於記憶體每次增長的大小記憶體
- JVM記憶體GC的騙局JVM記憶體GC
- Chakra GC記憶體管理(未完)GC記憶體
- jvm:記憶體模型、記憶體分配及GC垃圾回收機制JVM記憶體模型GC
- 深入理解Java的堆記憶體和執行緒記憶體Java記憶體執行緒
- 用 verbose GC 分析 IBM WebSphere Portal 的記憶體問題GCIBMWeb記憶體
- 遺失的JVM堆記憶體JVM記憶體
- JVM堆記憶體詳解JVM記憶體
- JVM堆記憶體設定JVM記憶體
- Tomcat增加堆記憶體Tomcat記憶體