導航:首頁 > 源碼編譯 > 垃圾回收機制演算法

垃圾回收機制演算法

發布時間:2023-11-28 18:32:37

Ⅰ JVM垃圾回收的「三色標記演算法」實現,內容太干

三色標記法是一種垃圾回收法,它可以讓JVM不發生或僅短時間發生STW(Stop The World),從而達到清除JVM內存垃圾的目的。JVM中的 CMS、G1垃圾回收器 所使用垃圾回收演算法即為三色標記法。

三色標記法將對象的顏色分為了黑、灰、白,三種顏色。

白色 :該對象沒有被標記過。(對象垃圾)

灰色 :該對象已經被標記過了,但該對象下的屬性沒有全被標記完。(GC需要從此對象中去尋找垃圾)

黑色 :該對象已經被標記過了,且該對象下的屬性也全部都被標記過了。(程序所需要的對象)

從我們main方法的根對象(JVM中稱為GC Root)開始沿著他們的對象向下查找,用黑灰白的規則,標記出所有跟GC Root相連接的對象,掃描一遍結束後,一般需要進行一次短暫的STW(Stop The World),再次進行掃描,此時因為黑色對象的屬性都也已經被標記過了,所以只需找出灰色對象並順著繼續往下標記(且因為大部分的標記工作已經在第一次並發的時候發生了,所以灰色對象數量會很少,標記時間也會短很多), 此時程序繼續執行,GC線程掃描所有的內存,找出掃描之後依舊被標記為白色的對象(垃圾),清除。

具體流程:

在JVM虛擬機中有兩種常見垃圾回收器使用了該演算法:CMS(Concurrent Mark Sweep)、G1(Garbage First) ,為了解決三色標記法對對象漏標問題各自有各自的法:

CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。目前很大一部分的java應用集中在互聯網網站或者基於瀏覽器的B/S系統的服務端上,這類應用通常都會較為關注服務的響應速度,希望系統停頓時間盡可能短,以給用戶帶來良好的交互體驗。CMS收集器就非常符合這類應用的需求(但是實際由於某些問題,很少有使用CMS作為主要垃圾回收器的)。

從名字(包含「Mark Sweep」)上就可以看出CMS收集器是基於標記-清除演算法實現的,它的運作過程相對於前面幾種收集器來說要更復雜一些,整個過程分為四個步驟,包括:1)初始標記(CMS initial mark) 2)並發標記(CMS concurrent mark) 3)重新標記(CMS remark) 4)並發清除(CMS concurrent sweep)

其中初始標記、重新標記這兩個步驟仍然需要「Stop The World」。初始標記僅僅只是標記一下GCRoots能直接關聯到的對象,速度很快;

並發標記階段就是從GC Roots的直接關聯對象開始遍歷整個對象圖的過程,這個過程耗時較長但是不需要停頓用戶線程,可以與垃圾收集線程一起並發運行;

重新標記階段則是為了修正並發標記期間,因用戶程序繼續運作而導致標記產生變動的那一部分對象的標記記錄,這個階段的停頓時間通常會比初始標記階段稍長一些,但也遠比並發標記階段的時間短;

最後是並發清除階段,清理刪除掉標記階段判斷的已經死亡的對象,由於不需要移動存活對象,所以這個階段也是可以與用戶線程同時並發的。由於在整個過程中耗時最長的並發標記和並發清除階段中,垃圾收集器線程都可以與用戶線程一起工作,所以從總體上來說,CMS收集器的內存回收過程是與用戶線程一起並發執行的。

在應對漏標問題時,CMS使用了增量更新(Increment Update)方法來做:

在一個未被標記的對象(白色對象)被重新引用後, 引用它的對象若為黑色則要變成灰色,在下次二次標記時讓GC線程繼續標記它的屬性對象

但是就算是這樣,其仍然是存在漏標的問題:

G1(Garbage First)物理內存不再分代,而是由一塊一塊的Region組成,但是邏輯分代仍然存在。G1不再堅持固定大小以及固定數量的分代區域劃分,而是把連續的Java堆劃分為多個大小相等的獨立區域(Region),每一個Region都可以根據需要,扮演新生代的Eden空間、Survivor空間,或者老年代空間。收集器能夠對扮演不同角色的Region採用不同的策略去處理,這樣無論是新創建的對象還是已經存活了一段時間、熬過多次收集的舊對象都能獲取很好的收集效果。

Region中還有一類特殊的Humongous區域,專門用來存儲大對象。G1認為只要大小超過了一個Region容量一半的對象即可判定為大對象。每個Region的大小可以通過參數-XX:G1HeapRegionSize設定,取值范圍為1MB~32MB,且應為2的N次冪。而對於那些超過了整個Region容量的超級大對象,將會被存放在N個連續的Humongous Region之中,G1的大多數行為都把Humongous Region作為老年代的一部分來進行看待,如圖所示

Card Table(多種垃圾回收器均具備)

RSet(Remembered Set)

是輔助GC過程的一種結構,典型的空間換時間工具,和Card Table有些類似。

後面說到的CSet(Collection Set)也是輔助GC的,它記錄了GC要收集的Region集合,集合里的Region可以是任意年代的。

在GC的時候,對於old->young和old->old的跨代對象引用,只要掃描對應的CSet中的RSet即可。邏輯上說每個Region都有一個RSet,RSet記錄了其他Region中的對象引用本Region中對象的關系,屬於points-into結構(誰引用了我的對象)。

而Card Table則是一種points-out(我引用了誰的對象)的結構,每個Card 覆蓋一定范圍的Heap(一般為512Bytes)。G1的RSet是在Card Table的基礎上實現的:每個Region會記錄下別的Region有指向自己的指針,並標記這些指針分別在哪些Card的范圍內。這個RSet其實是一個Hash Table,Key是別的Region的起始地址,Value是一個集合,裡面的元素是Card Table的Index。每個Region中都有一個RSet,記錄其他Region到本Region的引用信息;使得垃圾回收器不需要掃描整個堆找到誰引用當前分區中的對象,只需要掃描RSet即可。

CSet(Collection Set)

一組可被回收的分區Region的集合, 是多個對象的集合內存區域。

新生代與老年代的比例

5% - 60%,一般不使用手工指定,因為這是G1預測停頓時間的基準,這地方簡要說明一下,G1可以指定一個預期的停頓時間,然後G1會根據你設定的時間來動態調整年輕代的比例,例如時間長,就將年輕代比例調小,讓YGC盡早行。

SATB(Snapshot At The Beginning), 在應對漏標問題時,G1使用了SATB方法來做,具體流程:

因為SATB在重新標記環節只需要去重新掃描那些被推到堆棧中的引用,並配合Rset來判斷當前對象是否被引用來進行回收;

並且在最後G1並不會選擇回收所有垃圾對象,而是根據Region的垃圾多少來判斷與預估回收價值(指回收的垃圾與回收的STW時間的一個預估值),將一個或者多個Region放到CSet中,最後將這些Region中的存活對象壓縮並復制到新的Region中,清空原來的Region。

會,當內存滿了的時候就會進行Full GC;且JDK10之前的Full GC,為單線程的,所以使用G1需要避免Full GC的產生。

解決方案:

Ⅱ JVM垃圾收集機制

JVM垃圾回收機制是java程序員必須要了解的知識,對於程序調優具有很大的幫助(同時也是大廠面試必問題)。

要了解垃圾回收機制,主要從三個方面:

(1)垃圾回收面向的對象是誰?

(2)垃圾回收演算法有哪些?

(3)垃圾收集器有哪些?每個收集器有什麼特點。

接下來一一講解清楚:

一、垃圾回收面向的對象

也就是字面意思, 垃圾 回收嘛,重要的是垃圾,那什麼對象是垃圾呢,簡單來說就是無用的或已死的對象。這樣又引申出來什麼對象是已死的,怎麼判斷對象是否已死?

判斷對象是否已死有兩種演算法和對象引用分類:

(1)引用計數演算法:

也是字面意思,通過給對象添加引用計數器,添加引用+1,反之-1。當引用為0的時候,此時對象就可判斷為無用的。

優點:實現簡單,效率高。

缺點:無法解決循環引用(就是A引用B,B也引用A的情況)的問題。

(2)根搜索演算法(也就是GC Roots):

通過一系列稱為GC Roots的對象,向下搜索,路徑為引用鏈,當某個對象無法向上搜索到GC Roots,也就是成為GC Roots不可達,則為無用對象。

如果一個對象是GC Roots不可達,則需要經過兩次標記才會進行回收,第一次標記的時候,會判斷是否需要執行finalize方法(沒必要執行的情況:沒實現finalize方法或者已經執行過)。如果需要執行finalize方法,則會放入一個回收隊列中,對於回收隊列中的對象,如果執行finalize方法之後,沒法將對象重新跟GC Roots進行關聯,則會進行回收。

很抽象,對吧,來一個明了的解釋?

比如手機壞了(不可達對象),有錢不在乎就直接拿去回收(這就是沒實現finalize方法),如果已經修過但是修不好了(已經執行過finalize方法),就直接拿去回收站回收掉。如果沒修過,就會拿去維修店(回收隊列)進行維修,實在維修不好了(執行了finalize方法,但是無法連上GC Roots),就會拿去回收站回收掉了。

那什麼對象可以成為GC Roots呢?

1>虛擬機棧中的引用對象

2>本地方法棧中Native方法引用的對象

2>方法區靜態屬性引用對象

3>方法區常量引用對象

(3)對象引用分類

1>強引用:例如實例一個對象,就是即使內存不夠用了,打死都不回收的那種。

2>軟引用:有用非必須對象,內存夠,則不進行回收,內存不夠,則回收。例如A借錢給B,當A還有錢的時候,B可以先不還,A沒錢了,B就必須還了。

3>弱引用:非必須對象,只能存活到下一次垃圾回收前。

4>虛引用:幽靈引用,必須跟引用隊列配合使用,目的是回收前收到系統通知。

下面是java的引用類型結構圖:

(1)軟引用示例

內存夠用的情況:

運行結果:

內存不夠用的情況:

運行結果:

(2)弱引用示例結果:

無論如何都會被回收

(3)虛引用示例:

運行結果:

解釋:為什麼2和5的輸出為null呢,如下

3為null是因為還沒有進行gc,所以對象還沒加入到引用隊列中,在gc後就加入到了引用隊列中,所以6有值。

這個虛引用在GC後會將對象放到引用隊列中,所以可以在對象回收後做相應的操作,判斷對象是否在引用隊列中,可以進行後置通知,類似spring aop的後置通知。

二、垃圾回收發生的區域

垃圾回收主要發生在堆內存裡面,而堆內存又細分為 年輕代 老年代 ,默認情況下年輕代和老年代比例為1:2,比如整個堆內存大小為3G,年輕代和老年代分別就是1G和2G,想要更改這個比例需要修改JVM參數-XX:NewRatio,

比如-XX:NewRatio=4,那老年代:年輕代=4:1。而年輕代又分為Eden區,S0(Survivor From)和S1(Survivor To)區,一般Eden:S0:S1=8:1:1,如果想要更改此比例,則修改JVM參數-XX:SurvivorRatio=4,此時就是Eden:S0:S1=4:1:1。

在年輕代發生GC稱為Young GC,老年代發生GC成為Full GC,Young GC比Full GC頻繁。

解析Young GC:

JVM啟動後,第一次GC,就會把Eden區存活的對象移入S0區;第二次GC就是Eden區和S0一起GC,此時會把存活的對象移入S1區,S0清空;第三次GC就是Eden區和S1區進行GC,會把存活的對象移入S0區,如此往復循環15次(默認),就會把存活的對象存入老年區。

類似與如果有三個桶,編號分別為1(1號桶內的沙子是源源不斷的,就像工地上。你們沒去工地搬過磚可能不知道,但是我真的去工地上搬過啊),2,3。1裡面裝有沙子,需要將沙子篩為細沙。首先將桶1內的沙子篩選一遍過後的放置於桶2,第二次篩選就會將桶1和桶2裡面的沙子一起篩,篩完之後放到桶3內,桶2清空。第三次篩選就會將桶1和桶3的沙子一起篩選,曬完放到桶2內,桶3清空。如此往復循環15次,桶2或桶3裡面的沙子就是合格的沙子,就需要放到備用桶內以待使用。

上述中桶1就是Eden區,桶2就是S0區,桶3就是S1區。

三、垃圾回收演算法

三種,分別是復制演算法,標記-清除演算法,標記-整理演算法。

(1)復制演算法。

其會將內存區域分成同樣大小的兩塊,一塊用來使用,另外一塊在GC的時候存放存活的對象,然後將使用的一塊清除。如此循環往復。

適用於新生代。

優點:沒有內存碎片,缺點:只能使用一般的內存。

(2)標記-清除演算法。

使用所有內存區域,在GC的時候會將需要回收的內存區域先進行標記,然後同意回收。

適用於老年代。

缺點:產生大量內存碎片,會直接導致大對象無法分配內存。

(3)標記-整理演算法。

使用所有內存區域,在GC的時候會先將需要回收的內存區域進行標記,然後將存活對象忘一邊移動,最後將清理掉邊界以外的所有內存。

適用於老年代。

四、GC日誌查看

利用JVM參數-XX:+PrintGCDetails就可以在GC的時候列印出GC日誌。

年輕代GC日誌:

老年代GC日誌:

五、垃圾收集器

主要有四類收集器以及七大收集器

四類:

(1)Serial:單線程收集器,阻塞工作線程,它一工作,全部都得停下。

(2)Paralle:Serial的多線程版本,也是阻塞工作線程

(3)CMS(ConcMarkSweep):並行垃圾收集器,可以和工作線程一起工作。

(4)G1:將堆分成大小一致的區域,然後並發的對其進行垃圾回收。

怎麼查看默認的收集器呢?

用JVM參數-XX:+PrintCommandLineFlags,運行之後會輸出如下參數。可以看到,jdk1.8默認是Parallel收集器。

七大收集器:

(1)Serial:串列垃圾收集器,單線程收集器。用於新生代。用JVM參數-XX:+UseSerialGC開啟,開啟後Young區用Serial(底層復制演算法),Old區用Serial Old(Serial的老年代版本,底層是標記整理演算法)。

(2)ParNew:用於新生代,並行收集器。就是Serial的多線程版本。用JVM參數-XX:+UseParNewGC,young:parnew,復制演算法。Old:serialOld,標記整理演算法。-XX:ParallecGCThreads限制線程回收數量,默認跟cpu數目一樣。只是新生代用並行,老年代用串列。

(3)Parallel Scavenge:並行回收收集器。用JVM參數-XX:+UseParallelGC開啟,young:parallel scavenge(底層是復制演算法),old:parallel old(parallel的老年代版本,底層是標記整理),新生代老年代都用並行回收器。

這個收集器有兩個優點:

可控的吞吐量 :就是工作線程工作90%的時間,回收線程工作10%的時間,即是說有90%的吞吐量。

自適應調節策略 :會動態調節參數以獲取最短的停頓時間。

(4)Parallel Old:Parallel Scavenge的老年代版本,用的是標記整理演算法。用JVM參數-XX:+UseParallelOldGC開啟,新生代用Parallel Scavenge,老年代用Parallel Old

(5)CMS(ConcMarkSweep):並發標記清除。 底層是標記清除演算法,所以會產生內存碎片,同時也會耗cpu。 以獲取最短回收停頓時間為目標。-XX:+UseConcMarkSweep,新生代用ParNew,老年代用CMS。CMS必須在堆內存用完之前進行清除,否則會失敗,這時會調用SerialOld後備收集器。

初始標記和重新標記都會停止工作線程,並發標記和並發清除會跟工作線程一起工作。

(6)SerialOld:老年代串列收集器(以後Hotspot虛擬機會直接移除掉)。

(7)G1:G1垃圾收集器,演算法是標記整理,不會產生內存碎片。橫跨新生代老年代。實現盡量高吞吐量,滿足回收停頓時間更短。

G1可以精確控制垃圾收集的停頓時間,用JVM參數-XX:MaxGCPauseMillis=n,n為停頓時間,單位為毫秒。

區域化內存劃片Region,會把整個堆劃分成同樣大小的區域塊(1MB~32MB),最多2048個內存區域塊,所以能支持的最大內存為32*2048=65535MB,約為64G。

上圖是收集前和收集後的對比,有些對象很大,分割之後就是連續的區域,也即是上圖的Humongous。

上述理論可能有點乏味,下圖很清晰明了(某度找的)。

下面來一張整個垃圾回收機制的思維導圖(太大,分成兩部分)。

=======================================================

我是Liusy,一個喜歡健身的程序猿。

歡迎關注【Liusy01】,一起交流Java技術及健身,獲取更多干貨。

Ⅲ JVM有哪些垃圾回收演算法

標記-清除,標記-復制,標記-整理

python 中的垃圾回收機制

python採用的是 引用計數 機制為主, 標記-清除 分代收集(隔代回收) 兩種機制為輔的策略。

python里每一個東西都是對象,它們的核心就是一個結構體:PyObject

PyObject是每個對象必有的內容,其中ob_refcnt就是做為引用計數。當一個對象有新的引用時,它的ob_refcnt就會增加,當引用它的對象被刪除,它的ob_refcnt就會減少

引用計數為0時,該對象生命就結束了。

引用計數機制的優點:

1、簡單

2、實時性:一旦沒有引用,內存就直接釋放了,不用像其他機製得等到特定時機。實時性還帶來一個好處:處理回收內存的時間分攤到了平時。

引用計數機制的缺點:

1、維護引用計數消耗資源

2、循環引用

案例:

循環引用導致內存泄露

有三種情況會觸發垃圾回收:

gc模塊提供一個介面給開發者設置垃圾回收的選項。上面說到,採用引用計數的方法管理內存的一個缺陷是循環引用,而gc模塊的一個主要功能就是解決循環引用的問題。

常用函數

gc實踐案例

必須要import gc模塊,並且is_enable()=True才會啟動自動垃圾回收。
這個機制的主要作用就是發現並處理不可達的垃圾對象。

在Python中,採用分代收集的方法。把對象分為三代,一開始,對象在創建的時候,放在一代中,如果在一次一代的垃圾檢查中,該對象存活下來,就會被放到二代中,同理在一次二代的垃圾檢查中,該對象存活下來,就會被放到三代中。

gc模塊裡面會有一個長度為3的列表的計數器,可以通過 gc.get_count() 獲取。

gc模快有一個自動垃圾回收的閥值,即通過 gc.get_threshold 函數獲取到的長度為3的元組,例如 (700,10,10)
每一次計數器的增加,gc模塊就會檢查增加後的計數是否達到閥值的數目,如果是,就會執行對應的代數的垃圾檢查,然後重置計數器

注意:
如果循環引用中,兩個對象都定義了 __del__ 方法,gc模塊不會銷毀這些不可達對象,因為gc模塊不知道應該先調用哪個對象的 __del__ 方法,所以為了安全起見,gc模塊會把對象放到 gc.garbage 中,但是不會銷毀對象。

標記清除(Mark—Sweep)』演算法是一種基於追蹤回收(tracing GC)技術實現的垃圾回收演算法。它分為兩個階段:第一階段是標記階段,GC會把所有的『活動對象』打上標記,第二階段是把那些沒有標記的對象『非活動對象』進行回收。那麼GC又是如何判斷哪些是活動對象哪些是非活動對象的呢?

對象之間通過引用(指針)連在一起,構成一個有向圖,對象構成這個有向圖的節點,而引用關系構成這個有向圖的邊。從根對象(root object)出發,沿著有向邊遍歷對象,可達的(reachable)對象標記為活動對象,不可達的對象就是要被清除的非活動對象。根對象就是全局變數、調用棧、寄存器。 mark-sweepg 在上圖中,我們把小黑圈視為全局變數,也就是把它作為root object,從小黑圈出發,對象1可直達,那麼它將被標記,對象2、3可間接到達也會被標記,而4和5不可達,那麼1、2、3就是活動對象,4和5是非活動對象會被GC回收。

標記清除演算法作為Python的輔助垃圾收集技術主要處理的是一些容器對象,比如list、dict、tuple,instance等,因為對於字元串、數值對象是不可能造成循環引用問題。Python使用一個雙向鏈表將這些容器對象組織起來。不過,這種簡單粗暴的標記清除演算法也有明顯的缺點:清除非活動的對象前它必須順序掃描整個堆內存,哪怕只剩下小部分活動對象也要掃描所有對象。

Ⅳ java有哪些垃圾回收演算法

常用的垃圾回收演算法有:
(1).引用計數演算法:
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任何時刻計數器都為0的對象就是不再被使用的,垃圾收集器將回收該對象使用的內存。
引用計數演算法實現簡單,效率很高,微軟的COM技術、ActionScript、Python等都使用了引用計數演算法進行內存管理,但是引用計數演算法對於對象之間相互循環引用問題難以解決,因此java並沒有使用引用計數演算法。
(2).根搜索演算法:
通過一系列的名為「GC Root」的對象作為起點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時,則該對象不可達,該對象是不可使用的,垃圾收集器將回收其所佔的內存。
主流的商用程序語言C#、java和Lisp都使用根搜素演算法進行內存管理。
在java語言中,可作為GC Root的對象包括以下幾種對象:
a. java虛擬機棧(棧幀中的本地變數表)中的引用的對象。
b.方法區中的類靜態屬性引用的對象。
c.方法區中的常量引用的對象。
d.本地方法棧中JNI本地方法的引用對象。
java方法區在Sun HotSpot虛擬機中被稱為永久代,很多人認為該部分的內存是不用回收的,java虛擬機規范也沒有對該部分內存的垃圾收集做規定,但是方法區中的廢棄常量和無用的類還是需要回收以保證永久代不會發生內存溢出。
判斷廢棄常量的方法:如果常量池中的某個常量沒有被任何引用所引用,則該常量是廢棄常量。
判斷無用的類:
(1).該類的所有實例都已經被回收,即java堆中不存在該類的實例對象。
(2).載入該類的類載入器已經被回收。
(3).該類所對應的java.lang.Class對象沒有任何地方被引用,無法在任何地方通過反射機制訪問該類的方法。
Java中常用的垃圾收集演算法:
(1).標記-清除演算法:
最基礎的垃圾收集演算法,演算法分為「標記」和「清除」兩個階段:首先標記出所有需要回收的對象,在標記完成之後統一回收掉所有被標記的對象。
標記-清除演算法的缺點有兩個:首先,效率問題,標記和清除效率都不高。其次,標記清除之後會產生大量的不連續的內存碎片,空間碎片太多會導致當程序需要為較大對象分配內存時無法找到足夠的連續內存而不得不提前觸發另一次垃圾收集動作。
(2).復制演算法:
將可用內存按容量分成大小相等的兩塊,每次只使用其中一塊,當這塊內存使用完了,就將還存活的對象復制到另一塊內存上去,然後把使用過的內存空間一次清理掉。這樣使得每次都是對其中一塊內存進行回收,內存分配時不用考慮內存碎片等復雜情況,只需要移動堆頂指針,按順序分配內存即可,實現簡單,運行高效。
復制演算法的缺點顯而易見,可使用的內存降為原來一半。
(3).標記-整理演算法:
標記-整理演算法在標記-清除演算法基礎上做了改進,標記階段是相同的標記出所有需要回收的對象,在標記完成之後不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,在移動過程中清理掉可回收的對象,這個過程叫做整理。
標記-整理演算法相比標記-清除演算法的優點是內存被整理以後不會產生大量不連續內存碎片問題。
復制演算法在對象存活率高的情況下就要執行較多的復制操作,效率將會變低,而在對象存活率高的情況下使用標記-整理演算法效率會大大提高。
(4).分代收集演算法:
根據內存中對象的存活周期不同,將內存劃分為幾塊,java的虛擬機中一般把內存劃分為新生代和年老代,當新創建對象時一般在新生代中分配內存空間,當新生代垃圾收集器回收幾次之後仍然存活的對象會被移動到年老代內存中,當大對象在新生代中無法找到足夠的連續內存時也直接在年老代中創建。

Ⅵ 各種編程語言的實現都採用了哪些垃圾回收演算法

從各種垃圾收集演算法最基本的運行方式來說,大概可以分成三個類型:

1. 引用計數(reference counting):

基本思路是為每個對象加一個計數器,記錄指向這個對象的引用數量。每次有一個新的引用指向這個對象,計數器加一;反之每次有一個指向這個對象引用被置空或者指向其他對象,計數器減一。當計數器變為 0 的時候,自動刪除這個對象。

引用計數的優點是 1)相對簡單,不需要太多運行時(run-time)的支持,可以在原生不支持 GC 的語言里實現。2)對象會在成為垃圾的瞬間被釋放,不會給正常程序的執行帶來額外中斷。它的死穴是循環引用,對象 A 包含一個引用指向對象 B ,同時對象 B 包含一個引用指向對象 A,計數器就抓瞎了。另外,引用計數對正常程序的執行性能有影響(每次引用賦值都要改計數器),特別是在多線程環境下(改計數器要加鎖同步)。

現在仍然主要採用引用計數的例子有 Apple 的 ARC,C++ 新標准里的 std::shared_ptr。

2. 標記-清掃(mark-sweep)。

基本思路是先按需分配,等到沒有空閑內存的時候從寄存器和程序棧上的引用出發,遍歷以對象為節點、以引用為邊構成的圖,把所有可以訪問到的對象打上標記,然後清掃一遍內存空間,把所有沒標記的對象釋放。

標記-清掃沒有無法處理循環引用的問題,不觸發 GC 時也不影響正常程序的執行性能。但它的問題是當內存耗盡觸發 GC 時,需要中斷正常程序一段時間來清掃內存,在內存大對象多的時候這個中斷可能很長。

採用或者部分採用標記-清掃的例子非常多,不一一列舉了。

3. 節點復制(ing)。

基本思路是把整個內存空間一分為二,不妨記為 A 和 B。所有對象的內存在 A 中分配,當 A 塞滿的時候,同樣從寄存器和程序棧上的引用出發,遍歷以對象為節點、以引用為邊構成的圖,把所有可以訪問到的對象復制到 B 去,然後對調 A 和 B 的角色。

相對於標記-清掃,節點復制的主要缺點是總有一半空間空閑著無法利用,另一個比較隱晦的缺點是它使用內存的方式與現有的內存換頁、Cache 換入換出機制有潛在的沖突。但它有個很大的優點: 所有的對象在內存中永遠都是緊密排列的,所以分配內存的任務變得極為簡單,只要移動一個指針即可。對於內存分配頻繁的環境來說,性能優勢相當大。另外,由於不需要清掃整個內存空間,所以如果內存中存活對象很少而垃圾對象很多的話(有些語言有這個傾向),觸發 GC 造成的中斷會小於標記-清掃。

同樣的,採用或者部分採用節點復制的例子也非常多,不一一列舉了。

閱讀全文

與垃圾回收機制演算法相關的資料

熱點內容
命令方塊怎麼調鍵盤 瀏覽:841
不把密碼存在伺服器上怎麼辦 瀏覽:398
怎麼讓指令方塊的命令消失 瀏覽:543
用單片機做plc 瀏覽:404
雲伺服器進入子目錄命令 瀏覽:795
伺服器機櫃如何配電 瀏覽:578
怎麼刪除iphone資源庫里的app 瀏覽:940
pdf魚 瀏覽:648
單片機pcf8591什麼作用 瀏覽:805
sql命令學院 瀏覽:283
加密軟體在電腦那個盤 瀏覽:988
android獲取外部存儲 瀏覽:573
怎麼查自己家的伺服器地址 瀏覽:858
編程c語言工作好不好 瀏覽:569
單片機焊接地怎麼連接 瀏覽:694
游戲源碼怎麼抓 瀏覽:216
程序員幫大家引走怪物 瀏覽:16
手機網頁小游戲源碼 瀏覽:513
戰地一伺服器怎麼設置管理員 瀏覽:396
數控車床編程可以上班嗎 瀏覽:460