【JVM】一文掌握JVM垃圾回收机制
作為Java程序員,除了業(yè)務(wù)邏輯以外,隨著更深入的了解,都無(wú)法避免的會(huì)接觸到JVM以及垃圾回收相關(guān)知識(shí)。JVM調(diào)優(yōu)是一個(gè)聽起來(lái)很可怕,實(shí)際上很簡(jiǎn)單的事。
感到可怕,是因?yàn)槔厥障嚓P(guān)機(jī)制都在JVM的C++層實(shí)現(xiàn),我們?cè)贘ava開發(fā)中看不見摸不著;而實(shí)際很簡(jiǎn)單,是因?yàn)樗f(shuō)到底,也只是JVM替我們實(shí)現(xiàn)的垃圾對(duì)象回收機(jī)制,也是普通的程序代碼,只要理解了垃圾回收器的底層設(shè)計(jì)思想,掌握J(rèn)VM調(diào)優(yōu)并非難事!
一、JVM內(nèi)存模型
元數(shù)據(jù)區(qū):JDK8之前是方法區(qū)。存放虛擬機(jī)加載的:類型信息,域(Field)信息,方法(Method)信息,常量,靜態(tài)變量,即時(shí)編譯器編譯后的代碼緩存
虛擬機(jī)棧:虛擬機(jī)棧中保存了每一次方法調(diào)用的棧幀信息,棧幀中包含以下信息:
- 局部變量表:保存函數(shù) (即方法) 的局部變量
- 操作數(shù)棧:保存計(jì)算過(guò)程中的結(jié)果,即臨時(shí)變量
- 動(dòng)態(tài)鏈接:指向方法區(qū)的運(yùn)行時(shí)常量池。字節(jié)碼中的方法調(diào)用指令以常量池中指向方法的符號(hào)引用為參數(shù)。
- 方法的返回地址
本地方法棧:和虛擬機(jī)棧功能上類似,它管理了native方法的一些執(zhí)行細(xì)節(jié),而虛擬機(jī)棧管理的是Java方法的執(zhí)行細(xì)節(jié)。
程序計(jì)數(shù)器:程序計(jì)數(shù)器記錄線程執(zhí)行的字節(jié)碼行號(hào),如果當(dāng)前線程正在運(yùn)行native方法則為空。每個(gè)線程都有自己的計(jì)數(shù)器
堆:JVM中產(chǎn)生的實(shí)例對(duì)象的存儲(chǔ)位置
所謂的垃圾回收,主要就是回收J(rèn)VM中堆內(nèi)存的區(qū)域
二、垃圾定義
- 引用計(jì)數(shù)(ReferenceCount):存在循環(huán)引用的問題,漏掉循環(huán)引用的垃圾
- 根可達(dá)算法(RootSearching):判斷對(duì)象是否可通過(guò)引用尋到JVM的根節(jié)點(diǎn),不能則是垃圾
三、垃圾回收算法
- 標(biāo)記清除(mark sweep) - 位置不連續(xù) 產(chǎn)生碎片 效率偏低(兩遍掃描)
- 拷貝算法 (copying) - 沒有碎片,浪費(fèi)空間
- 標(biāo)記壓縮(mark compact) - 沒有碎片,效率偏低(兩遍掃描,指針需要調(diào)整)
四、垃圾回收器
通過(guò)以上三種算法的排列組合,產(chǎn)生了各種各樣的垃圾回收器
堆內(nèi)存邏輯分區(qū)
常見垃圾回收器
? Serial:?jiǎn)尉€程STW垃圾回收器,工作在年輕代。采用拷貝算法
? Serial Old:?jiǎn)尉€程STW垃圾回收器,工作在老年代。采用標(biāo)記清除加壓縮算法
? Parallel Scavenge:并行垃圾回收,工作在年輕代。采用拷貝算法
? Parallel Old:并行垃圾回收,工作在老年代。采用標(biāo)記清除加壓縮算法
? ParNew:并行垃圾回收,工作在年輕代。專門配合CMS使用
? CMS(Concurrent Mark-Sweep):并發(fā)標(biāo)記清除,工作在老年代,采用標(biāo)記清除算法。
? G1(Garbage First):垃圾優(yōu)先算法,采用拷貝算法
? ZGC(Z Garbage Collector):一種可伸縮的低延遲垃圾回收器,旨在處理TB級(jí)別的堆,同時(shí)保持低毫秒級(jí)別的停頓時(shí)間。它通過(guò)使用讀屏障和染色指針來(lái)實(shí)現(xiàn)這一點(diǎn),并且在垃圾回收過(guò)程中幾乎不需要暫停應(yīng)用線程
? Shenandoah GC:是一種旨在實(shí)現(xiàn)低停頓時(shí)間的垃圾回收器,它通過(guò)并發(fā)的方式來(lái)回收內(nèi)存。Shenandoah的目標(biāo)是減少停頓時(shí)間,而不是優(yōu)化吞吐量,適用于需要大內(nèi)存和低延遲的應(yīng)用
垃圾升級(jí)過(guò)程
- 新創(chuàng)建對(duì)象產(chǎn)生在eden區(qū)
- ygc觸發(fā),把eden區(qū)和s0區(qū)不是垃圾的對(duì)象復(fù)制到s1區(qū),并對(duì)非垃圾對(duì)象的頭部的分代年齡加一,然后清除eden區(qū)和s0區(qū)
- ygc觸發(fā),把eden區(qū)和s1區(qū)不是垃圾的對(duì)象復(fù)制到s0區(qū),并對(duì)非垃圾對(duì)象的頭部的分代年齡加一,然后清除eden區(qū)和s1區(qū)
- 當(dāng)對(duì)象頭記錄的分代年齡達(dá)到15(默認(rèn)最大分代年齡)時(shí),jvm將把他從年輕代升級(jí)到老年代
eden區(qū)和s0、s1的默認(rèn)比例是8:1:1,可通過(guò)參數(shù)-XX:SurvivorRatio配置
對(duì)象頭的年齡可通過(guò)-XX:MaxTenuringThreshold參數(shù)配置,但由于對(duì)象頭中只用4個(gè)比特位存儲(chǔ)分代年齡,因此它的區(qū)間是0-15
CMS垃圾回收器
CMS是用于回收老年代的垃圾回收器,它采用的是標(biāo)記清除算法。CMS的誕生的目的在于提供在多核環(huán)境下的并發(fā)處理中大型堆(MB~GB)垃圾的能力
特點(diǎn)
- 并發(fā)收集:CMS的主要特點(diǎn)是它允許垃圾回收線程與應(yīng)用程序線程同時(shí)運(yùn)行。這減少了應(yīng)用程序的停頓時(shí)間,特別是在長(zhǎng)時(shí)間運(yùn)行的老年代垃圾回收過(guò)程中。
- 低停頓時(shí)間:由于并發(fā)執(zhí)行,CMS旨在減少垃圾回收引起的停頓時(shí)間,這對(duì)于延遲敏感的應(yīng)用程序非常重要。
- 存在內(nèi)存碎片:CMS通常不執(zhí)行堆壓縮,這意味著它不會(huì)重新安排存活對(duì)象來(lái)消除空閑空間之間的碎片。這可以減少停頓時(shí)間,但可能導(dǎo)致更多的內(nèi)存碎片。
- CPU資源密集型:CMS需要更多的CPU資源來(lái)執(zhí)行并發(fā)的垃圾回收。在多核處理器上,這通常不是問題,但在CPU資源受限的環(huán)境中,可能會(huì)影響應(yīng)用程序的性能。
- 并發(fā)模式失敗:在極端情況下,如果老年代在CMS回收過(guò)程中被填滿,會(huì)發(fā)生“并發(fā)模式失敗”。這時(shí),JVM會(huì)退回到傳統(tǒng)的完全停頓式垃圾回收,以清理老年代。
- 適用于中到大型堆:CMS適合于中到大型的堆,尤其是在有足夠CPU資源和對(duì)停頓時(shí)間敏感的應(yīng)用場(chǎng)景中。
- 需要調(diào)優(yōu):為了獲得最佳性能,CMS可能需要通過(guò)JVM參數(shù)進(jìn)行調(diào)優(yōu),如設(shè)置堆的大小、老年代的大小、并發(fā)線程數(shù)等。
垃圾回收過(guò)程
- 初始標(biāo)記:找到根上的垃圾,會(huì)有非常短暫的STW
- 并發(fā)標(biāo)記:標(biāo)記垃圾,這一步可能產(chǎn)生漏標(biāo)(掃描完不是垃圾之后,突然失去引用變成了垃圾),也可能產(chǎn)生多標(biāo)(掃描完事垃圾之后,突然重新被引用變成不是垃圾)
- 重新標(biāo)記:重新標(biāo)記的目的是糾正上一步所產(chǎn)生的錯(cuò)誤標(biāo)記,會(huì)有時(shí)間不算很長(zhǎng)的STW
- 并發(fā)清理:清理前面步驟所標(biāo)記出來(lái)的垃圾
三色標(biāo)記算法
作用于并發(fā)標(biāo)記階段
對(duì)象標(biāo)記為黑白灰三個(gè)顏色,記錄當(dāng)前掃描標(biāo)記的位置。
- 黑色:自己已經(jīng)被標(biāo)記,自己的子引用也都標(biāo)記完成
- 白色:沒有遍歷到的節(jié)點(diǎn)
- 灰色:自己已經(jīng)被標(biāo)記,自己的子引用還沒被全部標(biāo)記完成
三色標(biāo)記的bug
由于并發(fā)標(biāo)記是與用戶線程并行的,所以在并發(fā)標(biāo)記的過(guò)程中對(duì)象的引用是可能發(fā)生變化的,所以可能會(huì)產(chǎn)生多標(biāo)和漏標(biāo)。并且重新標(biāo)記為了減少STW的時(shí)間不會(huì)再標(biāo)記黑色對(duì)象,而是掃描灰色對(duì)象的直接引用
- 多標(biāo):會(huì)導(dǎo)致產(chǎn)生浮動(dòng)垃圾,需要在下一次判斷引用再回收,無(wú)大礙
- 漏標(biāo):會(huì)導(dǎo)致不應(yīng)該被回收的對(duì)象被回收,問題嚴(yán)重
如上圖:在并發(fā)標(biāo)記的過(guò)程中,同時(shí)產(chǎn)生這兩種情況時(shí)就會(huì)發(fā)生回收錯(cuò)誤問題:A和C斷開了引用,A又引用了D。
- 對(duì)于對(duì)象C:應(yīng)該回收的對(duì)象現(xiàn)在是黑色,留了下來(lái)
- 對(duì)于對(duì)象D:被引用了但還是白色,由于重新標(biāo)記時(shí)不會(huì)再掃描黑色對(duì)象,這樣會(huì)導(dǎo)致對(duì)象D被當(dāng)作垃圾而回收,產(chǎn)生嚴(yán)重bug
CMS對(duì)于三色標(biāo)記的錯(cuò)標(biāo)處理
CMS的處理方式是Increment Updater(增量更新),即當(dāng)已經(jīng)被掃描完的黑色對(duì)象如果產(chǎn)生了新的引用,則把自己標(biāo)記為灰色,等待下次掃描重新標(biāo)記。
但在上述的多標(biāo)案例中,CMS存在卻依然并發(fā)標(biāo)記Bug,如下時(shí)序圖
當(dāng)兩個(gè)垃圾回收線程m1和m3加上一個(gè)業(yè)務(wù)線程m2同時(shí)標(biāo)記一個(gè)對(duì)象時(shí),m3認(rèn)為應(yīng)該標(biāo)灰,但m1認(rèn)為應(yīng)該標(biāo)黑,如果最終m1的標(biāo)記覆蓋了m3的標(biāo)記,那么對(duì)象的顏色標(biāo)記錯(cuò)誤,它下面新增的引用也不會(huì)被掃描到
CMS對(duì)于這個(gè)嚴(yán)重的bug的解決方案是,在重新標(biāo)記階段重新掃描時(shí),必須從頭掃描一遍,這樣就增加了STW的時(shí)間
G1垃圾回收器
G1(Garbage-First)垃圾回收器是Java虛擬機(jī)(JVM)的一個(gè)高級(jí)垃圾回收器,旨在為具有大內(nèi)存的多處理器機(jī)器提供高吞吐量和低延遲。G1垃圾回收器的主要特點(diǎn)包括:
特點(diǎn)
- 分區(qū)堆結(jié)構(gòu):G1將堆內(nèi)存分割成多個(gè)大小相等的區(qū)域(Region),這些區(qū)域可以被劃分為Eden區(qū)、Survivor區(qū)或Old區(qū)。這種分區(qū)方法有助于更有效地管理堆空間。
- 并發(fā)和并行處理:G1結(jié)合了并發(fā)和并行的垃圾回收機(jī)制,以優(yōu)化性能和減少停頓時(shí)間。
- 可預(yù)測(cè)的停頓時(shí)間:G1的一個(gè)關(guān)鍵目標(biāo)是提供可預(yù)測(cè)的停頓時(shí)間,允許用戶指定期望的停頓時(shí)間目標(biāo)(例如,不超過(guò)50毫秒),G1將盡量在這個(gè)時(shí)間范圍內(nèi)完成垃圾回收。
- 增量式清理:G1通過(guò)逐步清理堆中的區(qū)域來(lái)管理垃圾回收,這有助于控制停頓時(shí)間。
- 記憶集(RSet):G1使用記憶集來(lái)跟蹤跨區(qū)域引用的對(duì)象,這有助于在垃圾回收時(shí)快速確定哪些對(duì)象是存活的。
- 混合收集:G1可以同時(shí)回收Young和Old區(qū)域。在進(jìn)行混合收集時(shí),G1會(huì)根據(jù)需要和停頓時(shí)間目標(biāo)選擇性地回收一部分Old區(qū)域。
- 高效的大對(duì)象處理:G1能夠更有效地處理大對(duì)象,因?yàn)樗梢钥缍鄠€(gè)區(qū)域分配這些對(duì)象。
- 自適應(yīng)調(diào)整:G1會(huì)根據(jù)應(yīng)用程序的行為和指定的停頓時(shí)間目標(biāo)自動(dòng)調(diào)整堆占用和回收策略。
- 適用于大堆:G1特別適合于大堆(多GB)的應(yīng)用,因?yàn)樗軌蚋玫毓芾泶髢?nèi)存并保持合理的停頓時(shí)間。
分區(qū)算法(Region)
G1的物理分區(qū)從分代變成了分區(qū)(Region),邏輯上分代,物理上則取消了分代,把堆整體劃分成了多個(gè)(2048)相同大小的小格子(Region)
其中,每個(gè)Region的大小可通過(guò)-XX:G1HeapRegionSize設(shè)定,取值范圍為1-32MB,且必須為2的N次冪,即只能為2,4,8,16,32這五個(gè)數(shù)
每一個(gè)Region都可以根據(jù)需要充當(dāng)新生代的Eden區(qū)、S區(qū)(G1取消了S0和S1,只使用一個(gè)Survivor區(qū))或者老年代。在一般的垃圾收集中對(duì)于堆中的大對(duì)象,默認(rèn)直接會(huì)被分配到老年代,但是如果它是一個(gè)短期存在的大對(duì)象,就會(huì)對(duì)垃圾收集器造成負(fù)面影響。為了解決這個(gè)問題,G1劃分了一個(gè)Humongous區(qū),它用來(lái)專門存放大對(duì)象。如果一個(gè)H區(qū)裝不下一個(gè)大對(duì)象,那么G1會(huì)尋找連續(xù)的H區(qū)來(lái)存儲(chǔ)。為了能找到連續(xù)的H區(qū),有時(shí)候不得不啟動(dòng)Full GC。 G1的大多數(shù)行為都把H區(qū)作為老年代的一部分來(lái)看待。當(dāng)一個(gè)對(duì)象的大小超過(guò)了一個(gè)Region容量的一半,即被認(rèn)為是大對(duì)象。
雖然G1仍然保留新生代和老年代的概念,但新生代和老年代不再是固定的了,而是一系列區(qū)域(不需要連續(xù),邏輯連續(xù)即可)的動(dòng)態(tài)集合。由于G1這種基于Region回收的方式,可以預(yù)測(cè)停頓時(shí)間。G1會(huì)根據(jù)每個(gè)Region里面垃圾“價(jià)值”的大小,在后臺(tái)維護(hù)一個(gè)優(yōu)先級(jí)列表,每次根據(jù)用戶設(shè)定的允許收集停頓的時(shí)間(-XX:MaxGCPauseMillis,默認(rèn)為200毫秒)優(yōu)先處理價(jià)值收益最大的Region。
垃圾回收過(guò)程
G1采用的復(fù)制(copying)算法進(jìn)行回收
- 初始標(biāo)記:僅僅只是標(biāo)記一下GC Roots能直接關(guān)聯(lián)到的對(duì)象,并且修改TAMS指針的值,讓下一階段用戶線程并發(fā)運(yùn)行時(shí),能夠在Region上正確的分配對(duì)象。這個(gè)階段需要STW,耗時(shí)很短,而且是借用MinorGC(上一輪垃圾回收時(shí)觸發(fā)GC)時(shí)候同步完成的。
- 并發(fā)標(biāo)記:從GC Roots 開始對(duì)堆中的對(duì)象進(jìn)行可達(dá)性分析,遞歸掃描整個(gè)堆里的對(duì)象,這個(gè)過(guò)程耗時(shí)較長(zhǎng),但是是與用戶線程并發(fā)執(zhí)行的。對(duì)象掃描完之后還需要重新處理STAB記錄下的在并發(fā)時(shí)有引用變動(dòng)的對(duì)象。
- 最終標(biāo)記:這個(gè)階段也需要STW,用于處理并發(fā)階段結(jié)束后仍然遺留下來(lái)的最后少量的STAB記錄。
- 篩選回收:負(fù)責(zé)更新Region的統(tǒng)計(jì)數(shù)據(jù),對(duì)各個(gè)Region的回收價(jià)值和成本排序,根據(jù)用戶期望的停頓時(shí)間來(lái)執(zhí)行回收計(jì)劃,然后把決定回收的Region里的存活對(duì)象復(fù)制到空的Region,然后清空舊Region的空間。由于涉及到對(duì)象的移動(dòng),所以這個(gè)階段也是需要STW的。
從上述可以看出,除了并發(fā)標(biāo)記,其他階段都是需要STW的,G1收集器不單單是追求低延遲的收集器,也衡量了吞吐量,所以在延遲和吞吐量之間做了一個(gè)權(quán)衡。
G1對(duì)于三色標(biāo)記的錯(cuò)標(biāo)處理
從上述過(guò)程可以看出G1的處理方式是SATB(snapshot at the begining),即在并發(fā)標(biāo)記中,如果出現(xiàn)引用的變更,G1的垃圾回收器會(huì)記錄在SATB中,每次線程切回來(lái)進(jìn)行垃圾回收時(shí),先讀取SATB中的記錄。
RememberedSet
簡(jiǎn)稱RSet,記錄了其他Region的對(duì)象到本Region的引用,使得垃圾回收器不需要掃描整個(gè)堆找到誰(shuí)引用了當(dāng)前分區(qū)的對(duì)象,只需掃描RSet即可
更多技術(shù)干貨,歡迎關(guān)注我!
總結(jié)
以上是生活随笔為你收集整理的【JVM】一文掌握JVM垃圾回收机制的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Codeforces 918(div4)
- 下一篇: 文心一言大模型-function Cal