javascript中有GC(垃圾回收機(jī)制)。JavaScript是使用垃圾回收機(jī)制的語言,執(zhí)行環(huán)境負(fù)責(zé)在代碼執(zhí)行時(shí)管理內(nèi)存,會(huì)自動(dòng)將垃圾對(duì)象(沒有被引用的對(duì)象)從內(nèi)存中銷毀。
本教程操作環(huán)境:windows7系統(tǒng)、javascript1.8.5版、Dell G3電腦。
JavaScript 中的垃圾回收機(jī)制(GC)
垃圾回收相關(guān)概念
① 什么是垃圾
沒有被使用(引用)的對(duì)象就是垃圾。
② 什么是垃圾回收
沒有被引用的對(duì)象被銷毀,內(nèi)存被釋放,就是垃圾回收。
C、C++ 等編程語言需要手動(dòng)垃圾回收。
Java、JavaScript、PHP、Python 等語言自動(dòng)垃圾回收。
JS中擁有自動(dòng)的垃圾回收機(jī)制,會(huì)自動(dòng)將這些垃圾對(duì)象從內(nèi)存中銷毀,我們不需要也不能進(jìn)行垃圾回收的操作。我們需要做的只是要將不再使用的對(duì)象設(shè)置為 null 即可。
為什么需要垃圾回收
- 在C / C++中,跟蹤內(nèi)存的使用和管理內(nèi)存對(duì)開發(fā)者來說是很大的負(fù)擔(dān)
- JavaScript是使用垃圾回收機(jī)制的語言,也就是說執(zhí)行環(huán)境負(fù)責(zé)在代碼執(zhí)行時(shí)管理內(nèi)存,幫開發(fā)者卸下了這個(gè)負(fù)擔(dān)
- 通過自動(dòng)內(nèi)存管理實(shí)現(xiàn)內(nèi)存的分配和資源的回收
- 基本思路很簡(jiǎn)單,確定哪個(gè)變量不會(huì)再被使用了,把它的內(nèi)存空間釋放
- 這個(gè)過程是周期性的,意思是這個(gè)垃圾回收程序每隔一段時(shí)間就會(huì)運(yùn)行一次
- 像JS中的對(duì)象、字符串、對(duì)象的內(nèi)存是不固定的,只有真正用到的時(shí)候才會(huì)動(dòng)態(tài)分配內(nèi)存
- 這些內(nèi)存需在不使用后進(jìn)行釋放以便再次使用,否則在計(jì)算機(jī)可用內(nèi)存耗盡后造成崩潰
- 瀏覽器發(fā)展史上的垃圾回收法主要有
- 引用計(jì)數(shù)法
- 標(biāo)記清除法
引用計(jì)數(shù)法
思路
- 變量只是對(duì)值進(jìn)行引用
- 當(dāng)變量引用該值時(shí),引用次數(shù)+1
- 當(dāng)該變量的引用被覆蓋或者清除時(shí),引用次數(shù)-1
- 當(dāng)引用次數(shù)為0時(shí),就可以安全地釋放這塊內(nèi)存。
let arr = [1, 0, 1] // [1, 0, 1]這塊內(nèi)存被arr引用 引用次數(shù)為1 arr = [0, 1, 0] // [1, 0, 1]的內(nèi)存引用次數(shù)為0被釋放 // [0, 1, 0]的內(nèi)存被arr引用 引用次數(shù)為1 const tmp = arr // [0, 1, 0]的內(nèi)存被tmp引用 引用次數(shù)為2
循環(huán)引用問題
Netscape Navigator 3.0 采用
- 在這個(gè)例子中,ObjectA和ObjectB的屬性分別相互引用
- 造成這個(gè)函數(shù)執(zhí)行后,Object被引用的次數(shù)不會(huì)變成0,影響了正常的GC。
- 如果執(zhí)行多次,將造成嚴(yán)重的內(nèi)存泄漏。
- 而標(biāo)記清除法則不會(huì)出現(xiàn)這個(gè)問題。
function Example(){ let ObjectA = new Object(); let ObjectB = new Object(); ObjectA.p = ObjectB; ObjectB.p = ObjectA; } Example();
- 解決方法:在函數(shù)結(jié)束時(shí)將其指向null
ObjectA = null; ObjectB = null;
標(biāo)記清除法
為了解決循環(huán)引用造成的內(nèi)存泄漏問題,Netscape Navigator 4.0 開始采用標(biāo)記清除法
到了 2008 年,IE、Firefox、Opera、Chrome 和 Safari 都在自己的 JavaScript 實(shí)現(xiàn)中采用標(biāo)記清理(或 其變體),只是在運(yùn)行垃圾回收的頻率上有所差異。
思路
- 在變量進(jìn)入執(zhí)行上下文時(shí)打上“進(jìn)入”標(biāo)記
- 同時(shí)在變量離開執(zhí)行上下文時(shí)也打上“離開”標(biāo)記
- 從此以后,無法訪問這個(gè)變量
- 在下一次垃圾回收時(shí)進(jìn)行內(nèi)存的釋放
function Example(n){ const a = 1, b = 2, c = 3; return n * a * b * c; } // 標(biāo)記Example進(jìn)入執(zhí)行上下文 const n = 1; // 標(biāo)記n進(jìn)入執(zhí)行上下文 Example(n); // 標(biāo)記a,b,c進(jìn)入執(zhí)行上下文 console.log(n); // 標(biāo)記a, b, c離開執(zhí)行上下文,等待垃圾回收
const和let聲明提升性能
- const和let不僅有助于改善代碼風(fēng)格,同時(shí)有利于垃圾回收性能的提升
- const和let使JS有了塊級(jí)作用域,當(dāng)塊級(jí)作用域比函數(shù)作用域更早結(jié)束時(shí),垃圾回收程序更早介入
- 盡早回收該回收的內(nèi)存,提升了垃圾回收的性能
V8引擎的垃圾回收
V8引擎的垃圾回收采用標(biāo)記清除法與分代回收法
分為新生代和老生代
新生代
新生代垃圾回收采用
Scavenge
算法
分配給常用內(nèi)存和新分配的小量?jī)?nèi)存
-
內(nèi)存大小
- 32位系統(tǒng)16M內(nèi)存
- 64位系統(tǒng)32M內(nèi)存
-
分區(qū)
- 新生代內(nèi)存分為以下兩區(qū),內(nèi)存各占一半
- From space
- To space
-
運(yùn)行
- 實(shí)際運(yùn)行的只有From space
- To space處于空閑狀態(tài)
-
Scavenge
算法- 當(dāng)From space內(nèi)存使用將要達(dá)到上限時(shí)開始垃圾回收,將From space中的不可達(dá)對(duì)象都打上標(biāo)記
- 將From space的未標(biāo)記對(duì)象復(fù)制到To space。
- 解決了內(nèi)存散落分塊的問題(不連續(xù)的內(nèi)存空間)
- 相當(dāng)于用空間換時(shí)間。
- 然后清空From space、將其閑置,也就是轉(zhuǎn)變?yōu)門o space,俗稱反轉(zhuǎn)。
-
新生代 -> 老生代
- 新生代存放的是新分配的小量?jī)?nèi)存,如果達(dá)到以下條件中的一個(gè),將被分配至老生代
- 內(nèi)存大小達(dá)到From space的25%
- 經(jīng)歷了From space <-> To space的一個(gè)輪回
- 新生代存放的是新分配的小量?jī)?nèi)存,如果達(dá)到以下條件中的一個(gè),將被分配至老生代
老生代
老生代采用
mark-sweep
標(biāo)記清除和mark-compact
標(biāo)記整理
通常存放較大的內(nèi)存塊和從新生代分配過來的內(nèi)存塊
- 內(nèi)存大小
- 32位系統(tǒng)700M左右
- 64位系統(tǒng)1.4G左右
- 分區(qū)
- Old Object Space
- 字面的老生代,存放的是新生代分配過來的內(nèi)存。
- Large Object Space
- 存放其他區(qū)域放不下的較大的內(nèi)存,基本都超過1M
- Map Space
- 存放存儲(chǔ)對(duì)象的映射關(guān)系
- Code Space
- 存儲(chǔ)編譯后的代碼
- Old Object Space
- 回收流程
- 標(biāo)記分類(三色標(biāo)記)
- 未被掃描,可回收,下面簡(jiǎn)稱
1類
- 掃描中,不可回收,下面簡(jiǎn)稱
2類
- 掃描完成,不可回收,下面簡(jiǎn)稱
3類
- 未被掃描,可回收,下面簡(jiǎn)稱
- 遍歷
- 采用深度優(yōu)先遍歷,遍歷每個(gè)對(duì)象。
- 首先將非根部對(duì)象全部標(biāo)記為
1類
,然后進(jìn)行深度優(yōu)先遍歷。 - 遍歷過程中將對(duì)象壓入棧,這個(gè)過程中對(duì)象被標(biāo)記為
2類
。 - 遍歷完成對(duì)象出棧,這個(gè)對(duì)象被標(biāo)記為
3類
。 - 整個(gè)過程直至???/li>
- Mark-sweep
-
標(biāo)記完成之后,將標(biāo)記為
1類
的對(duì)象進(jìn)行內(nèi)存釋放
-
- 標(biāo)記分類(三色標(biāo)記)
-
Mark-compact
-
垃圾回收完成之后,內(nèi)存空間是不連續(xù)的。
-
這樣容易造成無法分配較大的內(nèi)存空間的問題,從而觸發(fā)垃圾回收。
-
所以,會(huì)有Mark-compact步驟將未被回收的內(nèi)存塊整理為連續(xù)地內(nèi)存空間。
-
頻繁觸發(fā)垃圾回收會(huì)影響引擎的性能,內(nèi)存空間不足時(shí)也會(huì)優(yōu)先觸發(fā)Mark-compact
-
垃圾回收優(yōu)化
- 增量標(biāo)記
- 如果用集中的一段時(shí)間進(jìn)行垃圾回收,新生代倒還好,老生代如果遍歷較大的對(duì)象,可能會(huì)造成卡頓。
- 增量標(biāo)記:使垃圾回收程序和應(yīng)用邏輯程序交替運(yùn)行,思想類似Time Slicing
- 并行回收
- 在垃圾回收的過程中,開啟若干輔助線程,提高垃圾回收效率。
- 并發(fā)回收
- 在邏輯程序執(zhí)行的過程中,開啟若干輔助線程進(jìn)行垃圾回收,清理和主線程沒有任何邏輯關(guān)系的內(nèi)存。
內(nèi)存泄露場(chǎng)景
全局變量
// exm1 function Example(){ exm = 'LeBron' } // exm2 function Example(){ this.exm = 'LeBron' } Example()
未清除的定時(shí)器
const timer = setInterval(() => { //... }, 1000) // clearInterval(timer)
閉包
function debounce(fn, time) { let timeout = null; return function () { if (timeout) { clearTimeout(timeout); } timeout = setTimeout(() => { fn.apply(this, arguments); }, time); }; } const fn = debounce(handler, 1000); // fn引用了timeout
未清除的DOM元素引用
const element = { // 此處引用了DOM元素 button:document.getElementById('LeBron'), select:document.getElementById('select') } document.body.removeChild(document.getElementById('LeBron'))
如何檢測(cè)內(nèi)存泄漏
這個(gè)其實(shí)不難,瀏覽器原帶的開發(fā)者工具Performance就可以
- 步驟
- F12打開開發(fā)者工具
- 選擇Performance工具欄
- 勾選屏幕截圖和Memory
- 點(diǎn)擊開始錄制
- 一段時(shí)間之后結(jié)束錄制
- 結(jié)果
- 堆內(nèi)存會(huì)周期性地分配和釋放
- 如果堆內(nèi)存的min值在逐漸上升則存在內(nèi)存泄漏
優(yōu)化內(nèi)存使用
1、盡量不在for循環(huán)中定義函數(shù)
// exm const fn = (idx) => { return idx * 2; } function Example(){ for(let i=0;i<1000;i++){ //const fn = (idx) => { // return idx * 2; // } const res = fn(i); } }
2、盡量不在for循環(huán)中定義對(duì)象
function Example() { const obj = {}; let res = ""; for (let i = 0; i < 1000; i++) { // const obj = { // a: i, // b: i * 2, // c: i * 3, // }; obj.a = i; obj.b = i * 2; obj.c = i * 3; res += JSON.stringify(obj); } return res }
3、清空數(shù)組
arr = [0, 1, 2] arr.length = 0; // 清空了數(shù)組,數(shù)組類型不變 // arr = [] // 重新申請(qǐng)了一塊空數(shù)組對(duì)象內(nèi)存
【推薦學(xué)習(xí):javascript高級(jí)教程】