愛鋒貝

 找回密碼
 立即注冊(cè)

只需一步,快速開始

扫一扫,极速登录

查看: 727|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

【面試必問系列】JVM垃圾回收算法、收集器看這一篇就夠了

[復(fù)制鏈接]

1496

主題

1567

帖子

6364

積分

Rank: 8Rank: 8

跳轉(zhuǎn)到指定樓層
樓主
發(fā)表于 2023-4-4 06:33:56 | 只看該作者 回帖獎(jiǎng)勵(lì) |倒序?yàn)g覽 |閱讀模式

一鍵注冊(cè),加入手機(jī)圈

您需要 登錄 才可以下載或查看,沒有帳號(hào)?立即注冊(cè)   

x
一、如何確定是垃圾

1、引用計(jì)數(shù)法

對(duì)象如果沒有與之關(guān)聯(lián)的引用,計(jì)數(shù)器為0的對(duì)象,就是可回收的對(duì)象。(目前python就使用)
優(yōu)點(diǎn):判定效率高,實(shí)現(xiàn)簡(jiǎn)單。
缺點(diǎn):不完全準(zhǔn)確,無(wú)法回收循環(huán)引用的對(duì)象,容易內(nèi)存泄漏。
2、可達(dá)性分析(根可達(dá))

通過一系列GC Roots的對(duì)象作為起始點(diǎn),從這些根節(jié)點(diǎn)開始向下搜,搜索所有走過的路叫做引用連,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何的引用鏈相連時(shí),則說(shuō)明此對(duì)象不可用。
優(yōu)點(diǎn):解決相互循環(huán)引用問題。
注:不可達(dá)對(duì)象不等價(jià)于可回收對(duì)象,不可達(dá)對(duì)象變?yōu)榭苫厥諏?duì)象至少要經(jīng)過兩次標(biāo)記過程,可以通過finalize()自救。
3、GC Roots對(duì)象

GC Roots對(duì)象包括:
虛擬機(jī)棧棧幀中本地變量表引用的對(duì)象;
方法區(qū)中類靜態(tài)屬性引用;
方法區(qū)中常量引用的對(duì)象;
本地方法棧中JNI引用的對(duì)象;
所有被同步鎖持有的對(duì)象等;
jvm中跨代引用的對(duì)象等;
.......還有其他的幾種,常用的前四種。
二、垃圾回收算法

java是自動(dòng)回收內(nèi)存的,C、c++等都要手工命令回收。
1、復(fù)制算法

按內(nèi)存容量將內(nèi)存劃分為大小相等的兩塊,每次使用一塊,這一塊滿后,將尚存活的復(fù)制到另一塊上去,把它使用的內(nèi)存清空掉。
優(yōu)點(diǎn):實(shí)現(xiàn)簡(jiǎn)單,不易產(chǎn)生碎片;
缺點(diǎn):可用內(nèi)存被壓縮為原來(lái)的一半,且存活對(duì)象多的話,此算法效率大大降低。
2、標(biāo)記清除算法(Mark-Sweep)

標(biāo)記要回收的對(duì)象,回收被標(biāo)記的對(duì)象占用的空間。
缺點(diǎn):內(nèi)存碎片化嚴(yán)重;
3、標(biāo)記整理算法(Mark-Compact)

標(biāo)記要回收的對(duì)象,標(biāo)記后不是清楚標(biāo)記對(duì)象,而是將存活的對(duì)象移向內(nèi)存的一端,然后清楚邊界外的對(duì)象。
優(yōu)點(diǎn):沒有碎片化;
4、分代收集算法

目前大部分JVM采用的【新生代、老年代、永久代】
根據(jù)對(duì)象存活的不同生命周期,將內(nèi)存劃分為不同的域,一般情況下,垃圾回收主要回收堆空間(因?yàn)閹缀醮蟛糠謱?duì)象都在堆空間,特例逃逸分析:棧上分配),所以將堆劃分為新生代(1/3)、老年代(2/3)。
老年代:大對(duì)象直接放在老年代;長(zhǎng)期存活的對(duì)象進(jìn)入老年代;
每次只有少量對(duì)象需要被回收,存活率高,比較穩(wěn)定。所以老年代選擇標(biāo)記整理算法或者標(biāo)記清除算法。老年代的垃圾回收叫Major GC;
新生代:存放新生對(duì)象,對(duì)象朝生夕死,大量對(duì)象被回收,少量存活所以復(fù)制成本低,所以新生代選擇復(fù)制算法。新生代的垃圾回收叫Minor GC(復(fù)制-清空-互換)。
永久代:方法區(qū)的永生代,用來(lái)存儲(chǔ)class類、常量、方法描述等,對(duì)永生代的回收主要是廢棄的常量和無(wú)用的類。垃圾較少,收益一般較小,所以垃圾回收主要回收堆空間。1.8之后叫做元空間。
Java內(nèi)存模型



  • 其中新生代又劃分為一個(gè)eden區(qū),兩個(gè)survivor區(qū),默認(rèn)比例為8:1:1。
  • 新生對(duì)象分配在eden區(qū)。如果eden區(qū)的垃圾經(jīng)過一次GC幸存,就復(fù)制到survivor區(qū),從eden-->survivor 對(duì)象年齡+1,survivor-->eden 對(duì)象年齡+1,如果最后存活對(duì)象的年齡達(dá)到15將被移至老年代。
  • 動(dòng)態(tài)年齡:按照年齡從小到大對(duì)其所占用的大小進(jìn)行累積,當(dāng)累積的某個(gè)年齡大小超過了survivor區(qū)的一半時(shí),取這個(gè)年齡和MaxTenuringThreshold中更小的一個(gè)值,作為新的晉升年齡閾值。eg:survivor區(qū)已經(jīng)有一半年齡為4的對(duì)象了,占用了一半survivor區(qū)內(nèi)存了,也將這些對(duì)象移至老年代。
注:Major GC前一般會(huì)先進(jìn)行Minor GC,Minor GC頻繁被觸發(fā),Major GC不會(huì),無(wú)法找到足夠大的連續(xù)空間分配給新創(chuàng)建的較大對(duì)象時(shí),也會(huì)提前觸發(fā)Major GC;Major GC速度一般比Minor GC慢10倍以上;
Full GC :是清理整個(gè)堆空間。

觸發(fā)條件
①手動(dòng)條用System.gc()
②老年代空間不足;
③方法區(qū)空間不足;
④經(jīng)過Minor GC后,進(jìn)行移動(dòng)或分配的對(duì)象大小大于老年代可用空間。
小結(jié):分代收集算法就是根據(jù)不同的區(qū)域?qū)ο笊芷谔攸c(diǎn)選擇不同的回收算法;
垃圾回收主要回收堆內(nèi)存;
survivor區(qū)晉升年齡閾值有兩種情況,年齡15或者達(dá)到survivor區(qū)的50%。
三、GC 性能衡量指標(biāo)

1、吞吐量:

這里的衡量吞吐量是指應(yīng)用程序所花費(fèi)的時(shí)間和系統(tǒng)總運(yùn)行時(shí)間的比值。我們可以按照這個(gè)公式來(lái)計(jì)算 GC 的吞吐量:系統(tǒng)總運(yùn)行時(shí)間 = 應(yīng)用程序耗時(shí)+GC 耗時(shí)。如果系統(tǒng)運(yùn)行了 100 分鐘,GC 耗時(shí) 1 分鐘,則系統(tǒng)吞吐量為 99%。GC 的吞吐量一般不能低于 95%。
2、停頓時(shí)間:

指垃圾回收器正在運(yùn)行時(shí),應(yīng)用程序的暫停時(shí)間。對(duì)于串行回收器而言,停頓時(shí)間可能會(huì)比較長(zhǎng);而使用并發(fā)回收器,由于垃圾收集器和應(yīng)用程序交替
運(yùn)行,程序的停頓時(shí)間就會(huì)變短,但其效率很可能不如獨(dú)占垃圾收集器,系統(tǒng)的吞吐量也很可能會(huì)降低。
3、垃圾回收頻率:

通常垃圾回收的頻率越低越好,增大堆內(nèi)存空間可以有效降低垃圾回收發(fā)生的頻率,但同時(shí)也意味著堆積的回收對(duì)象越多,最終也會(huì)增加回收時(shí)的停頓時(shí)間。所以我們需要適當(dāng)?shù)卦龃蠖褍?nèi)存空間,保證正常的垃圾回收頻率即可。
四、垃圾收集器

目前主流的7個(gè):
新生代收集器:Serial、ParNew、Parallel Scavenge;
老年代收集器:Serial Old、Parallel Old、CMS;
整堆收集器:G1;
下面一個(gè)個(gè)介紹下這些收集器特點(diǎn)及作用:
1、Serial(單線程,復(fù)制算法,新生代)

Serial在收集垃圾時(shí),必須暫停其他所有工作線程,直至垃圾回收結(jié)束,對(duì)于單個(gè)cpu來(lái)說(shuō),沒有線程交互的開銷,效率高。
因此Serial是java虛擬機(jī)運(yùn)行在Client模式下默認(rèn)的新生代垃圾收集器。
2、ParNew(Serial的并行的多線程版,復(fù)制算法,新生代)

ParNew除了使用多線程進(jìn)行垃圾回收以外,其他行為和Serial一樣,也要暫停所有工作線程。
ParNew默認(rèn)開啟和cpu數(shù)目相同的線程,可以通過參數(shù)-XX:ParallelGCThreads限制線程數(shù)量,是很多虛擬機(jī)在Server模式下新生代默認(rèn)的垃圾收集器。
3、Parallel Scavenge(并行的多線程版,復(fù)制算法,新生代)

重點(diǎn)關(guān)注程序可達(dá)到的一個(gè)可控制的吞吐量,有自適應(yīng)調(diào)節(jié)策略,提升用戶的
體驗(yàn)。是1.8默認(rèn)的新生代收集器。
吞吐量=cpu運(yùn)行用戶代碼時(shí)間/cpu總消耗時(shí)間
自適應(yīng)調(diào)節(jié)策略:
Parallel Scavenge收集器能夠配合自適應(yīng)調(diào)節(jié)策略,把內(nèi)存管理的調(diào)優(yōu)任務(wù)交給虛擬機(jī)去完成。只需要把基本的內(nèi)存數(shù)據(jù)設(shè)置好(如-Xmx設(shè)置最大堆),然后使用MaxGCPauseMillis參數(shù)(更關(guān)注最大停頓時(shí)間)或GCTimeRatio參數(shù)(更關(guān)注吞吐量)給虛擬機(jī)設(shè)立一個(gè)優(yōu)化目標(biāo),那具體細(xì)節(jié)參數(shù)的調(diào)節(jié)工作就由虛擬機(jī)完成了。
1)java -XX:+PrintFlagsFinal 可以看到1.8默認(rèn)的是 UseParallelGC
ParallelGC 默認(rèn)的是 Parallel Scavenge(新生代)+ Parallel Old(老年代)
2)自適應(yīng)調(diào)節(jié)策略也是Parallel Scavenge收集器與ParNew收集器的一個(gè)重要區(qū)別。
4、Serial Old(單線程,標(biāo)記整理算法,老年代)

主要運(yùn)行在Client模式下,java虛擬機(jī)默認(rèn)的老年代垃圾收集器,
在Server模式下兩種用途:
①jdk1.5之前與新生代的Parallel Scavenge搭配使用;
②作為老年代中CMS收集器的后備垃圾收集方案。
5、Parallel Old(并行的多線程,標(biāo)記整理算法,老年代)

若同樣考慮老年代的吞吐量,可以考慮搭配新生代的Parallel Scavenge使用。
6、CMS(Concurent Mark Sweep)(并發(fā)的多線程,標(biāo)記清除算法、老年代)

主要目的是獲取最短垃圾回收停頓時(shí)間,可以為交互較高的程序提高用戶體驗(yàn)。是目前老年代中唯一一個(gè)標(biāo)記清除算法而不是標(biāo)記整理算法的垃圾收集器。
缺點(diǎn):cpu敏感、浮動(dòng)垃圾、內(nèi)存碎片
CMS分為四個(gè)階段:
1)初始標(biāo)記(暫停):只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)的對(duì)象,速度快,暫停所有工作線程;
2)并發(fā)標(biāo)記(并發(fā)):進(jìn)行GC Roots跟蹤,和用戶線程一起工作;
1)重新標(biāo)記(暫停):修正并發(fā)標(biāo)記期間,因程序運(yùn)行導(dǎo)致的標(biāo)記變動(dòng)那部分對(duì)象的標(biāo)記,暫停所有工作線程;
1)并發(fā)清除(并發(fā)):清除GC Roots不可達(dá)對(duì)象,和用戶線程一起工作。
所以總體上看,CMS收集器的內(nèi)存回收和用戶線程是一起并發(fā)執(zhí)行的。eg:web程序,B/S服務(wù)。
7、G1(Garbage first)

G1最突出改進(jìn):
1)基于標(biāo)記整理算法,沒有碎片;
2)可以非常精準(zhǔn)的控制停頓時(shí)間,在不犧牲吞吐量的前提下,實(shí)現(xiàn)低停頓的回收,讓使用者明確指定在一個(gè)長(zhǎng)度為M毫秒的時(shí)間片段內(nèi),消耗在垃圾回收上的時(shí)間不得超過N毫秒。
G1是避免全區(qū)域的垃圾收集,而是將java堆劃分為多個(gè)大小固定的獨(dú)立區(qū)域,并且跟蹤這些區(qū)域里面的垃圾堆積程度,在后臺(tái)維護(hù)一個(gè)優(yōu)先列表,每次根據(jù)允許的收集時(shí)間,優(yōu)先回收垃圾最多的區(qū)域。
8、垃圾收集器的搭配

上述7種垃圾收集器的搭配使用,有連接的可以搭配。


注意幾個(gè)概念:?jiǎn)尉€程、并行、并發(fā)是不一樣的。
并行(Parallel):指多條垃圾收集線程并行工作,但此時(shí)用戶線程依舊處于等待狀態(tài); 如ParNew、Parallel Scavenge、Parallel Old;
并發(fā)(Concurrent):指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的,可能會(huì)交替執(zhí)行),如CMS、G1;
CMS、G1的并發(fā)標(biāo)記 :采用了三色標(biāo)記法:分別是白色、灰色和黑色。三色標(biāo)記最大的好處是可以異步執(zhí)行,從而可以以中斷時(shí)間極少的代價(jià)或者完全沒有中斷來(lái)進(jìn)行整個(gè) GC。
并發(fā)標(biāo)記容易產(chǎn)品漏標(biāo)問題,CMS從根從新掃描,G1快照方式對(duì)比差異,具體此處不多延伸。
五、安全點(diǎn)與安全區(qū)域

1、安全點(diǎn)

不是在任何時(shí)候都可以隨便GC的,當(dāng)系統(tǒng)要進(jìn)行垃圾回收時(shí),業(yè)務(wù)線程不是立馬停下來(lái)的,可想而知立馬停下可能會(huì)有問題,為了準(zhǔn)確安全地回收內(nèi)存,JVM是在Safe Point點(diǎn)時(shí)才進(jìn)行回收,
就是業(yè)務(wù)線程去按照某種策略輪詢檢查這個(gè)變量一旦發(fā)現(xiàn)是安全點(diǎn)(Safe Point)就主動(dòng)掛起,那樣當(dāng)JVM達(dá)到Safe Point就可以安全準(zhǔn)確的GC了。
安全點(diǎn)主要在以下位置設(shè)置:
1) 循環(huán)的末尾
2)方法返回前
3)調(diào)用方法的call之后
4) 拋出異常的位置
2、安全區(qū)域

若用戶線程sleep了等,不能主動(dòng)檢測(cè)變量走向安全點(diǎn),顯然JVM也不可能等待程序喚醒,這時(shí)候就需要安全區(qū)域了。
安全區(qū)域是指一段代碼片中,引用關(guān)系不會(huì)發(fā)生變化,在這個(gè)區(qū)域任何地方GC都是安全的,安全區(qū)域可以看做是安全點(diǎn)的一個(gè)擴(kuò)展。線程執(zhí)行到安全區(qū)域的代碼時(shí),首先標(biāo)識(shí)自己進(jìn)入了安全區(qū)域,這樣GC時(shí)就不用管進(jìn)入安全區(qū)域的線層了,線程要離開安全區(qū)域時(shí)就檢查JVM是否完成了GC Roots枚舉,如果完成就繼續(xù)執(zhí)行,如果沒有完成就等待直到收到可以安全離開的信號(hào)。

-----------------------------
精選高品質(zhì)二手iPhone,上愛鋒貝APP
您需要登錄后才可以回帖 登錄 | 立即注冊(cè)   

本版積分規(guī)則

QQ|Archiver|手機(jī)版|小黑屋|愛鋒貝 ( 粵ICP備16041312號(hào)-5 )

GMT+8, 2025-2-8 17:20

Powered by Discuz! X3.4

© 2001-2013 Discuz Team. 技術(shù)支持 by 巔峰設(shè)計(jì).

快速回復(fù) 返回頂部 返回列表