首頁 web前端 js教程 怎樣使用垃圾回收器

怎樣使用垃圾回收器

May 30, 2018 am 10:11 AM
使用 回收 垃圾

這次帶給大家怎樣使用垃圾回收器,使用垃圾回收器的注意事項有哪些,以下就是實戰案例,一起來看一下。

垃圾回收器是一把十足的雙面刃。其好處是可以大幅簡化程式的記憶體管理程式碼,因為記憶體管理無需程式設計師來操作,由此也減少了(但沒有根除)長時間運轉的程式的記憶體洩漏。對於某些程式設計師來說,它甚至能夠提升程式碼的效能。

另一方面,選擇垃圾回收器也意味著程式當中無法完全掌控內存,而這正是行動終端開發的癥結。對於JavaScript,程式中沒有任何記憶體管理的可能-ECMAScript標準中沒有暴露任何垃圾回收器的介面。網頁應用既沒有辦法管理內存,也沒辦法給垃圾回收器提示。

嚴格來講,使用垃圾回收器的語言在效能上不一定比不使用垃圾回收器的語言好或差。在C語言中,分配和釋放記憶體有可能是非常昂貴的操作,為了使分配的記憶體能夠在將來釋放,堆的管理會趨於複雜。而在託管記憶體的語言中,分配記憶體往往只是增加一個指標。但隨後我們就會看到,當記憶體耗盡時,垃圾回收器介入回收所產生的巨大代價。一個未經琢磨的垃圾回收器,會致使程式在運行中出現長時間、無法預期的停頓,這直接影響到互動系統(特別是帶有動畫效果的)在使用上的體驗。引用計數系統時常被吹捧為垃圾回收機制的替代品,但當大型子圖中的最後一個物件的引用解除後,同樣也會有無法預期的停頓。而且引用計數系統在頻繁執行讀取、改寫、儲存操作時,也會有可觀的效能負擔。

或好或壞,JavaScript需要一個垃圾回收器。 V8的垃圾回收器實現現在已經成熟,其性能優異,停頓短暫,性能負擔也非常可控。

基本概念

垃圾回收器要解決的最基本問題就是,辨別需要回收的記憶體。一旦辨別完畢,這些記憶體區域即可在未來的分配中重複使用,或是返還給作業系統。一個物件當它不是處於活躍狀態的時候它就死了(廢話)。一個物件處於活躍狀態,當且僅當它被一個根物件或另一個活躍物件指向。根物件被定義為處於活躍狀態,是瀏覽器或V8所引用的物件。比方說,被局部變數所指向的對象屬於根對象,因為它們的棧被視為根對象;全域對象屬於根對象,因為它們總是可被存取;瀏覽器對象,如DOM元素,也屬於根對象,儘管在某些場合下它們只是弱引用。

從側面來說,上面的定義非常寬鬆。實際上我們可以說,當一個物件可被程式引用時,它就是活躍的。例如:

function f() {
	 var obj = {x: 12};
	 g(); // 可能包含一个死循环
	 return obj.x;
	}
登入後複製
def scavenge():
	 swap(fromSpace, toSpace)
	 allocationPtr = toSpace.bottom
	 scanPtr = toSpace.bottom
	 for i = 0..len(roots):
	 root = roots[i]
	 if inFromSpace(root):
	  rootCopy = copyObject(&allocationPtr, root)
	  setForwardingAddress(root, rootCopy)
	  roots[i] = rootCopy
	 while scanPtr < allocationPtr:
	 obj = object at scanPtr
	 scanPtr += size(obj)
	 n = sizeInWords(obj)
	 for i = 0..n:
	  if isPointer(obj[i]) and not inOldSpace(obj[i]):
	  fromNeighbor = obj[i]
	  if hasForwardingAddress(fromNeighbor):
	   toNeighbor = getForwardingAddress(fromNeighbor)
	  else:
	   toNeighbor = copyObject(&allocationPtr, fromNeighbor)
	   setForwardingAddress(fromNeighbor, toNeighbor)
	  obj[i] = toNeighbor
	def copyObject(*allocationPtr, object):
	 copy = *allocationPtr
	 *allocationPtr += size(object)
	 memcpy(copy, object, size(object))
	 return copy
登入後複製

在這個演算法的執行過程中,我們總是維護兩個出區中的指標:allocationPtr指向我們即將為新物件分配記憶體的地方,scanPtr指向我們即將進行活躍檢查的下一個對象。 scanPtr所指向地址之前的對像是處理過的對象,它們及其鄰接都在出區,其指針都是更新過的,位於scanPtr和allocationPtr之間的對象,會被複製至出區,但這些對象內部所包含的指標如果指向入區中的對象,則這些入區中的物件不會被複製。邏輯上,你可以將scanPtr和allocationPtr之間的物件想像為一個廣度優先搜尋用到的物件佇列。

譯註:廣度優先搜尋中,通常會將節點從佇列頭部取出並展開,將展開得到的子節點存入佇列末端,周而復始進行。這個過程與更新兩個指標間物件的過程相似。

我們在演算法的初始時,複製新區所有可從根對象達到的對象,之後進入一個大的循環。在循環的每一輪,我們都會從佇列中刪除一個對象,也就是對scanPtr增量,然後追蹤存取對象內部的指針。如果指針不指向入區,則不管它,因為它必然指向老生區,而這就不是我們的目標了。而如果指標指向入區中某個對象,但我們還沒有複製(未設定轉送位址),則將這個物件複製至出區,也就是增加到我們佇列的末端,同時也就是對allocationPtr增量。這時我們也會將一個轉送位址存到出區物件的首字,替換掉Map指標。這個轉址就是物件複製後所存放的位址。垃圾回收器可以輕易將轉送位址與Map指標分清,因為Map指標經過了標記,而這個位址則未標記。如果我們發現一個指針,而其指向的物件已經複製過了(設定過轉送位址),我們就把這個指標更新為轉送位址,然後打上標記。

演算法在所有物件都處理完畢時終止(即scanPtr和allocationPtr相遇)。這時入區的內容都可視為垃圾,未來可能會被釋放或重複使用。

秘密武器:寫入屏障

上面有一個細節被忽略了:如果新生區中某個對象,只有一個指向它的指針,而這個指針恰好是在老生區的對象當中,我們如何知道新生區中那個對像是活躍的呢?顯然我們並不希望將老生區再遍歷一次,因為老生區中的對像很多,這樣做一次消耗太大。

為了解決這個問題,實際上在寫緩衝區中有一個列表,列表中記錄了所有老生區物件指向新生區的情況。新物件誕生的時候,並不會有指向它的指針,而當有老生區中的物件出現指向新生區物件的指針時,我們便記錄下來這樣的跨區指向。由於這種記錄行為總是發生在寫入操作時,它被稱為寫入屏障——因為每個寫入操作都要經歷這樣一關。

你可能好奇,如果每次進行寫入操作都要經過寫屏障,豈不是會多出大量的程式碼麼?沒錯,這就是我們這種垃圾回收機制的代價之一。但情況沒你想像的那麼嚴重,寫操作畢竟比讀取操作少。某些垃圾回收演算法(不是V8的)會採用讀取屏障,而這需要硬體來輔助才能確保一個較低的消耗。 V8也有一些最佳化來降低寫入屏障帶來的消耗:

大多數的腳本執行時間都是發生在Crankshaft當中的,而Crankshaft常常能靜態地判斷出某個物件是否處於新生區。對於指向這些物件的寫入操作,可以無需寫入屏障。

Crankshaft中新出現了一種最佳化,即當物件不存在指向它的非局部參考時,該物件會被指派在堆疊上。而一個棧上物件的相關寫入操作顯然無需寫屏障。 (譯註:新生區和老生區在堆上。)

「老→新」這樣的情況相對較為少見,因此透過將「新→新」和「老→老」兩種常見情況的程式碼做優化,可以相對提升多數情形下的效能。每個頁都以1MB對齊,因此給定一個物件的記憶體位址,透過將低20bit濾除來快速定位其所在的頁;而頁頭有相關的標識來表明其屬於新生區還是老生區,因此透過判斷兩個物件所屬的區域,也可以快速確定是否為「老→新」。

一旦我們找到「老→新」的指針,我們就可以將其記錄在寫緩衝區的末端。經過一定的時間(寫緩衝區滿的時候),我們將其排序,合併相同的項目,然後再除去已經不符合“老→新”這一情形的指針。 (譯註:這樣指標的數量就會減少,寫入屏障的時間相應也會縮短)

「標記-清除」演算法與「標記-緊縮」演算法

Scavenge演算法對於快速回收、緊密縮小片記憶體效果很好,但對於大片記憶體則消耗過大。因為Scavenge演算法需要出區和入區兩個區域,這對於小片記憶體尚可,而對於超過數MB的記憶體就開始變得不切實際了。老生區包含有數百MB的數據,對於這麼大的區域,我們採取另外兩種相互較為接近的演算法:「標記-清除」演算法與「標記-緊縮」演算法。

這兩個演算法都包含兩個階段:標記階段,清除或緊縮階段。

在标记阶段,所有堆上的活跃对象都会被标记。每个页都会包含一个用来标记的位图,位图中的每一位对应页中的一字(译注:一个指针就是一字大小)。这个标记非常有必要,因为指针可能会在任何字对齐的地方出现。显然,这样的位图要占据一定的空间(32位系统上占据3.1%,64位系统上占据1.6%),但所有的内存管理机制都需要这样占用,因此这种做法并不过分。除此之外,另有2位来表示标记对象的状态。由于对象至少有2字长,因此这些位不会重叠。状态一共有三种:如果一个对象的状态为白,那么它尚未被垃圾回收器发现;如果一个对象的状态为灰,那么它已被垃圾回收器发现,但它的邻接对象仍未全部处理完毕;如果一个对象的状态为黑,则它不仅被垃圾回收器发现,而且其所有邻接对象也都处理完毕。

如果将堆中的对象看作由指针相互联系的有向图,标记算法的核心实际是深度优先搜索。在标记的初期,位图是空的,所有对象也都是白的。从根可达的对象会被染色为灰色,并被放入标记用的一个单独分配的双端队列。标记阶段的每次循环,GC会将一个对象从双端队列中取出,染色为黑,然后将它的邻接对象染色为灰,并把邻接对象放入双端队列。这一过程在双端队列为空且所有对象都变黑时结束。特别大的对象,如长数组,可能会在处理时分片,以防溢出双端队列。如果双端队列溢出了,则对象仍然会被染为灰色,但不会再被放入队列(这样他们的邻接对象就没有机会再染色了)。因此当双端队列为空时,GC仍然需要扫描一次,确保所有的灰对象都成为了黑对象。对于未被染黑的灰对象,GC会将其再次放入队列,再度处理。

以下是标记算法的伪码:

markingDeque = []
	overflow = false
	def markHeap():
	 for root in roots:
	 mark(root)
	 do:
	 if overflow:
	  overflow = false
	  refillMarkingDeque()
	 while !markingDeque.isEmpty():
	  obj = markingDeque.pop()
	  setMarkBits(obj, BLACK)
	  for neighbor in neighbors(obj):
	  mark(neighbor)
	 while overflow
	 
	def mark(obj):
	 if markBits(obj) == WHITE:
	 setMarkBits(obj, GREY)
	 if markingDeque.isFull():
	  overflow = true
	 else:
	  markingDeque.push(obj)
	def refillMarkingDeque():
	 for each obj on heap:
	 if markBits(obj) == GREY:
	  markingDeque.push(obj)
	  if markingDeque.isFull():
	  overflow = true
	  return
登入後複製

标记算法结束时,所有的活跃对象都被染为了黑色,而所有的死对象则仍是白的。这一结果正是清理和紧缩两个阶段所期望的。

标记算法执行完毕后,我们可以选择清理或是紧缩,这两个算法都可以收回内存,而且两者都作用于页级(注意,V8的内存页是1MB的连续内存块,与虚拟内存页不同)。

清理算法扫描连续存放的死对象,将其变为空闲空间,并将其添加到空闲内存链表中。每一页都包含数个空闲内存链表,其分别代表小内存区(<256字)、中内存区(<2048字)、大内存区(<16384字)和超大内存区(其它更大的内存)。清理算法非常简单,只需遍历页的位图,搜索连续的白对象。空闲内存链表大量被scavenge算法用于分配存活下来的活跃对象,但也被紧缩算法用于移动对象。有些类型的对象只能被分配在老生区,因此空闲内存链表也被它们使用。

紧缩算法会尝试将对象从碎片页(包含大量小空闲内存的页)中迁移整合在一起,来释放内存。这些对象会被迁移到另外的页上,因此也可能会新分配一些页。而迁出后的碎片页就可以返还给操作系统了。迁移整合的过程非常复杂,因此我只提及一些细节而不全面讲解。大概过程是这样的。对目标碎片页中的每个活跃对象,在空闲内存链表中分配一块其它页的区域,将该对象复制至新页,并在碎片页中的该对象上写上转发地址。迁出过程中,对象中的旧地址会被记录下来,这样在迁出结束后V8会遍历它所记录的地址,将其更新为新的地址。由于标记过程中也记录了不同页之间的指针,此时也会更新这些指针的指向。注意,如果一个页非常“活跃”,比如其中有过多需要记录的指针,则地址记录会跳过它,等到下一轮垃圾回收再进行处理。

增量标记与惰性清理

你应该想到了,当一个堆很大而且有很多活跃对象时,标记-清除和标记-紧缩算法会执行的很慢。起初我研究V8时,垃圾回收所引发的500-1000毫秒的停顿并不少见。这种情况显然很难接受,即使是对于移动设备。

2012年年中,Google引入了两项改进来减少垃圾回收所引起的停顿,并且效果显著:增量标记和惰性清理。

增量標記允許堆的標記發生在幾次5-10毫秒(行動裝置)的小停頓中。增量標記在堆的大小達到一定的閾值時啟用,啟用之後每當一定量的記憶體分配後,腳本的執行就會停頓並進行一次增量標記。就像普通的標記一樣,增量標記也是一個深度優先搜索,並同樣採用白灰黑機制來分類物件。

但增量標記和普通標記不同的是,物件的圖譜關係可能會改變!我們需要特別注意的是,那些從黑對象指向白對象的新指標。回想一下,黑對象表示已完全被垃圾回收器掃描,並不會再進行二次掃描。因此如果有「黑→白」這樣的指標出現,我們就有可能將那個白物件漏掉,錯當死對象處理掉。 (譯註:標記過程結束後剩餘的白對像都被認為是死對象。)於是我們必須再度啟用寫入屏障。現在寫入屏障不只記錄「舊→新」指針,同時還要記錄「黑→白」指針。一旦發現這樣的指針,黑對象會被重新染色為灰對象,重新放回雙端隊列。當演算法將該物件取出時,其包含的指標會被重新掃描,這樣活躍的白物件就不會漏掉。

增量標記完成後,惰性清理就開始了。所有的物件都已被處理,因此非死即活,堆上多少空間可以變成空閒成為定局。此時我們可以不急著釋放那些空間,而將清理的過程延遲一下也並無大礙。因此無需一次清理所有的頁,垃圾回收器會視需要逐一清理,直到所有的頁都清理完畢。這時增量標記又蓄勢待發了。

Google近期也新增了平行清理支援。由於腳本的執行緒不會再觸及死對象,頁的清理任務可以放在另一個單獨的執行緒中進行並且只需極少的同步工作。同樣的支援工作也正在並行標記上開展著,但目前仍處於早期試驗階段。

總結

垃圾回收真的很複雜。我在文章中已經略過了大量的細節,而文章仍然變得很長。我有一個同事說他覺得研究垃圾回收器比暫存器分配還要可怕,我表示確實如此。也就是說,我寧可將這些繁瑣的細節交給執行時間來處理,也不想交給所有的應用程式開發者來做。儘管垃圾回收存在一些性能問題而且偶爾會出現靈異現象,它還是將我們從大量的細節中解放了出來,以便讓我們集中精力於更重要的事情上。

相信看了本文案例你已經掌握了方法,更多精彩請關注php中文網其它相關文章!

推薦閱讀:

如何使用nodeJs爬蟲

#如何使用node.js中render和send

#

以上是怎樣使用垃圾回收器的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1666
14
CakePHP 教程
1425
52
Laravel 教程
1325
25
PHP教程
1273
29
C# 教程
1252
24
如何使用磁力鏈接 如何使用磁力鏈接 Feb 18, 2024 am 10:02 AM

磁力連結是一種用於下載資源的連結方式,相較於傳統的下載方式更為便利和有效率。使用磁力連結可以透過點對點的方式下載資源,而不需要依賴中介伺服器。本文將介紹磁力連結的使用方法及注意事項。一、什麼是磁力連結磁力連結是一種基於P2P(Peer-to-Peer)協定的下載方式。透過磁力鏈接,使用者可以直接連接到資源的發布者,從而完成資源的共享和下載。與傳統的下載方式相比,磁

如何使用mdf和mds文件 如何使用mdf和mds文件 Feb 19, 2024 pm 05:36 PM

mdf檔案和mds檔案怎麼用隨著電腦科技的不斷進步,我們可以透過多種方式來儲存和共享資料。在數位媒體領域,我們經常會遇到一些特殊的文件格式。在這篇文章中,我們將討論一種常見的文件格式—mdf和mds文件,並介紹它們的使用方法。首先,我們需要了解mdf檔案和mds檔案的含義。 mdf是CD/DVD鏡像檔的副檔名,而mds檔則是mdf檔的元資料檔。

crystaldiskmark是什麼軟體? -crystaldiskmark如何使用? crystaldiskmark是什麼軟體? -crystaldiskmark如何使用? Mar 18, 2024 pm 02:58 PM

CrystalDiskMark是一款適用於硬碟的小型HDD基準測試工具,可快速測量順序和隨機讀取/寫入速度。接下來就讓小編為大家介紹一下CrystalDiskMark,以及crystaldiskmark如何使用吧~一、CrystalDiskMark介紹CrystalDiskMark是一款廣泛使用的磁碟效能測試工具,用於評估機械硬碟和固態硬碟(SSD)的讀取和寫入速度和隨機I/O性能。它是一款免費的Windows應用程序,並提供用戶友好的介面和各種測試模式來評估硬碟效能的不同方面,並被廣泛用於硬體評

foob​​ar2000怎麼下載? -foobar2000怎麼使用 foob​​ar2000怎麼下載? -foobar2000怎麼使用 Mar 18, 2024 am 10:58 AM

foob​​ar2000是一款能隨時收聽音樂資源的軟體,各種音樂無損音質帶給你,增強版本的音樂播放器,讓你得到更全更舒適的音樂體驗,它的設計理念是將電腦端的高級音頻播放器移植到手機上,提供更便捷高效的音樂播放體驗,介面設計簡潔明了易於使用它採用了極簡的設計風格,沒有過多的裝飾和繁瑣的操作能夠快速上手,同時還支持多種皮膚和主題,根據自己的喜好進行個性化設置,打造專屬的音樂播放器支援多種音訊格式的播放,它還支援音訊增益功能根據自己的聽力情況調整音量大小,避免過大的音量對聽力造成損害。接下來就讓小編為大

pip鏡像來源簡易指南:輕鬆掌握使用方法 pip鏡像來源簡易指南:輕鬆掌握使用方法 Jan 16, 2024 am 10:18 AM

輕鬆上手:如何使用pip鏡像來源隨著Python在全球的普及,pip成為了Python套件管理的標準工具。然而,許多開發者在使用pip安裝套件時面臨的常見問題是速度慢。這是因為預設情況下,pip從Python官方來源或其他外部來源下載包,而這些來源可能位於海外伺服器,導致下載速度緩慢。為了提高下載速度,我們可以使用pip鏡像來源。什麼是pip鏡像來源?簡單來說,就

BTCC教學:如何在BTCC交易所綁定使用MetaMask錢包? BTCC教學:如何在BTCC交易所綁定使用MetaMask錢包? Apr 26, 2024 am 09:40 AM

MetaMask(中文也叫小狐狸錢包)是一款免費的、廣受好評的加密錢包軟體。目前,BTCC已支援綁定MetaMask錢包,綁定後可使用MetaMask錢包進行快速登錄,儲值、買幣等,且首次綁定還可獲得20USDT體驗金。在BTCCMetaMask錢包教學中,我們將詳細介紹如何註冊和使用MetaMask,以及如何在BTCC綁定並使用小狐狸錢包。 MetaMask錢包是什麼? MetaMask小狐狸錢包擁有超過3,000萬用戶,是當今最受歡迎的加密貨幣錢包之一。它可免費使用,可作為擴充功能安裝在網絡

網易信箱大師怎麼用 網易信箱大師怎麼用 Mar 27, 2024 pm 05:32 PM

網易郵箱,作為中國網友廣泛使用的一種電子郵箱,一直以來以其穩定、高效的服務贏得了用戶的信賴。而網易信箱大師,則是專為手機使用者打造的信箱軟體,它大大簡化了郵件的收發流程,讓我們的郵件處理變得更加便利。那麼網易信箱大師該如何使用,具體又有哪些功能呢,下文中本站小編將為大家帶來詳細的內容介紹,希望能幫助到大家!首先,您可以在手機應用程式商店搜尋並下載網易信箱大師應用程式。在應用寶或百度手機助手中搜尋“網易郵箱大師”,然後按照提示進行安裝即可。下載安裝完成後,我們打開網易郵箱帳號並進行登錄,登入介面如下圖所示

百度網盤app怎麼用 百度網盤app怎麼用 Mar 27, 2024 pm 06:46 PM

在如今雲端儲存已成為我們日常生活和工作中不可或缺的一部分。百度網盤作為國內領先的雲端儲存服務之一,憑藉其強大的儲存功能、高效的傳輸速度以及便捷的操作體驗,贏得了廣大用戶的青睞。而且無論你是想要備份重要文件、分享資料,還是在線上觀看影片、聽取音樂,百度網盤都能滿足你的需求。但很多用戶可能對百度網盤app的具體使用方法還不了解,那麼這篇教學就將為大家詳細介紹百度網盤app如何使用,還有疑惑的用戶們就快來跟著本文詳細了解一下吧!百度雲網盤怎麼用:一、安裝首先,下載並安裝百度雲軟體時,請選擇自訂安裝選

See all articles