node使用V8作為javaScript腳本引擎
v8的內存限制和對象分配
限制:64為大約1.4G,32位大約0.7G
v8中所有javascript對象都是通過堆內存進行分配的。內存查看命令process.memoryUsage()
為何要內存限制
表層原因為v8最初為瀏覽器設計,不太可能遇到大量的內存的場景。對於網頁來說,v8的限制已經綽綽有余,深層原因是v8的垃圾回收機制的限制.
v8打開堆內存的限制命令 --max-old-space-size或--max-new-space-size
v8的垃圾回收機制
v8的內存分代
主要將內存分為新生代和老生代。新生代中為存活時間交短的對象,老生代中的對象為存活時間較長或者常駐內存對象
- 新生代內存在64位系統中為16MB,32位中為8MB
- v8堆內存最大保留空間 4 * 新生內存 + 老生代內存
新生代內存垃圾回收
采用一種復制方式的垃圾回收算法,將堆內存一分為二,只有一部分空間被使用稱為From空間,另一個處於閑置稱為To空間。當進行分配對象的時候先在from空間分配,當進行垃圾回收時,會檢查from空間中的存活對象,將這些存活對象復制到to空間中,復制完成后From和to空間角色互換,清空to空間,在垃圾回收過程中就是通過將存活對象在兩個空間中進行復制。
- 缺點: 只能使用一半的內存
- 優點: 只復制存活的對象,對於生命周期短的場景存活對象只占小部分,所以時間效率高
當一個對象經過多次復制依然存活時,就會被認為是生命周期較長的對象,會被移入老生代內存中。
對於移入老生代內存有兩個條件: - 對象已經經過新生代內存回收機制的回收依然存活
- 復制到To空間的對象超過25%(為什么是25%?這個To空間接下來會成為From空間並接受內存分配,如果占比過高影響后續分配)
老生代內存垃圾回收
采用標記清除,它分為標記清除兩個階段
在標記階段遍歷所有的對象並標記活着的對象,在清除階段只清除死亡的對象,死亡對象在老生代內存只占一小部分。老生代內存進行一次清除后,內存空間會出現不連續的狀態,所以清理完成需要進行一步標記整理。
為了避免出現javaScript應用邏輯與垃圾回收器看到不一致的情況,垃圾回收都要將應用邏輯停下來,這種行為會造成停頓,在新生代垃圾回收過程中因為存活對象比較少,即使停頓基本影響不大。在老生代垃圾回收中,通常存活對象較多,全堆垃圾回收的標記、清除、整理影響較大。
解決辦法:分批次進行,拆分成許多小步,每進行一小步就讓邏輯運行一會
node 查看垃圾回收日志 運行時加入 參數 --trace_gc
高效使用內存
作用域
減少使用全局作用域,在局部作用域聲明變量。當函數執行完成,該作用域就會被銷毀,只別局部變量引用的對象存活較短,會被分配到新生代內存,方便回收
閉包
function test() { var a = 1 return function () { return a } } var fn1 = test()
堆外內存
通過命令process.memoryUsage() 可以查看代rss總是大於常駐內存總量,在node中並不是所有的內存都通過v8進行分配,不通過v8進行分配的內存稱為堆外內存,通過Buffer 分配的內存即為堆外內存,所有處理大量數據的時候可以使用Buffer 進行分配
內存泄漏
通常造成內存泄漏的原因有:
- 緩存
- 隊列消費不及時
- 作用域未釋放
慎用內存當緩存
緩存的訪問效率要比I/O的效率高很多,一旦命中緩存,就可以節省一次I/O的時間,但是在node中,一旦一個對象被當作緩存,那它將常駐老生代內存,緩存中儲存的鍵越多,長期存活的對象就越多,這將導致垃圾回收在進行掃描和整理是頻繁的多這些對象做無用功。