記憶體控制
-
V8的垃圾回收機制 / 記憶體限制
- V8讓JS虛擬機器的效能達到了很快的地步,所以node實現在V8上
- V8的記憶體限制:Node中通過JS使用記憶體只能使用部分記憶體(64G下大概1.4GB),所以Node無法直接操作大記憶體物件
- V8的物件分配
- V8中,所有JS物件都是通過堆進行分配,如果已申請的堆空閒不夠分配新的物件,將繼續申請堆記憶體
- 通過process.memoryUsage()可以看到現在的記憶體使用情況
- Node在啟動的時候可以傳遞 --max-old-space-size / --max-new-space-size 來調整記憶體限制的大小,只在初始化有效。
- V8 的垃圾回收機制
- V8主要的垃圾回收演算法:分代式垃圾回收機制
- V8中將記憶體分為新生代和老生代,新生代的物件存活時間短,老生代是存存活活時間長/常駐記憶體的物件
- 新生代中的物件主要通過scavenge演算法進行垃圾回收,而在scavenge的具體實現使用了Cheney演算法
- 將對記憶體一分為二,每一部分空間成為semi space。
- 2個semispace只有一個在使用,一個處於閒置狀態。處於使用狀態的semi space空間稱為from空間,處於閒置狀態的空間成為To空間。
- 分配物件的時候,先從from空間中開始分配
- 垃圾回收時,會檢查from空間中的存活物件,存活物件唄複製到To空間,不存活的物件佔用的空間將會被釋放。
- to和from空間 交換
- 總之,這個演算法的意思就是將存活物件在兩個semi space空間中進行復制/翻轉
- 這個演算法犧牲了空間,換取時間;之所以採用這個演算法是因為新生代中物件的生命週期很短,佔用的記憶體也少
- 當一個物件經過多次複製和轉換依然存活,它會被認為是生命週期較長的物件,從而晉升到老生代中。而晉升的套件一般是2個
- 物件是否經歷過scavenge回收,如果有,就晉升
- To空間的記憶體佔用比是否超過比例,如果超過,直接晉升
- 老生代中常用Mark - Sweep(標記清除)& Mark - compact(標記整理)方法
- mark-sweep 標記清除
- 遍歷每個物件,並標記存活的物件,清楚階段只清楚沒有被標記的物件。
- 缺點:很容易出現清理之後記憶體空間不連續,這種記憶體碎片對後續的記憶體分配造成問題,因為很可能出現需要分配一個打碎片,所有的碎片空間都無法完成此次分配,就會提前觸發垃圾回收
- mark-compact 標記整理
- 標記每個物件,將活著的物件往一端移動,移動完成之後,直接清理掉邊界外的記憶體。
- incremental Marking 增量標記,減少垃圾回收的停頓時間
- 因為每次回收的時候,JS會阻塞,所以把大的垃圾回收工程拆分一小步一小步進行,比如增量標記和增量清理,還有延遲清理(lazy sweeping)
- mark-sweep 標記清除
- V8主要的垃圾回收演算法:分代式垃圾回收機制
- 檢視垃圾回收日誌
- 啟動node的時候新增 --trace-gc引數
-
高效使用記憶體
- 作用域退出/不再使用了 就會釋放這個作用域裡的變數
- 變數的主動釋放
- 如果變數是全域性變數,物件將常駐在記憶體。刪除手段有
- delete操作刪除引用關係
- 重新賦值,讓舊物件脫離引用關係/ 其實直接設定成空/null不行嗎
- 如果變數是全域性變數,物件將常駐在記憶體。刪除手段有
- 閉包(closure)
- 閉包導致閉包自身作用域不得到釋放
- 變數的主動釋放
- 作用域退出/不再使用了 就會釋放這個作用域裡的變數
-
記憶體指標
- 檢視記憶體使用情況:通過process.memoryUsage()可以看到現在的記憶體使用情況
- 檢視程式的記憶體使用,process.memoryUsage()
- 檢視系統的記憶體佔用,os模組的totalmem() 和 freemem() 用來檢視系統的總記憶體和限制記憶體
- 堆外記憶體:我們把不是通過V8分配的記憶體叫做 堆外記憶體
-
記憶體洩漏
- 造成記憶體洩漏的原因常見的有
- 快取
- 佇列消費不及時
- 作用域未釋放
- 記憶體 != 快取,慎重
- 一旦命中快取,就可以節省一次I/O的時間;但是一個物件被當作快取使用,意味著它常駐在老生代中;快取中儲存的鍵越多,長期存活的物件也就越多,那麼垃圾回收舊會做無用功
- 快取限制策咯:限制快取的無限增長
- 作者寫過一個limitablemap模組——P128
- 快取的解決方案
- 直接將記憶體作為快取的方案,除了快取大小的顧慮外,還要考慮程式之間無法共享記憶體,程式內使用快取將導致快取不可避免的有重複,浪費物理空間
- 解決方案:採用程式外的快取,程式自身不儲存狀態
- 將快取轉移到外部,減少常駐記憶體的物件的數量,讓垃圾回收變得高效
- 程式之間可以共享快取
- 常見的快取方案( 有客戶端)
- Redis
- Memcached
- 關注佇列狀態,因為也有可能造成記憶體洩漏
- 造成記憶體洩漏的原因常見的有
-
記憶體洩漏排查
- 常見工具:v8-profiler(3年沒維護了),node-heapdump,node-mtrace,dtrace,node-memwatch
-
大記憶體應用
-
使用stream模組處理大檔案(由於V8記憶體限制,我們無法通過fs.readFile()和fs.writeFile()直接對大檔案進行操作)
-
使用fs.createReadStream() / fs.createWriteStream()方法通過流的方式實現對大檔案的操作
-
var reader = fs.createReadStream('in.txt'); var writer = fs.createWriteStream('out.txt'); reader.on('data',function(chunk){ writer.write(chunk); }) reader.on('end',function(){ writer.end; }) //利用es6 中的pipe,簡寫後 var reader = fs.createReadStream('in.txt'); var writer = fs.createWriteStream('out.txt'); reader.pipe(writer); 複製程式碼
-
-