javascript
javascript中的内存管理
文章目錄
- 簡介
- 內存生命周期
- JS中的垃圾回收器
- 引用計數垃圾回收算法
- Mark-and-sweep回收算法
- 調試內存問題
- 閉包Closures中的內存泄露
簡介
在c語言中,我們需要手動分配和釋放對象的內存,但是在java中,所有的內存管理都交給了java虛擬機,程序員不需要在手動進程內存的分配和釋放,大大的減少了程序編寫的難度。
同樣的,在javascript中,內存管理也是自動進行的,雖然有自動的內存管理措施,但是這并不意味著程序員就不需要關心內存管理了。
本文將會進行詳細的介紹javascript中的內存管理策略。
內存生命周期
對于任何程序來說,內存的生命周期通常都是一樣的。
可以分為三步:
所有的程序都需要手動執行第二步,對于javascript來說,第1,3兩步是隱式實現的。
我們看下javascript中分配內存空間的例子。
通過初始化分配內存空間:
var n = 123; // 為數字分配內存 var s = 'azerty'; // 為String分配內存var o = {a: 1,b: null }; // 為對象分配內存// 為數組分配內存 var a = [1, null, 'abra']; function f(a) {return a + 2; } // 為函數分配內存通過函數調用分配內存空間:
var d = new Date(); // 通過new分配date對象var e = document.createElement('div'); // 分配一個DOM對象var s = 'azerty'; var s2 = s.substr(0, 3); // 因為js中字符串是不可變的,所以substr的操作將會創建新的字符串var a = ['ouais ouais', 'nan nan']; var a2 = ['generation', 'nan nan']; var a3 = a.concat(a2); // 同樣的,concat操作也會創建新的字符串釋放空間最難的部分就是需要判斷空間什么時候不再被使用。在javascript中這個操作是由GC垃圾回收器來執行的。
垃圾回收器的作用就是在對象不再被使用的時候進行回收。
JS中的垃圾回收器
判斷一個對象是否可以被回收的一個非常重要的標準就是引用。
如果一個對象被另外一個對象所引用,那么這個對象肯定是不能夠被回收的。
引用計數垃圾回收算法
引用計數垃圾回收算法是一種比較簡單和簡潔的垃圾回收算法。他把對象是否能夠被回收轉換成了對象是否仍然被其他對象所引用。
如果對象沒有被引用,那么這個對象就是可以被垃圾回收的。
我們舉一個引用計數的例子:
var x = { a: {b: 2} }; //我們創建了兩個對象,a對象和a外面用大括號創建的對象。 // 我們將大括號創建的對象引用賦值給了x變量,所以x擁有大括號創建對象的引用,該對象不能夠被回收。 // 同時,因為a對象是創建在大括號對象內部的,所以大括號對象默認擁有a對象的引用 // 因為兩個對象都有引用,所以都不能夠被垃圾回收var y = x; //我們將x賦值給y,大括號對象現在擁有兩個引用x = 1; // 我們將1賦值給x,這樣只有y引用了大括號的對象var z = y.a; // 將y中的a對象引用賦值給z,a對象擁有兩個引用y = 'flydean'; // 重新賦值給y,大括號對象的引用數為0,大括號對象可以被回收了,但是因為其內部的a對象還有一個z在被引用// 所以暫時不能被回收z = null; // z引用也被重新賦值,a對象的引用數為0,兩個對象都可以被回收了引用計數的一個缺點就是可能會出現循環引用的情況。
考慮下面的一個例子:
function f() {var x = {};var y = {};x.a = y; // x references yy.a = x; // y references xreturn 'flydean'; }f();在上面的例子中,x中的a屬性引用了y。而y中的a屬性又引用了x。
從而導致循環引用的情況,最終導致內存泄露。
在實際的應用中,IE6 和IE7 對DOM對象使用的就是引用計數的垃圾回收算法,所以可能會出現內存泄露的情況。
var div; window.onload = function() {div = document.getElementById('myDivElement');div.circularReference = div;div.lotsOfData = new Array(10000).join('*'); };上面的例子中,DOM中的myDivElement元素使用circularReference引用了他本身,如果在引用計數的情況下,myDivElement是不會被回收的。
當myDivElement中包含了大量的數據的時候,即使myDivElement從DOM tree中刪除了,myDivElement也不會被垃圾回收,從而導致內存泄露。
Mark-and-sweep回收算法
講到這里,大家是不是覺得JS的垃圾回收算法和java中的很類似,java中也有引用計數和mark-and-sweep清除算法。
這種回收算法的判斷標準是對象不可達。
在javascript中,通過掃描root對象(JS中的root對象那些全局對象),然后找到這些root對象的引用對象,然后再找到這些被引用對象的引用對象,一層一層的往后查找。
最后垃圾回收器會找到所有的可達的對象和不可達的對象。
使用不可達來標記不再被使用的對象可以有效的解決引用計數法中出現的循環引用的問題。
事實上,現在基本上所有的現代瀏覽器都支持Mark-and-sweep回收算法。
調試內存問題
如果發送了內存泄露,我們該怎么調試和發現這個問題呢?
在nodejs中我們可以添加–inspect,然后借助Chrome Debugger來完成這個工作:
node --expose-gc --inspect index.js上面的代碼將會開啟nodejs的調試功能。
我們看下輸出結果:
Debugger listening on ws://127.0.0.1:9229/88c23ae3-9081-41cd-98b0-d0f7ebceab5a For help, see: https://nodejs.org/en/docs/inspector結果告訴了我們兩件事情,第一件事情就是debugger監聽的端口。默認情況下將會開啟127.0.0.1的9229端口。并且分配了一個唯一的UUID以供區分。
第二件事情就是告訴我們nodejs使用的調試器是Inspector。
使用Chrome devTools進行調試的前提是我們已經開啟了 --inspect模式。
在chrome中輸入chrome://inspect:
我們可看到chrome inspect的界面,如果你本地已經有開啟inspect的nodejs程序的話,在Remote Target中就可以直接看到。
選中你要調試的target,點擊inspect,即可開啟Chrome devTools調試工具:
你可以對程序進行profile,也可以進行調試。
閉包Closures中的內存泄露
所謂閉包就是指函數中的函數,內部函數可以訪問外部函數的參數或者變量,從而導致外部函數內部變量的引用。
我們看一個簡單閉包的例子:
function parentFunction(paramA){var a = paramA;function childFunction(){return a + 2;}return childFunction();}上面的例子中,childFunction引用了parentFunction的變量a。只要childFunction還在被使用,a就無法被釋放,從而導致parentFunction無法被垃圾回收。事實上Closure默認就包含了對父function的引用。
我們看下面的例子:
<html><body><script type="text/javascript">document.write("Program to illustrate memory leak via closure");window.onload=function outerFunction(){var obj = document.getElementById("element");obj.onclick=function innerFunction(){alert("Hi! I will leak");};obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));// This is used to make the leak significant};</script><button id="element">Click Me</button></body></html>上面的例子中,obj引用了 DOM 對象element,而element的onclick是outerFunction的內部函數,從而導致了對外部函數的引用,從而引用了obj。
這樣最終導致循環引用,造成內存泄露。
怎么解決這個問題呢?
一個簡單的辦法就是在使用完obj之后,將其賦值為null,從而中斷循環引用的關系:
<html><body><script type="text/javascript">document.write("Avoiding memory leak via closure by breaking the circularreference");window.onload=function outerFunction(){var obj = document.getElementById("element");obj.onclick=function innerFunction(){alert("Hi! I have avoided the leak");// Some logic here};obj.bigString=new Array(1000).join(new Array(2000).join("XXXXX"));obj = null; //This breaks the circular reference};</script><button id="element">"Click Here"</button></body></html>還有一種很簡潔的辦法就是不要使用閉包,將其分成兩個獨立的函數:
<html><head><script type="text/javascript">document.write("Avoid leaks by avoiding closures!");window.onload=function(){var obj = document.getElementById("element");obj.onclick = doesNotLeak;}function doesNotLeak(){//Your Logic herealert("Hi! I have avoided the leak");}</script></head><body><button id="element">"Click Here"</button></body></html>本文作者:flydean程序那些事
本文鏈接:http://www.flydean.com/js-memory-management/
本文來源:flydean的博客
歡迎關注我的公眾號:「程序那些事」最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
總結
以上是生活随笔為你收集整理的javascript中的内存管理的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: javascript中的模块系统
- 下一篇: javascript中的闭包closur