溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Java中怎么實現(xiàn)一個垃圾收集器

發(fā)布時間:2021-06-18 16:43:04 來源:億速云 閱讀:132 作者:Leah 欄目:大數(shù)據(jù)

這期內(nèi)容當中小編將會給大家?guī)碛嘘PJava中怎么實現(xiàn)一個垃圾收集器,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

 1、對象存活判定算法 :

1)引用記數(shù)算法:給每個對象添加一個引用計數(shù)器,每當有一個地方引用,計數(shù)器就加1,當引用失效時就減1,任何時刻計數(shù)器為0的對象就是不可能再被引用的。但是主流的Java虛擬機里面沒有選用引用計數(shù)器算法來管理內(nèi)存,其中最主要的原因是它很難解決對象之間相互循環(huán)引用的問題。

2)可達性分析算法:通過一系列稱為“GC Roots”的對象作為起始點,從這些節(jié)點向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GCRoots沒有任何引用鏈時,則證明此對象是不可用的。主流實現(xiàn)。

在Java語言中,可作為GCRoots的對象包括下面幾種:a.虛擬機棧中引用的對象;b.方法區(qū)中類靜態(tài)屬性引用的對象;c.方法區(qū)中常量引用的對象;d.本地方法棧中JNI引用的對象。

2、生存還是死亡

真正宣告一個對象死亡,至少要經(jīng)歷兩次標記過程:

1)可達性分析后發(fā)現(xiàn)不可達,進行第一次標記,并將沒有必要執(zhí)行finalize方法的對象放到一個F-Queue隊列;

2)GC對F-Queue隊列中的對象進行第二次小規(guī)模標記,如果還是不可達,基本就真的被回收了。

3、關于finalize方法

1)當對象沒有覆蓋finalize方法,或者finalize方法已經(jīng)被虛擬機調(diào)用過,虛擬機將這兩種情況都視為“沒有必要執(zhí)行”,所謂“執(zhí)行”是指虛擬機會觸發(fā)發(fā)這個方法,但并不承諾會等待它運行結束;

2)finalize方法只會被系統(tǒng)調(diào)用一次;

3)finalize方法能做的所有工作,try-finally或者其它方式都可以做的更好、更及時,所以建議完全可以忘掉Java語言中這個方法的存在。

4、垃圾收集算法:標記-清理、復制、標記-整理、分代收集

1)標記-清理算法

a.分兩個階段:標記和清理,首先標記出需要回收的對象,在標記完成后統(tǒng)一回收所有被標記的對象;

b.不足:一個是效率問題,標記和清理兩個過程的效率不高;另一個是空間問題,標記清理后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導致以后在程序運行過程中需要分配較大對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)一次垃圾收集動作。

2)復制算法

a.將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當一塊的內(nèi)存用完了,就將還存活的對象復制到另一塊上面,然后再把已使用的內(nèi)存空間一次清理掉。效率提高,也沒有內(nèi)存碎片的問題。代價是內(nèi)存縮小為原來的一半。

b.新生代的對象98%是“朝生夕死”的,所以并不需要按照1:1的比例來劃分內(nèi)存空間,而是將內(nèi)存分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden和其中一塊Survivor。當回收時,將Eden和Survivor中還存活的對象一次性復制到另外一塊Survivor空間上,最后清理掉Eden和剛才用過的Survivor空間。HotSpot虛擬機默認Eden和Survivor的大小比例是8:1,也就是每次新生代中可用內(nèi)存空間為整個新生代容量的90%,只有10%的內(nèi)存會被“浪費”。如果另外一塊Survivor空間沒有足夠的空間存放上一次新生代收集下來的存活對象時,這些對象將直接通過分配擔保機制進入老年代。

3)標記-整理算法

a.分兩個階段:標記和整理,首先標記出需要回收的對象,然后讓存活的對象都往一端移動,然后直接清理掉邊界以外的內(nèi)存。

4)分代收集算法

a.一般把Java堆分成新生代和老年代,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴āT谛律?,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或者“標記-整理”算法來進行回收。

5、HotSpot的算法實現(xiàn)

1)枚舉根結點:由于目前的主流Java虛擬機使用的都是準確式GC,所以當執(zhí)行系統(tǒng)停頓下來后,并不需要一個不漏的檢查完所有執(zhí)行上下文和全局的引用位置,虛擬機應當有辦法直接得知哪些地方存放著對象引用。在HotSpot的實現(xiàn)中,使用一組成為OopMap的數(shù)據(jù)結構來記錄對象引用。

2)安全點:程序在執(zhí)行時并非所有的地方都能停頓下來開始GC,只有在到達安全點時才能暫停。安全點的選定既不能太少以致于讓GC等待時間太常,也不能太頻繁以致于過分增大運行時負荷。所以安全點的選定基本上是以程序“是否具有讓程序長時間執(zhí)行的特征”為標準進行選定的。“長時間執(zhí)行”最明顯的特征就是指令序列復用,例如方法調(diào)用、循環(huán)跳轉、異常跳轉等,所以具有這些功能的指令才會產(chǎn)生安全點。另外一個需要考慮的問題時如何在GC發(fā)生時讓所有線程“跑”到最近的安全點上再停頓下來。這里有兩個方案:搶占式中斷和主動式中斷。現(xiàn)在幾乎沒有虛擬機實現(xiàn)采用搶占式中斷來暫停線程從而相應GC事件。而主動式中斷的思想時當GC需要中斷線程的時候,不直接對線程操作,僅僅簡單地設置一個標志,各個線程執(zhí)行時主動去輪詢這個標志,發(fā)現(xiàn)中斷標志為真時就中斷掛起,輪詢標志的地方和安全點是重合的,另外再加上創(chuàng)建對象需要分配內(nèi)存的地方。

3、安全區(qū)域:指在一段代碼片段之中,引用關系不會發(fā)生變化。在這個區(qū)域中的任意地方開始GC都是安全的。在線程執(zhí)行到安全區(qū)域中的代碼時,首先標識自己已經(jīng)進入安全區(qū)域,那樣,當在這段時間里JVM要發(fā)起GC時,就不用管標識自己為安全區(qū)域狀態(tài)的線程了。在線程要離開安全區(qū)域時,它要檢查系統(tǒng)是否已經(jīng)完成根結點枚舉,如果完成了,那線程就繼續(xù)執(zhí)行,否則它就必須等待直到收到可以安全離開安全區(qū)域的信號為止。 

6、垃圾收集器:a.新生代收集器:Serial、Parnew、Parallel Scavenge,b.老年代收集器:Parallel Old、CMS、Serial Old,c.G1。

1)Serial收集器:單線程收集器,在它進行垃圾回收時,必須暫停其他所有的工作線程,直到它收集結束;

2)ParNew收集器:Serial收集器的多線程版本,除Serial收集器外,目前只有它能與CMS收集器配合工作;

3)Parallel Scavenge收集器:目標時達到一個可控制的吞吐量,也稱“吞吐量優(yōu)先”收集器;

4)Serial Old收集器:Serial收集器的老年代版本,同樣是一個單線程收集器,使用“標記-整理”算法;

5)Parallel Old收集器:Parallel Scavenge收集器的老年代版本,使用多線程和“標記-整理”算法,在注重吞吐量以及CPU資源敏感的場合,都可以優(yōu)先考慮Parallel Scavenge + Parallel Old收集器。

6)CMS收集器:以獲取最短回收停頓時間為目標的收集器,基于“標記-清理”算法實現(xiàn)的。優(yōu)點是:并發(fā)收集、低停頓。

7)G1收集器:它將整個Java堆劃分為多個大小相等的獨立區(qū)域,能夠建立可預測的停頓時間模型。G1跟蹤各個Region里面的垃圾堆積的價值大小,在后臺維護一個優(yōu)先列表,每次根據(jù)允許的收集時間,優(yōu)先回收價值最大的Region。

7、GC日志:每種收集器的日至不一樣,但都有共性,以下是典型的GC日志

1)33.125: [GC [DefNew: 3324K->152K(3712K), 0.0025925 secs] 3324K->152K(11904K), 0.0031680 secs]

2)100.667: [Full GC [Tenured: 0K->210K(10240K), 0.0149142 secs] 4603K->210K(19456K), [Perm: 2999K->2999K(21248K)], 0.015007 secs] [Times: user=0.01 sys=0.00, real=0.02 secs]

a.最開始的數(shù)字代表GC發(fā)生的時間,這個數(shù)字的含義是從Java虛擬機啟動以來經(jīng)過的秒數(shù);

b.[GC 和[Full GC說明了這次垃圾收集的停頓類型,而不是來區(qū)分新生代GC和老年代GC的。Full,說明這次GC發(fā)生了一次Stop-The-World。

c.[DefNew 、[Tenured、[Perm表示GC發(fā)生的區(qū)域,不同的垃圾收集器顯示的名稱不同

d.后面方括號內(nèi)部3324->152K(3712)表示“GC前該內(nèi)存區(qū)域已使用容量->GC后該內(nèi)存區(qū)域已使用容量(該內(nèi)存區(qū)域總容量)”

e.方括號之外的3324->152K(11904K)表示“GC前Java堆已使用容量->GC后Java堆已使用容量(Java堆總容量)”

f.再往后,0.0025925 secs表示該內(nèi)存區(qū)域GC所占用的時間,單位是秒,有的收集器會給出更具體的時間,user、sys、real分別表示用戶態(tài)消耗的CPU時間、內(nèi)核態(tài)消耗的CPU時間和操作從開始到結束所經(jīng)過的墻鐘時間(Wall Clock Time)。CPU時間與墻鐘時間的區(qū)別是,墻鐘時間包括各種非運算的等待耗時。

8、內(nèi)存分配規(guī)則:以下是幾條最普遍的內(nèi)存分配規(guī)則

1)對象優(yōu)先在Eden分配;

2)大對象直接進入老年代:所謂大對象是指需要大量連續(xù)內(nèi)存空間Java對象,最典型的大對象就是那種很長的字符串以及數(shù)組,避免短命大對象;

3)長期存活的對象直接進入老年代:虛擬機給每個對象定義了一個對象年齡計數(shù)器,每次MinorGC后仍然存活,并且能被Survivor容納的話,對象年齡加1,當它的年齡到達一定程度(默認15歲),就會晉升到老年代。

4)動態(tài)對象年齡判定:虛擬機不是必須要求對象的年齡必須達到MaxTenuringThreshold才能晉升老年代,如果Survivor空間中相同年齡所有對象大小總和大于Survivor空間的一半,年齡大于或等于該年齡的對象就可以直接進入老年代。

9、空間分配擔保

在發(fā)生MinorGC之前,虛擬機會先檢查老年代最大可用的連續(xù)空間是否大于新生代所有對象總空間,如果這個條件成立,那么MinorGC可以確保是安全的,如果不成立,則虛擬機會查看HandlePromotionFailure設置值是否允許擔保失敗,如果允許,那么會繼續(xù)檢查老年代最大可用的連續(xù)空間是否大于歷次晉升到老年代對象的平均大小,如果大于,將嘗試進行一次MinorGC,盡管這次MinorGC是有風險的,如果小于或者HandlePromotionFailure設置不允許冒險,那這時也要改為進行一次FullGC。

上述就是小編為大家分享的Java中怎么實現(xiàn)一個垃圾收集器了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關知識,歡迎關注億速云行業(yè)資訊頻道。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。

AI