Node與V8
-
基本概念
V8是Node的JavaScript執行引擎,V8引擎實際是一個高性能虛擬機。Node在JavaScript的執行直接受益于V8,可以隨著V8的升級就能享受更好的性能或新的語言特性(如ES5和ES6)
-
二者之間的關系
1)大小限制說明
對于一般的后端開發語言,基本內存使用是沒有限制的,但是在Node中通過javaScript使用內存時只能使用部分內存(64位系統下約為1.4G,32位系統下約為0.6G)
2)限制的原因與特殊說明
Node基于V8構建,所以在Node中使用javaScript基本都是通過V8自己的方式進行分配和管理的。但是Node的內存并不完全是通過V8進行分配管理的。查看內存使用情況的時候,發現堆中的內存用量總是小于進程的常駐內存用量rss。Node中的內存使用并非都是通過V8進行分配的,還有一些不是通過V8進行分配的對象,我們稱之為堆外內存,堆外內存文章末尾會有一個說明(例如Buffer對象就不同于其他對象,他不經過V8的內存分配機制,不會有堆內存的限制)
3)V8的對象分配
V8中,所有的javaScript對象都是通過堆來進行分配的。V8的堆內存包括heapToal(已經申請到的堆內存),heapUsed(當前使用的堆內存);我們在代碼中聲明變量并賦值的時候,所使用的對象的內存就分配在堆中。如果已申請的堆空閑內存不夠分配新的對象,將繼續申請堆內存,直到堆的大小超過V8的限制為止。
說明:基于V8這種限制將會導致Node無法操作大內存對象,也因此后來出現了buffer這種不受V8丟內存控制的堆外內存管理。
開發過程中的那些不好回收的內存(高效使用內存)
由于V8已經對內存做了限制,我們應該做到高效的使用內存,讓垃圾回收機制更高效的工作,避免一些不容易回收內存的出現。
-
作用域
在JavaScript中,能形成作用域的有函數,with以及全局作用域。
1)作用域舉例最基本的內存回收過程
var a=function(){
var local={};
}
函數a在每次被調用的時候會創建對應的作用域,函數執行結束后,該作用域將會銷毀。同時因為該作用域中聲明的局部變量分配在該作用域上,隨作用域的銷毀而銷毀。只被局部變量引用的對象存活周期較短。代碼中,由于對象較小,將會分配在新生代的Form空間中。作用域失效后,局部變量local失效,其引用的對象將會在下次垃圾回收時被釋放。
2)作用域中的變量查找
JavaScript在執行時會查找變量定義在哪,最先查找的當前作用域,當前作用域沒有,會向上級的作用域查找,直到最頂層全局作用域查到,如果沒有最后返回undefine。
3)變量的主動釋放
如果變量是全局變量(通過var聲明或定義在global變量上),全局作用域直到進程退出才能釋放,這種情況將導致引用的對象常駐內存(常駐在老生代中)。這種需要釋放常駐內存中的對象,可以使用delete操作來刪除引用關系,或者將變量重新賦值,讓舊對象脫離引用關系(也就是對象的引用即所占的內存空間原本指向某個變量現在指向空獲未定義),這樣在接下來的老生代內存 清 除和整理的過程中會被釋放。
global.foo="i am gang";
console.log(global.foo);// i am gang
delete global.foo;
//或者重新賦值
global.foo=undefined;// or null
console.log(global.foo);//undefined
說明:雖然兩種方式都可以主動釋放變量引用的對象(也就是那一小塊內存),但是推薦大家使用重新賦值的方法,因為在V8中通過delete刪除對象的屬性有可能干擾V8的優化。
-
閉包
在javaScript中,實現外部作用域訪問內部作用域中變量的方法叫做閉包(closure)。這得益于高階函數的特性:函數可以作為參數或者返回值。 閉包它實現了外部作用域訪問內部作用域中變量的方法。這句話需要好好理解。
簡單例子說明閉包 兩段代碼對比:
var A=function(){
(function(){
var local="局部變量";
}());
console.log(local); //local未定義異常
}
var B=function(){
var C=function(){
var local="局部變量";
return function(){
return local;
};
};
var c=C();
console.log(c()); //局部變量
};
分析第二段代碼,函數C執行完成后,局部變量local會隨著作用域的銷毀而被回收。但是注意這里的特點是返回值是一個匿名函數,而且這個函數中具備了訪問local的條件,后面的代碼執行,外部作用域是無法直接訪問local的,但是若要訪問它,只要通過這個中間函數稍作周轉即可。以上就是閉包的基本分析,現在能夠更好的理解我畫重點的那句話了吧。
對于閉包的詳細介紹,大家可以看我的這篇博客javascript中的閉包這一篇就夠了
內存相關基本命令使用
- V8中內存使用情況查看:
$ node
> process.memoryUsage();
{
rss:14958592,
heapTotal:7195904,
heapUsed:2821496
}
heapTotal:V8中已申請的堆內存
heapUsed:V8中當前使用的堆內存
rss:進程的常駐內存部分
- 查看系統的內存占用
$ node
> os.totalmem()
82132131
> os.freemem()
31273127
os.totalmem 操作系統的總內存
os.freemem 操作系統的閑置內存
-
堆外內存
查看v8內存使用情況,process.memoryUsage()的結果可以看到,V8堆中的內存用量總是小于進程的常駐內存用量rss,也就是說Node中的內存使用并非都是V8控制,還有一部分不是通過V8分配的(rss-heaptotal這部分),不通過V8分配的內存稱之為堆外內存。
使用buffer每次構造200MB的內存,代碼如下:
var useMem=function(){
var size=200*1024*1024;
var buffer=new Buffer(size);
for(var i=0;i<size;i++){
buffer[i]=0
}
return buffer;
};
代碼執行過程中,查看內存使用情況會發現到最后,V8的使用內存heapUsed和申請的內存heaptotal基本不變,而常駐內存rss在不斷增加,可以看出buffer對象不同于其它對象,不經過V8內存分配機制,不會有堆內存的限制。后面的文章會對buffer進行詳細的講解。
內存泄露
Node對內存泄漏十分敏感,一旦線上應用有成千上萬的流量,哪怕一個字節的內存泄漏也會造成堆積,垃圾回收過程中將會耗費更多時間進行對象掃描,應用響應緩慢,直到進程內存溢出,應用奔潰。
-
內存泄漏的本質
應當回收的對象出現意外而沒有被回收,變成常駐在老生代中的對象。
-
造成內存泄漏的原因
1)作用域未釋放
2)隊列消費不及時
3)作用域未釋放
內存泄漏這里有一個點,我們每次把服務重啟的時候會不會這個內存泄漏的堆積重新開始計算,forever restart和forever stop之后在forever start
覺得本文對你有幫助?請分享給更多人
歡迎大家關注我的公眾號——程序員成長指北。請自行微信搜索——“程序員成長指北”