?nèi)存管理-垃圾回收機(jī)制-內(nèi)存泄露)
內(nèi)存管理C 語(yǔ)言這樣的底層語(yǔ)言一般都有底層的內(nèi)存管理接口比如 malloc()和free()。而 JavaScript 是在創(chuàng)建變量對(duì)象字符串等時(shí)自動(dòng)進(jìn)行了分配內(nèi)存并且在不使用它們時(shí)“自動(dòng)”釋放。釋放的過(guò)程稱(chēng)為垃圾回收。整個(gè)過(guò)程為分配內(nèi)存 - 使用內(nèi)存 - 清理內(nèi)存。程序的運(yùn)行需要內(nèi)存。只要程序提出要求操作系統(tǒng)或者運(yùn)行時(shí)(runtime)就必須供給內(nèi)存。對(duì)于持續(xù)運(yùn)行的服務(wù)進(jìn)程(daemon)必須及時(shí)釋放不再用到的內(nèi)存。否則內(nèi)存占用越來(lái)越高輕則影響系統(tǒng)性能重則導(dǎo)致進(jìn)程崩潰。也就是說(shuō)不再用到的內(nèi)存如果沒(méi)有及時(shí)釋放就叫做內(nèi)存泄漏。JavaScript 在定義變量或者函數(shù)調(diào)用時(shí)就完成了內(nèi)存分配varn123;// 給數(shù)值變量分配內(nèi)存varo{a:1,b:null,};// 給對(duì)象及其包含的值分配內(nèi)存// 給數(shù)組及其包含的值分配內(nèi)存就像對(duì)象一樣vara[1,null,abra];functionf(a){returna2;}// 給函數(shù)可調(diào)用的對(duì)象分配內(nèi)存// 函數(shù)表達(dá)式也能分配一個(gè)對(duì)象someElement.addEventListener(click,function(){someElement.style.backgroundColorblue;},false,);vardnewDate();// 分配一個(gè) Date 對(duì)象varedocument.createElement(div);// 分配一個(gè) DOM 元素V8 引擎內(nèi)存圖新生代空間(New Space/Young Generation)作用主要用于存放生命周期短的小對(duì)象。這部分空間較小但對(duì)象的創(chuàng)建和銷(xiāo)毀都非常頻繁。 組成新生代內(nèi)存被分為兩個(gè)半空間From Space和To Space。. √初始時(shí)對(duì)象被分配到From Space中。 √使用復(fù)制算法(Copying Garbage Collection)進(jìn)行垃圾回收。 √當(dāng)進(jìn)行垃圾回收時(shí)活動(dòng)的對(duì)象即仍然被引用的對(duì)象)被復(fù)制到To Space中而非活動(dòng)的對(duì)象不再被引用的對(duì)象)被丟棄。 √完成復(fù)制后From Space和To Space的角色互換新的對(duì)象將分配到新的From Space中原To Space成為新的From Space。老生代空間(Old Space/Old Generation)作用存放生命周期長(zhǎng)或從新生代晉升過(guò)來(lái)的對(duì)象。 √當(dāng)對(duì)象在新生代中經(jīng)歷了一定數(shù)量的垃圾回收周期后通常是一到兩次且仍然存活它們被認(rèn)為是生命周期較長(zhǎng)的對(duì)象。 分為兩個(gè)主要區(qū)域 √老指針空間(Old Pointer Space):主要存放包含指向其他對(duì)象的指針的對(duì)象。 √老數(shù)據(jù)空間(Old Data Space):用于存放只包含原始數(shù)據(jù)如數(shù)值、字符串的對(duì)象不含指向其他對(duì)象的指針。大對(duì)象空間(Large Object Space):用于存放大對(duì)象如超過(guò)新生代大小限制的數(shù)組或?qū)ο?。這些對(duì)象直接在大對(duì)象空間中分配避免在新生代和老生代之間的復(fù)制操作。代碼空間(Code Space):存放編譯后的函數(shù)代碼。單元空間(Cell Space)用于存放小的數(shù)據(jù)結(jié)構(gòu)比如閉包的變量環(huán)境。屬性單元空間(Property Cell Space):存放對(duì)象的屬性值主要針對(duì)全局變量或者屬性值對(duì)于訪問(wèn)頻繁的全局變量或者屬性值來(lái)說(shuō)V8在這里存儲(chǔ)是為了提高它的訪問(wèn)效率。映射空間(Map Space):存放對(duì)象的映射即對(duì)象的類(lèi)型信息描述對(duì)象的結(jié)構(gòu)。當(dāng)你定義一個(gè)Person構(gòu)造函數(shù)時(shí)可以通過(guò)它創(chuàng)建出來(lái)oerson1和oerson2。這些實(shí)例(person1)和person2)本身存儲(chǔ)在堆內(nèi)存的相應(yīng)空間中具體是新生代還是老生代取決于它們的生命周期和大小。每個(gè)實(shí)例都會(huì)持有一個(gè)指向其映射的指針這個(gè)映射指明了如何訪問(wèn)name和age屬性目的是訪問(wèn)屬性效果變高)。堆內(nèi)存(Heap Memory)與棧(Stack)堆內(nèi)存JavaScript對(duì)象、字符串等數(shù)據(jù)存放的區(qū)域按照上述分類(lèi)進(jìn)行管理。棧用于存放執(zhí)行上下文中的變量、函數(shù)調(diào)用的返回地址繼續(xù)執(zhí)行哪里的代碼)等棧有助于跟蹤函數(shù)調(diào)用的順序和局部變量。面試題V8引擎的垃圾回收機(jī)制具體是如何工作的V8引擎使用了一種高度優(yōu)化的垃圾回收機(jī)制來(lái)管理內(nèi)存采用了標(biāo)記清除、標(biāo)記整理同時(shí)又結(jié)合了多種策略來(lái)實(shí)現(xiàn)高效的內(nèi)存管理包括結(jié)合了分代回收(Generational Collection)和增量回收(Incremental Collection)等多種策略。分代回收V8將對(duì)象分為“新生代”和“老生代”。新生代存放生命周期短的小對(duì)象使用高效的復(fù)制式垃圾回收算法而老生代存放生命周期長(zhǎng)或從新生代晉升而來(lái)的對(duì)象使用標(biāo)記-清除或標(biāo)記整理算法。這種分代策略減少了垃圾回收的總體開(kāi)銷(xiāo)尤其是針對(duì)短命對(duì)象的快速回收。增量回收為了減少垃圾回收過(guò)程中的停頓時(shí)間V8實(shí)現(xiàn)了增量回收。這意味著垃圾回收過(guò)程被分解為許多小步驟這些小步驟穿插在應(yīng)用程序的執(zhí)行過(guò)程中進(jìn)行。這有助于避免長(zhǎng)時(shí)間的停頓改善了應(yīng)用程序的響應(yīng)性和性能。延遲清理和空閑時(shí)間收集V8還嘗試在CPU空閑時(shí)進(jìn)行垃圾回收以進(jìn)一步減少對(duì)程序執(zhí)行的影響。這些技術(shù)的結(jié)合使得V8能夠在執(zhí)行]avaScript代碼時(shí)有效地管理內(nèi)存同時(shí)最小化垃圾回收對(duì)性能的影響。面試題有哪些操作可能引起內(nèi)存泄漏如何在開(kāi)發(fā)中避免性能優(yōu)化內(nèi)存泄漏通常是指程序中已經(jīng)不再需要使用的內(nèi)存由于某些原因未被垃圾回收器回收從而導(dǎo)致可用內(nèi)存逐漸減少。全局變量濫用創(chuàng)建的全局變量例如忘記使用var,Iet,或const聲明變量可能會(huì)導(dǎo)致這些變量不被回收。未及時(shí)清理的定時(shí)器和回調(diào)函數(shù)比如使用setInterval在不適用時(shí)沒(méi)有及時(shí)清除阻止它們被回收。閉包閉包可以維持對(duì)外部函數(shù)作用域的引用如果這些閉包一直存活它們引用的外部作用域及其變量也無(wú)法被回收。DOM 引用JavaScript對(duì)象持有已從DOM中刪除的元素的引用這會(huì)阻止這些DOM元素的內(nèi)存被釋放。監(jiān)聽(tīng)器的回調(diào)在使用完畢后沒(méi)有從DOM元素上移除事件監(jiān)聽(tīng)器這可能導(dǎo)致內(nèi)存泄漏。面試題垃圾回收機(jī)制js 具有自動(dòng)垃圾回收機(jī)制GC。垃圾回收器會(huì)定期周期性找出不在繼續(xù)使用的變量然后釋放其內(nèi)存防止內(nèi)存泄漏管理內(nèi)存資源。在運(yùn)行時(shí)垃圾回收機(jī)制主要通過(guò)追蹤每個(gè)對(duì)象的生命周期來(lái)工作。原理對(duì)象通常在它們不再被程序的任何部分引用時(shí)被視為垃圾。一旦這些對(duì)象被識(shí)別垃圾回收器將自動(dòng)回收它們占用的內(nèi)存空間使這部分內(nèi)存可以重新被分配和使用。垃圾回收機(jī)制有幾種不同的實(shí)現(xiàn)方法最常見(jiàn)的包括引用計(jì)數(shù)每個(gè)對(duì)象都有一個(gè)與之關(guān)聯(lián)的計(jì)數(shù)器記錄引用該對(duì)象的次數(shù)。當(dāng)引用計(jì)數(shù)變?yōu)榱銜r(shí)意味著沒(méi)有任何引用指向該對(duì)象因此可以安全地回收其內(nèi)存。標(biāo)記-清除這種方法通過(guò)從根對(duì)象集合開(kāi)始標(biāo)記所有可達(dá)的對(duì)象。所有未被標(biāo)記的對(duì)象都被視為垃圾并將被清除。標(biāo)記整理與標(biāo)記-清除相似但在清除階段它還會(huì)移動(dòng)存活的對(duì)象以減少內(nèi)存碎片。1. 引用計(jì)數(shù)每個(gè)對(duì)象都有一個(gè)關(guān)聯(lián)的計(jì)數(shù)器通常稱(chēng)為“引用計(jì)數(shù)”。當(dāng)一個(gè)對(duì)象有一個(gè)引用指向它時(shí)那么這個(gè)對(duì)象的引用就1如果另一個(gè)變量也開(kāi)始引用該對(duì)象引用計(jì)數(shù)加1如果一個(gè)變量停止引用該對(duì)象引用計(jì)數(shù)減1。當(dāng)一個(gè)對(duì)象的引用為0時(shí)這個(gè)對(duì)象就可以被銷(xiāo)毀掉varo{a:{b:2,},};// 兩個(gè)對(duì)象被創(chuàng)建一個(gè)作為另一個(gè)的屬性被引用另一個(gè)被分配給變量 o// 很顯然沒(méi)有一個(gè)可以被垃圾收集varo2o;// o2 變量是第二個(gè)對(duì)“這個(gè)對(duì)象”的引用o1;// 現(xiàn)在“這個(gè)對(duì)象”只有一個(gè) o2 變量的引用了“這個(gè)對(duì)象”的原始引用 o 已經(jīng)沒(méi)有varoao2.a;// 引用“這個(gè)對(duì)象”的 a 屬性// 現(xiàn)在“這個(gè)對(duì)象”有兩個(gè)引用了一個(gè)是 o2一個(gè)是 oao2yo;// 雖然最初的對(duì)象現(xiàn)在已經(jīng)是零引用了可以被垃圾回收了// 但是它的屬性 a 的對(duì)象還在被 oa 引用所以還不能回收oanull;// a 屬性的那個(gè)對(duì)象現(xiàn)在也是零引用了// 它可以被垃圾回收了存在的問(wèn)題循環(huán)引用引用計(jì)數(shù)算法考慮到它們互相都有至少一次引用所以它們不會(huì)被回收。當(dāng)讓我們可以通過(guò) WeakMap 弱引用解決。functionf(){varo{};varo2{};o.ao2;// o 引用 o2o2.ao;// o2 引用 oreturnazerty;}f();2. 標(biāo)記清除過(guò)程分為標(biāo)記 - 清除。標(biāo)記清除的核心思路是可達(dá)性(Reachability)這個(gè)算法是設(shè)置一個(gè)根對(duì)象(root object),垃圾回收器會(huì)定期從這個(gè)根開(kāi)始找所有從根開(kāi)始有引用到的對(duì)象對(duì)于哪些沒(méi)有引用到的對(duì)象就認(rèn)為是不可用的對(duì)象在這個(gè)階段垃圾回收器標(biāo)記所有可達(dá)的對(duì)象之后垃圾回收器遍歷所有的對(duì)象收集那些在標(biāo)記階段未被標(biāo)記為可達(dá)的對(duì)象。這些對(duì)象被視為垃圾因?yàn)樗鼈儾辉俦怀绦蛑械钠渌钴S對(duì)象或根對(duì)象所引用。這個(gè)算法可以很好的解決循環(huán)引用的問(wèn)題functiontest(){vara10// 被標(biāo)記 進(jìn)入環(huán)境varb20// 被標(biāo)記 進(jìn)入環(huán)境}test()// 執(zhí)行完畢 之后 a b 又被標(biāo)記 離開(kāi)環(huán)境垃圾回收器在運(yùn)行的時(shí)候會(huì)給存儲(chǔ)在內(nèi)存中的所有變量都加上標(biāo)記當(dāng)然可以使用任何標(biāo)記方式。然后它會(huì)去掉環(huán)境中的變量以及被環(huán)境中的變量引用的變量的標(biāo)記閉包。而在此之后再被加上標(biāo)記的變量將被視為準(zhǔn)備刪除的變量原因是環(huán)境中的變量已經(jīng)無(wú)法訪問(wèn)到這些變量了。最后垃圾回收器完成內(nèi)存清除工作銷(xiāo)毀那些帶標(biāo)記的值并回收它們所占用的內(nèi)存空間。其他垃圾回收算法js 引擎比較廣泛的采用的就是可達(dá)性中的標(biāo)記清除算法當(dāng)然類(lèi)似于V8引擎為了進(jìn)行更好的優(yōu)化它在算法的實(shí)現(xiàn)細(xì)節(jié)上也會(huì)結(jié)合一些其他的算法。標(biāo)記整理(Mark-Compact)和“標(biāo)記-清除”相似不同的是回收期間同時(shí)會(huì)將保留的存儲(chǔ)對(duì)象搬運(yùn)匯集到連續(xù)的內(nèi)存空間從而整合空閑空間避免內(nèi)存碎片化分代收集(Generational collection)一一對(duì)象被分成兩組“新的”和“I舊的”。許多對(duì)象出現(xiàn)完成它們的工作并很快“死去”它們可以很快被清理那些長(zhǎng)期存活的對(duì)象會(huì)變得“老舊”而且被檢查的頻次也會(huì)減少增量收集(Incremental collection)如果有許多對(duì)象并且我們?cè)噲D一次遍歷并標(biāo)記整個(gè)對(duì)象集則可能需要一些時(shí)間并在執(zhí)行過(guò)程中帶來(lái)明顯的延遲。所以引擎試圖將垃圾收集工作分成幾部分來(lái)做分解成多個(gè)小步驟或“增量”然后將這幾部分會(huì)逐一進(jìn)行處理這樣會(huì)有許多微小的延遲而不是一個(gè)大的延遲閑時(shí)收集(ldle-time collection)垃圾收集器只會(huì)在 CPU 空閑時(shí)嘗試運(yùn)行以減少可能對(duì)代碼執(zhí)行的影響。