深入理解JavaScript內(nèi)存管理和GC算法
前言
JavaScript在創(chuàng)建變量(數(shù)組、字符串、對(duì)象等)是自動(dòng)進(jìn)行了分配內(nèi)存,并且在不使用它們的時(shí)候會(huì)“自動(dòng)”的釋放分配的內(nèi)容;JavaScript語(yǔ)言不像其他底層語(yǔ)言一樣,例如C語(yǔ)言,他們提供了內(nèi)存管理的接口,比如malloc()
用于分配所需的內(nèi)存空間、free()
釋放之前所分配的內(nèi)存空間。
我們將釋放內(nèi)存的過(guò)程稱(chēng)為垃圾回收,像JavaScript這種高級(jí)語(yǔ)言提供了內(nèi)存自動(dòng)分配和自動(dòng)回收,因?yàn)檫@個(gè)自動(dòng)就導(dǎo)致許多開(kāi)發(fā)者不會(huì)去關(guān)心內(nèi)存管理。
即使高級(jí)語(yǔ)言提供了自動(dòng)內(nèi)存管理,但是我們也需要對(duì)內(nèi)管管理有一下基本的理解,有時(shí)候自動(dòng)內(nèi)存管理出現(xiàn)了問(wèn)題,我們可以更好的去解決它,或者說(shuō)使用代價(jià)最小的方法解決它。
內(nèi)存的生命周期
其實(shí)不管是什么語(yǔ)言,內(nèi)存的聲明周期大致分為如下幾個(gè)階段:
下面我們對(duì)每一步進(jìn)行具體說(shuō)明:
- 內(nèi)存分配:當(dāng)我們定義變量時(shí),系統(tǒng)會(huì)自動(dòng)為其分配內(nèi)存,它允許在程序中使用這塊內(nèi)存。
- 內(nèi)存使用:在對(duì)變量進(jìn)行讀寫(xiě)的時(shí)候發(fā)生
- 內(nèi)存回收:使用完畢后,自動(dòng)釋放不需要內(nèi)存,也就是由垃圾回收機(jī)制自動(dòng)回收不再使用的內(nèi)存
JavaScript中的內(nèi)存分配
為了保護(hù)開(kāi)發(fā)人員的頭發(fā),JavaScript在定義變量時(shí)就自動(dòng)完成了內(nèi)存分配,示例代碼如下:
let num = 123 // 給數(shù)值變量分配內(nèi)存 let str = '一碗周' // 給字符串分配內(nèi)存 let obj = { name: '一碗周', age: 18, } // 給對(duì)象及其包含的值分配內(nèi)存 // 給數(shù)組及其包含的值分配內(nèi)存(類(lèi)似于對(duì)象) let arr = [1, null, 'abc'] function fun(a) { return a + 2 } // 給函數(shù)(可調(diào)用的對(duì)象)分配內(nèi)存 // 函數(shù)表達(dá)式也能分配一個(gè)對(duì)象 Element.addEventListener( 'click', event => { console.log(event) }, false, )
有些時(shí)候并不會(huì)重新分配內(nèi)存,如下面這段代碼:
// 給數(shù)組及其包含的值分配內(nèi)存(類(lèi)似于對(duì)象) let arr = [1, null, 'abc'] let arr2 = [arr[0], arr[2]] // 這里并不會(huì)重新對(duì)分配內(nèi)存,而是直接存儲(chǔ)原來(lái)的那份內(nèi)存
在JavaScript中使用內(nèi)存
JavaScript中使用值的過(guò)程實(shí)際上是對(duì)分配內(nèi)存進(jìn)行讀取與寫(xiě)入的操作。這里的讀取與寫(xiě)入可能是寫(xiě)入一個(gè)變量、讀取某個(gè)變量的值、寫(xiě)入一個(gè)對(duì)象的屬性值以及為函數(shù)傳遞參數(shù)等。
釋放內(nèi)存
JavaScript中的內(nèi)存釋放是自動(dòng)的,釋放的時(shí)機(jī)就是某些值(內(nèi)存地址)不在使用了,JavaScript就會(huì)自動(dòng)釋放其占用的內(nèi)存。
其實(shí)大多數(shù)內(nèi)存管理的問(wèn)題都在這個(gè)階段。在這里最艱難的任務(wù)就是找到那些不需要的變量。
雖然說(shuō)現(xiàn)在打高級(jí)語(yǔ)言都有自己垃圾回收機(jī)制,雖然現(xiàn)在的垃圾回收算法很多,但是也無(wú)法智能的回收所有的極端情況,這就是我們?yōu)槭裁匆獙W(xué)習(xí)內(nèi)存管理以及垃圾回收算法的理由了。
接下來(lái)我們來(lái)討論一下JavaScript中的垃圾回收以及常用的垃圾回收算法。
JavaScript中的垃圾回收
前面我們也說(shuō)了,JavaScript中的內(nèi)存管理是自動(dòng)的,在創(chuàng)建對(duì)象時(shí)會(huì)自動(dòng)分配內(nèi)存,當(dāng)對(duì)象不在被引用或者不能從根上訪問(wèn)時(shí),就會(huì)被當(dāng)做垃圾給回收掉。
JavaScript中的可達(dá)對(duì)象簡(jiǎn)單的說(shuō)就是可以訪問(wèn)到的對(duì)象,不管是通過(guò)引用還是作用域鏈的方式,只要能訪問(wèn)到的就稱(chēng)之為可達(dá)對(duì)象??蛇_(dá)對(duì)象的可達(dá)是有一個(gè)標(biāo)準(zhǔn)的,就是必須從根上出發(fā)是否能被找到;這里的根可以理解為JavaScript中的全局變量對(duì)象,在瀏覽器環(huán)境中就是window
、在Node環(huán)境中就是global
。
為了更好的理解引用的概念,看下面這一段代碼:
let person = { name: '一碗周', } let man = person person = null
圖解如下:
根據(jù)上面那個(gè)圖我們可以看到,最終這個(gè){ name: '一碗周' }
是不會(huì)被當(dāng)做垃圾給回收掉的,因?yàn)檫€具有一個(gè)引用。
現(xiàn)在我們來(lái)理解一下可達(dá)對(duì)象,代碼如下:
function groupObj(obj1, obj2) { obj1.next = obj2 obj2.prev = obj1 return { obj1, obj2, } } let obj = groupObj({ name: '大明' }, { name: '小明' })
調(diào)用groupObj()
函數(shù)的的結(jié)果obj
是一個(gè)包含兩個(gè)對(duì)象的一個(gè)對(duì)象,其中obj.obj1
的next
屬性指向obj.obj2
;而obj.obj2
的prev
屬性又指向obj.obj2
。最終形成了一個(gè)無(wú)限套娃。
如下圖:
現(xiàn)在來(lái)看下面這段代碼:
delete obj.obj1 delete obj.obj2.prev
我們刪除obj
對(duì)象中的obj1
對(duì)象的引用和obj.obj2
中的prev
屬性對(duì)obj1
的引用。
圖解如下:
此時(shí)的obj1
就被當(dāng)做垃圾給回收了。
GC算法
GC是Garbage collection的簡(jiǎn)寫(xiě),也就是垃圾回收。當(dāng)GC進(jìn)行工作的時(shí)候,它可以找到內(nèi)存中的垃圾、并釋放和回收空間,回收之后方便我們后續(xù)的進(jìn)行使用。
在GC中的垃圾包括程序中不在需要使用的對(duì)象以及程序中不能再訪問(wèn)到的對(duì)象都會(huì)被當(dāng)做垃圾。
GC算法就是工作時(shí)查找和回收所遵循的規(guī)則,常見(jiàn)的GC算法有如下幾種:
- 引用計(jì)數(shù):通過(guò)一個(gè)數(shù)字來(lái)記錄引用次數(shù),通過(guò)判斷當(dāng)前數(shù)字是不是0來(lái)判斷對(duì)象是不是一個(gè)垃圾。
- 標(biāo)記清除:在工作時(shí)為對(duì)象添加一個(gè)標(biāo)記來(lái)判斷是不是垃圾。
- 標(biāo)記整理:與標(biāo)記清除類(lèi)似。
- 分代回收:V8中使用的垃圾回收機(jī)制。
引用計(jì)數(shù)算法
引用計(jì)數(shù)算法的核心思想就是設(shè)置一個(gè)引用計(jì)數(shù)器,判斷當(dāng)前引用數(shù)是否為0 ,從而決定當(dāng)前對(duì)象是不是一個(gè)垃圾,從而垃圾回收機(jī)制開(kāi)始工作,釋放這塊內(nèi)存。
引用計(jì)數(shù)算法的核心就是引用計(jì)數(shù)器 ,由于引用計(jì)數(shù)器的存在,也就導(dǎo)致該算法與其他GC算法有所差別。
引用計(jì)數(shù)器的改變是在引用關(guān)系發(fā)生改變時(shí)就會(huì)發(fā)生變化,當(dāng)引用計(jì)數(shù)器變?yōu)?的時(shí)候,該對(duì)象就會(huì)被當(dāng)做垃圾回收。
現(xiàn)在我們通過(guò)一段代碼來(lái)看一下:
// { name: '一碗周' } 的引用計(jì)數(shù)器 + 1 let person = { name: '一碗周', } // 又增加了一個(gè)引用,引用計(jì)數(shù)器 + 1 let man = person // 取消一個(gè)引用,引用計(jì)數(shù)器 - 1 person = null // 取消一個(gè)引用,引用計(jì)數(shù)器 - 1。此時(shí) { name: '一碗周' } 的內(nèi)存就會(huì)被當(dāng)做垃圾回收 man = null
引用計(jì)數(shù)算法的優(yōu)點(diǎn)如下:
- 發(fā)現(xiàn)垃圾時(shí)立即回收;
- 最大限度減少程序暫停,這里因?yàn)榘l(fā)現(xiàn)垃圾就立刻回收了,減少了程序因內(nèi)存爆滿(mǎn)而被迫停止的現(xiàn)象。
缺點(diǎn)如下:
- 無(wú)法回收循環(huán)引用的對(duì)象;
就比如下面這段代碼:
function fun() { const obj1 = {} const obj2 = {} obj1.next = obj2 obj2.prev = obj1 return '一碗周' } fun()
上面的代碼中,當(dāng)函數(shù)執(zhí)行完成之后函數(shù)體的內(nèi)容已經(jīng)是沒(méi)有作用的了,但是由于obj1
和obj2
都存在不止1個(gè)引用,導(dǎo)致兩種都無(wú)法被回收,就造成了空間內(nèi)存的浪費(fèi)。
- 時(shí)間開(kāi)銷(xiāo)大,這是因?yàn)橐糜?jì)數(shù)算法需要時(shí)刻的去監(jiān)控引用計(jì)數(shù)器的變化。
標(biāo)記清除算法
標(biāo)記清除算法解決了引用計(jì)數(shù)算法的?些問(wèn)題, 并且實(shí)現(xiàn)較為簡(jiǎn)單, 在V8引擎中會(huì)有被?量的使?到。
在使?標(biāo)記清除算法時(shí),未引用對(duì)象并不會(huì)被立即回收.取?代之的做法是,垃圾對(duì)象將?直累計(jì)到內(nèi)存耗盡為?.當(dāng)內(nèi)存耗盡時(shí),程序?qū)?huì)被掛起,垃圾回收開(kāi)始執(zhí)行.當(dāng)所有的未引用對(duì)象被清理完畢 時(shí),程序才會(huì)繼續(xù)執(zhí)行.該算法的核心思想就是將整個(gè)垃圾回收操作分為標(biāo)記和清除兩個(gè)階段完成。
第一個(gè)階段就是遍歷所有對(duì)象,標(biāo)記所有的可達(dá)對(duì)象;第二個(gè)階段就是遍歷所有對(duì)象清除沒(méi)有標(biāo)記的對(duì)象,同時(shí)會(huì)抹掉所有已經(jīng)標(biāo)記的對(duì)象,便于下次的工作。
為了區(qū)分可用對(duì)象與垃圾對(duì)象,我們需要在每?個(gè)對(duì)象中記錄對(duì)象的狀態(tài)。 因此, 我們?cè)诿?個(gè)對(duì)象中加?了?個(gè)特殊的布爾類(lèi)型的域, 叫做marked
。 默認(rèn)情況下, 對(duì)象被創(chuàng)建時(shí)處于未標(biāo)記狀態(tài)。 所以, marked
域被初始化為false
。
標(biāo)記清除算法的圖解如下圖所示:
進(jìn)行垃圾回收完畢之后,將回收的內(nèi)存放在空閑鏈表中方便我們后續(xù)使用。
標(biāo)記清除算法最大的優(yōu)點(diǎn)就是解決了引用計(jì)數(shù)算法無(wú)法回收循環(huán)引用的對(duì)象的問(wèn)題 。就比如下面這段代碼:
function fun() { const obj1 = {}, obj2 = {}, obj3 = {}, obj4 = {}, obj5 = {}, obj6 = {} obj1.next = obj2 obj2.next = obj3 obj2.prev = obj6 obj4.next = obj6 obj4.prev = obj1 obj5.next = obj4 obj5.prev = obj6 return obj1 } const obj = fun()
當(dāng)函數(shù)執(zhí)行完畢后obj4
的引用并不是0,但是使用引用計(jì)數(shù)算法并不能將其作為垃圾回收掉,而使用標(biāo)記清除算法就解決了這個(gè)問(wèn)題。
而標(biāo)記清除算法的缺點(diǎn)也是有的,這種算法會(huì)導(dǎo)致內(nèi)存碎片化,地址不連續(xù);還有就是使用標(biāo)記清除算法即使發(fā)現(xiàn)了垃圾對(duì)象不里能立刻清除,需要到第二次去清除。
標(biāo)記整理算法
標(biāo)記整理算法可以看做是標(biāo)記清除算法的增強(qiáng)型,其步驟也是分為標(biāo)記和清除兩個(gè)階段。
但是標(biāo)記整理算法那的清除階段會(huì)先進(jìn)行整理,移動(dòng)對(duì)象的位置,最后進(jìn)行清除。
步驟如下圖:
V8中的內(nèi)存管理
V8是什么
V8是一款主流的JavaScript執(zhí)行引擎,現(xiàn)在的Node.js和大多數(shù)瀏覽器都采用V8作為JavaScript的引擎。V8的編譯功能采用的是及時(shí)編譯,也稱(chēng)為動(dòng)態(tài)翻譯或運(yùn)行時(shí)編譯,是一種執(zhí)行計(jì)算機(jī)代碼的方法,這種方法涉及在程序執(zhí)行過(guò)程中(在執(zhí)行期)而不是在執(zhí)行之前進(jìn)行編譯。
V8引擎對(duì)內(nèi)存是設(shè)有上限的,在64位操作系統(tǒng)下上限是1.5G的,而32位操作系統(tǒng)的上限是800兆的。至于為什么設(shè)置內(nèi)存上限主要是內(nèi)容V8引擎主要是為瀏覽器而準(zhǔn)備的,不適合太大的空間;還有一點(diǎn)就是這個(gè)大小的垃圾回收是很快的,用戶(hù)幾乎沒(méi)有感覺(jué),從而增加用戶(hù)體驗(yàn)。
V8垃圾回收策略
V8引擎采用的是分代回收的思想,主要是將我們的內(nèi)存按照一定的規(guī)則分成兩類(lèi),一個(gè)是新生代存儲(chǔ)區(qū),另一個(gè)是老生代存儲(chǔ)區(qū)。
新生代的對(duì)象為存活時(shí)間較短的對(duì)象,簡(jiǎn)單來(lái)說(shuō)就是新產(chǎn)生的對(duì)象,通常只支持一定的容量(64位操作系統(tǒng)32兆、32位操作系統(tǒng)16兆),而老生代的對(duì)象為存活事件較長(zhǎng)或常駐內(nèi)存的對(duì)象,簡(jiǎn)單來(lái)說(shuō)就是經(jīng)歷過(guò)新生代垃圾回收后還存活下來(lái)的對(duì)象,容量通常比較大。
下圖展示了V8中的內(nèi)存:
V8引擎會(huì)根據(jù)不同的對(duì)象采用不同的GC算法,V8中常用的GC算法如下:
- 分代回收
- 空間復(fù)制
- 標(biāo)記清除
- 標(biāo)記整理
- 標(biāo)記增量
新生代對(duì)象垃圾回收
上面我們也介紹了,新生代中存放的是存活時(shí)間較短的對(duì)象。新生代對(duì)象回收過(guò)程采用的是復(fù)制算法和標(biāo)記整理算法。
復(fù)制算法將我們的新生代內(nèi)存區(qū)域劃分為兩個(gè)相同大小的空間,我們將當(dāng)前使用狀態(tài)的空間稱(chēng)之為From狀態(tài),空間狀態(tài)的空間稱(chēng)之為T(mén)o狀態(tài),
如下圖所示:
我們將活動(dòng)的對(duì)象全部存儲(chǔ)到From空間,當(dāng)空間接近滿(mǎn)的時(shí)候,就會(huì)觸發(fā)垃圾回收。
首先需要將新生代From空間中的活動(dòng)對(duì)象做標(biāo)記整理,標(biāo)記整理完成之后將標(biāo)記后的活動(dòng)對(duì)象拷貝到To空間并將沒(méi)有進(jìn)行標(biāo)記的對(duì)象進(jìn)行回收;最后將From空間和To空間進(jìn)行交換。
還有一點(diǎn)需要說(shuō)的就是在進(jìn)行對(duì)象拷貝的時(shí)候,會(huì)出現(xiàn)新生代對(duì)象移動(dòng)至老生代對(duì)象中。
這些被移動(dòng)的對(duì)象是具有指定條件的,主要有兩種:
- 經(jīng)過(guò)一輪垃圾回收還存活的新生代對(duì)象會(huì)被移動(dòng)到老生代對(duì)象中
- 在To空間占用率超過(guò)了25%,這個(gè)對(duì)象也會(huì)被移動(dòng)到老生代對(duì)象中(25%的原因是怕影響后續(xù)內(nèi)存分配)
如此可知,新生代對(duì)象的垃圾回收采用的方案是空間換時(shí)間。
老生代對(duì)象垃圾回收
老生代區(qū)域存放的對(duì)象就是存活時(shí)間長(zhǎng)、占用空間大的對(duì)象。也正是因?yàn)槠浯婊畹臅r(shí)間長(zhǎng)且占用空間大,也就導(dǎo)致了不能采用復(fù)制算法,如果采用復(fù)制算法那就會(huì)造成時(shí)間長(zhǎng)和空間浪費(fèi)的現(xiàn)象。
老生代對(duì)象一般采用標(biāo)記清除、標(biāo)記整理和增量標(biāo)記算法進(jìn)行垃圾回收。
在清除階段主要才采用標(biāo)記清除算法來(lái)進(jìn)行回收,當(dāng)一段時(shí)候后,就會(huì)產(chǎn)生大量不連續(xù)的內(nèi)存碎片,過(guò)多的碎片無(wú)法分配足夠的內(nèi)存時(shí),就會(huì)采用標(biāo)記整理算法來(lái)整理我們的空間碎片。
老生代對(duì)象的垃圾回收會(huì)采用增量標(biāo)記算法來(lái)優(yōu)化垃圾回收的過(guò)程,增量標(biāo)記算法如下圖所示:
由于JavaScript是單線程,所以程序執(zhí)行和垃圾回收同時(shí)只能運(yùn)行一個(gè),這就會(huì)導(dǎo)致在執(zhí)行垃圾回收的時(shí)候程序卡頓,這樣給用戶(hù)的體驗(yàn)肯定是不好的。
所以提出了增量標(biāo)記,在程序運(yùn)行時(shí),程序先跑一段時(shí)間,然后進(jìn)行進(jìn)行初步的標(biāo)記,這個(gè)標(biāo)記有可能只標(biāo)記直接可達(dá)的對(duì)象,然后程序繼續(xù)跑一段時(shí)間,在進(jìn)行增量標(biāo)記 ,也即是標(biāo)記哪些間接可達(dá)的對(duì)象。由此反復(fù),直至結(jié)束。
Performance工具
由于JavaScript沒(méi)有給我們提供操作內(nèi)存的API,只能靠本身提供的內(nèi)存管理,但是我們并不知道實(shí)際上的內(nèi)存管理是什么樣的。而有時(shí)我們需要時(shí)刻關(guān)注內(nèi)存的使用情況,Performance工具提供了多種監(jiān)控內(nèi)存的方式。
Performance使用步驟
首先我們打開(kāi)Chrome瀏覽器(這里我們使用的是Chrome瀏覽器,其他瀏覽器也是可以的),在地址欄中輸入我們的目標(biāo)地址,然后打開(kāi)開(kāi)發(fā)者工具,選擇【性能】面板。
選擇性能面板后開(kāi)啟錄制功能,然后去訪問(wèn)具體界面,模仿用戶(hù)去執(zhí)行一些操作,然后停止錄制,最后我們可以在分析界面中分析記錄的內(nèi)存信息。
結(jié)果如下圖所示:
內(nèi)存問(wèn)題的體現(xiàn)
出現(xiàn)內(nèi)存的問(wèn)題主要有如下幾種表現(xiàn):
- 頁(yè)面出現(xiàn)延遲加載或經(jīng)常性暫停,它的底層就伴隨著頻繁的垃圾回收的執(zhí)行;為什么會(huì)頻繁的進(jìn)行垃圾回收,可能是一些代碼直接導(dǎo)致內(nèi)存爆滿(mǎn)而且需要立刻進(jìn)行垃圾回收。
關(guān)于這個(gè)問(wèn)題我們可以通過(guò)內(nèi)存變化圖進(jìn)行分析其原因:
- 頁(yè)面持續(xù)性出現(xiàn)糟糕的性能表現(xiàn),也就是說(shuō)在我們使用的過(guò)程中,頁(yè)面給我們的感覺(jué)就是一直不好用,它的底層我們一般認(rèn)為都會(huì)存在內(nèi)存膨脹,所謂的內(nèi)存膨脹就是當(dāng)前頁(yè)面為了達(dá)到某種速度從而申請(qǐng)遠(yuǎn)大于本身需要的內(nèi)存,申請(qǐng)的這個(gè)存在超過(guò)了我們?cè)O(shè)備本身所能提供的大小,這個(gè)時(shí)候我們就能感知到一個(gè)持續(xù)性的糟糕性能的體驗(yàn)。
導(dǎo)致內(nèi)存膨脹的問(wèn)題有可能是我們代碼的問(wèn)題,也有可能是設(shè)備本身就很差勁,想要分析定位并解決的話(huà)需要我們?cè)诙鄠€(gè)設(shè)備上進(jìn)行反復(fù)的測(cè)試
- 頁(yè)面的性能隨著時(shí)間的延長(zhǎng)導(dǎo)致頁(yè)面越來(lái)越差,加載時(shí)間越來(lái)越長(zhǎng),出現(xiàn)這種問(wèn)題的原因可能是由于代碼的原因出現(xiàn)內(nèi)存泄露。
想要檢測(cè)內(nèi)存是否泄漏,我們可以通過(guò)內(nèi)存總視圖來(lái)監(jiān)聽(tīng)我們的內(nèi)存,如果內(nèi)存是持續(xù)升高的,就可能已經(jīng)出現(xiàn)了內(nèi)存泄露。
監(jiān)控內(nèi)存的方式
在瀏覽器中監(jiān)控內(nèi)存主要有以下幾種方式:
- 瀏覽器提供的任務(wù)管理器
- Timeline時(shí)序圖
- 堆快照查找分離DOM
- 判斷是否存在頻繁的垃圾回收
接下來(lái)我們就分別講解這幾種方式。
任務(wù)管理器監(jiān)控內(nèi)存
在瀏覽器中按【Shift】+【ESC】鍵即可打開(kāi)瀏覽器提供的任務(wù)管理器,下圖展示了Chrome瀏覽器中的任務(wù)管理器,我們來(lái)解讀一下
上圖中我們可以看到【掘金】標(biāo)簽頁(yè)的【內(nèi)存占用空間】表示的的這個(gè)頁(yè)面的DOM在瀏覽器中所占的內(nèi)存,如果不斷增加就表示有新的DOM在創(chuàng)建;而后面的【JavaScript使用的內(nèi)存】(默認(rèn)不開(kāi)啟,需要通過(guò)右鍵開(kāi)啟)表示的是JavaScript中的堆,而括號(hào)中的大小表示JavaScript中的所有可達(dá)對(duì)象。
Timeline記錄內(nèi)存
上面描述的瀏覽器中提供的任務(wù)管理器只能用來(lái)幫助我們判斷頁(yè)面是否存在問(wèn)題,卻不能定位頁(yè)面的問(wèn)題。
Timeline是Performance工具中的一個(gè)小的選項(xiàng)卡,其中以毫秒為單位記錄了頁(yè)面中的情況,從而可以幫助我們更簡(jiǎn)單的定位問(wèn)題。
堆快照查找分離DOM
堆快照是很有針對(duì)性的查找當(dāng)前的界面對(duì)象中是否存在一些分離的DOM,分離DOM的存在也就是存在內(nèi)存泄漏。
首先我們先要弄清楚DOM有幾種狀態(tài):
- 首先,DOM對(duì)象存在DOM樹(shù)中,這屬于正常的DOM
- 然后,不存在DOM樹(shù)中且不存在JS引用,這屬于垃圾DOM對(duì)象,是需要被回收的
- 最后,不存在DOM樹(shù)中但是存在JS引用,這就是分離DOM,需要我們手動(dòng)進(jìn)行釋放。
查找分離DOM的步驟:打開(kāi)開(kāi)發(fā)者工具→【內(nèi)存面板】→【用戶(hù)配置】→【獲取快照】→在【過(guò)濾器】中輸入Detached
來(lái)查找分離DOM,
如下圖所示:
查找到創(chuàng)建的分離DOM后,我們找到該DOM的引用,然后進(jìn)行釋放。
判斷是否存在頻繁的垃圾回收
因?yàn)?code>GC工作時(shí)應(yīng)用程序是停止的,如果當(dāng)前垃圾回收頻繁工作,而且時(shí)間過(guò)長(zhǎng)的話(huà)對(duì)頁(yè)面來(lái)說(shuō)很不友好,會(huì)導(dǎo)致應(yīng)用假死說(shuō)我狀態(tài),用戶(hù)使用中會(huì)感知應(yīng)用有卡頓。
我們可以通過(guò)如下方式進(jìn)行判斷是否存在頻繁的垃圾回收,具體如下:
- 通過(guò)Timeline時(shí)序圖判斷,對(duì)當(dāng)前性能面板中的內(nèi)存走勢(shì)進(jìn)行監(jiān)控,如果其中頻繁的上升下降,就出現(xiàn)了頻繁的垃圾回收。這個(gè)時(shí)候要定位代碼,看看是執(zhí)行什么的時(shí)候造成了這種情況。
- 使用瀏覽器任務(wù)管理器會(huì)簡(jiǎn)單一些,任務(wù)管理器中主要是數(shù)值的變化,其數(shù)據(jù)頻繁的瞬間增加減小,也是頻繁的垃圾回收。
寫(xiě)在最后
本篇文章介紹了JavaScript的垃圾回收機(jī)制以及常用的垃圾回收算法;還講解了V8引擎中的內(nèi)存管理,最后介紹了Performance工具如何使用。
到此這篇關(guān)于深入理解JavaScript內(nèi)存管理和GC算法的文章就介紹到這了,更多相關(guān)JS內(nèi)存管理內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Bootstrap編寫(xiě)一個(gè)兼容主流瀏覽器的受眾巨幕式風(fēng)格頁(yè)面
這篇文章主要介紹了Bootstrap編寫(xiě)一個(gè)兼容IE8、谷歌等主流瀏覽器的受眾巨幕式風(fēng)格頁(yè)面,感興趣的小伙伴們可以參考一下2016-07-07JavaScript數(shù)據(jù)結(jié)構(gòu)之雙向鏈表
這篇文章主要為大家詳細(xì)介紹了JavaScript數(shù)據(jù)結(jié)構(gòu)之雙向鏈表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-03-03JS實(shí)現(xiàn)線性表的順序表示方法示例【經(jīng)典數(shù)據(jù)結(jié)構(gòu)】
這篇文章主要介紹了JS實(shí)現(xiàn)線性表的順序表示方法,簡(jiǎn)單分析了線性表的原理并結(jié)合實(shí)例形式給出了線性表的插入與刪除實(shí)現(xiàn)技巧,需要的朋友可以參考下2017-04-04解決webpack多頁(yè)面內(nèi)存溢出的方法示例
這篇文章主要介紹了解決webpack多頁(yè)面內(nèi)存溢出的方法示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10javascript實(shí)現(xiàn)簡(jiǎn)易聊天室
這篇文章主要為大家詳細(xì)介紹了javascript實(shí)現(xiàn)簡(jiǎn)易聊天室,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07用js實(shí)現(xiàn)的自定義的對(duì)話(huà)框的實(shí)現(xiàn)代碼
javascript alert函數(shù)的替代方案,一個(gè)自定義的對(duì)話(huà)框的方法2010-03-03多種方法實(shí)現(xiàn)load加載完成后把圖片一次性顯示出來(lái)
如何一個(gè)load 加載完成后把圖片一次性顯示出來(lái),下面有個(gè)不錯(cuò)的方法,希望對(duì)大家有所幫助2014-02-02Javascript通過(guò)控制類(lèi)名更改樣式
這篇文章主要介紹了Javascript通過(guò)控制類(lèi)名更改樣式,下面來(lái)和小編一起來(lái)學(xué)習(xí)吧2019-05-05layui點(diǎn)擊數(shù)據(jù)表格添加或刪除一行的例子
今天小編就為大家分享一篇layui點(diǎn)擊數(shù)據(jù)表格添加或刪除一行的例子,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2019-09-09JavaScript 異步調(diào)用框架 (Part 4 - 鏈?zhǔn)秸{(diào)用)
我們已經(jīng)實(shí)現(xiàn)了一個(gè)簡(jiǎn)單的異步調(diào)用框架,然而還有一些美中不足,那就是順序執(zhí)行的異步函數(shù)需要用嵌套的方式來(lái)聲明。2009-08-08