JVM原理之完整的一次GC流程解讀
JVM 的 GC 是指垃圾回收,主要是對(duì)堆內(nèi)存的回收。
本文將介紹 JVM 中一次完整的 GC 流程是怎樣的,首先拋出第一個(gè)問題,什么樣的對(duì)象會(huì)是 JVM 回收的目標(biāo)?
一、可達(dá)性分析算法(GC Roots)
有一種引用計(jì)數(shù)法,可以用來判斷對(duì)象被引用的次數(shù),如果引用次數(shù)為0,則代表可以被回收。
這種實(shí)現(xiàn)方式比較簡(jiǎn)單,但對(duì)于循環(huán)引用的情況束手無策,所以 Java 采用了可達(dá)性分析算法。
即判斷某個(gè)對(duì)象是否與 GC Roots 的這類對(duì)象之間的路徑可達(dá),若不可達(dá),則有可能成為回收對(duì)象,被判定為不可達(dá)的對(duì)象要成為可回收對(duì)象必須至少經(jīng)歷兩次標(biāo)記過程,如果在這兩次標(biāo)記過程中仍然沒有逃脫成為可回收對(duì)象的可能性,則基本上就真的成為可回收對(duì)象了。
在 Java 中,可作為 GC Roots 的對(duì)象包括以下幾種:
- 虛擬機(jī)棧(本地變量表)中引用的對(duì)象
- 方法區(qū)中類靜態(tài)屬性引用的對(duì)象
- 方法區(qū)中常量引用的對(duì)象
- 本地方法棧中引用的對(duì)象
二、JVM中的堆結(jié)構(gòu)
JVM 中的堆可劃分為兩大部分,新生代和老年代,大小比例為1:2,如下:
其中,新生代分為 Eden 區(qū)和 Survivor 區(qū), Survivor 幸存者區(qū)又分為大小相等的兩塊 from 和 to 區(qū)。
這便是 JVM 中堆的結(jié)構(gòu)和各部分默認(rèn)的比例,當(dāng)然這些比例都可通過對(duì)應(yīng) JVM 參數(shù)來調(diào)整。
2.1 為何新生代要分為三個(gè)區(qū)
這里需要介紹新生代的垃圾回收算法——復(fù)制算法。
該算法的核心是將可用內(nèi)存按容量劃分為大小相等的兩塊,每次回收周期只用其中一塊,當(dāng)這一塊的內(nèi)存用完,就將還存活的對(duì)象復(fù)制到另一塊上面,然后把已使用過的內(nèi)存空間清理掉。
- 優(yōu)點(diǎn):不必考慮內(nèi)存碎片問題;效率高。
- 缺點(diǎn):可用容量減少為原來的一半,比較浪費(fèi)。
【最優(yōu)設(shè)置】:根據(jù)權(quán)威數(shù)據(jù)分析,90%的對(duì)象都是朝生夕死的,所以采用10%的空間用作交換區(qū),因?yàn)榻粨Q區(qū)必須要有等量的兩個(gè),所以采用復(fù)制算法中新生代中三個(gè)區(qū)默認(rèn)分配比例為8:1:1。
2.2 新生代對(duì)象的分配和回收
(1)基本上新的對(duì)象優(yōu)先在 Eden 區(qū)分配;
(2)當(dāng) Eden 區(qū)沒有足夠空間時(shí),會(huì)發(fā)起一次 Minor GC;
(3)Minor GC 回收新生代采用復(fù)制回收算法的改進(jìn)版本,即
- from 區(qū)和 to 區(qū)的兩個(gè)交換區(qū),這兩個(gè)區(qū)只有一個(gè)區(qū)有數(shù)據(jù)
- 采用8:1:1的默認(rèn)分配比例(-XX:SurvivorRatio默認(rèn)為8,代表 Eden 區(qū)與 Survivor 區(qū)的大小比例)
2.3 老年代對(duì)象的分配和回收
(1)老年代的對(duì)象一般來自于新生代中的長(zhǎng)期存活對(duì)象。這里有一概念叫做年齡閾值,每個(gè)對(duì)象定義了年齡計(jì)數(shù)器,經(jīng)過一次 Minor GC (在交換區(qū))后年齡加1,對(duì)象年齡達(dá)到15次后將會(huì)晉升到老年代,老年代空間不夠時(shí)進(jìn)行 Full GC。當(dāng)然這個(gè)參數(shù)仍是可以通過 JVM 參數(shù)(-XX:MaxTenuringThreshold,默認(rèn)15)來調(diào)整。
(2)大對(duì)象直接進(jìn)入老年代。即超過 Eden 區(qū)空間,或超過一個(gè)參數(shù)值(-XX:PretenureSizeThreshold=30m,無默認(rèn)值)。這樣做的目的是避免在Eden區(qū)及兩個(gè)Survivor區(qū)之間發(fā)生大量的內(nèi)存復(fù)制。
(3)對(duì)象提前晉升到老年代(組團(tuán))。動(dòng)態(tài)年齡判定:如果在 Survivor 區(qū)中相同年齡所有對(duì)象大小總和大于 Survivor 區(qū)大小的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,而無須等到自己的晉升年齡。
三、JVM完整的GC流程
對(duì)象的正常流程:Eden 區(qū) -> Survivor 區(qū) -> 老年代。
新生代GC:Minor GC;老年代GC:Full GC,比 Minor GC 慢10倍。
【總結(jié)】:內(nèi)存區(qū)域不夠用了,就會(huì)引發(fā)GC,JVM 會(huì)“stop the world”,嚴(yán)重影響性能。Minor GC 避免不了,F(xiàn)ull GC 盡量避免。
【處理方式】:保存堆棧快照日志、分析內(nèi)存泄漏、調(diào)整內(nèi)存設(shè)置控制垃圾回收頻率,選擇合適的垃圾回收器等。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用Vue+Spring Boot實(shí)現(xiàn)Excel上傳功能
這篇文章主要介紹了使用Vue+Spring Boot實(shí)現(xiàn)Excel上傳,需要的朋友可以參考下2018-11-11mybatis?log4j2打印sql+日志實(shí)例代碼
在學(xué)習(xí)mybatis的時(shí)候,如果用log4j2來協(xié)助查看調(diào)試信息,則會(huì)大大提高學(xué)習(xí)的效率,加快debug速度,下面這篇文章主要給大家介紹了關(guān)于mybatis?log4j2打印sql+日志的相關(guān)資料,需要的朋友可以參考下2022-08-08Java結(jié)合Vue項(xiàng)目打包并進(jìn)行服務(wù)器部署
本文主要介紹了Java結(jié)合Vue項(xiàng)目打包并進(jìn)行服務(wù)器部署,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07Java分支結(jié)構(gòu)程序設(shè)計(jì)實(shí)例詳解
這篇文章主要介紹了Java分支結(jié)構(gòu)程序設(shè)計(jì)例題,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-01-01springboot+redis過期事件監(jiān)聽實(shí)現(xiàn)過程解析
這篇文章主要介紹了springboot+redis過期事件監(jiān)聽實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-03-03IDEA 單元測(cè)試報(bào)錯(cuò):Class not found:xxxx springb
這篇文章主要介紹了IDEA 單元測(cè)試報(bào)錯(cuò):Class not found:xxxx springboot的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-01-01