java基礎(chǔ)學(xué)習(xí)JVM中GC的算法
在java學(xué)習(xí)到JVM時(shí)候,總會(huì)很多朋友問到關(guān)于GC算法的問題,小編在此給大家整理關(guān)于JVM中GC算法的原理以及圖文詳細(xì)分析,希望能夠幫助你對(duì)這個(gè)GC算法的理解。
JVM內(nèi)存組成結(jié)構(gòu):

(1)堆
所有通過new創(chuàng)建的對(duì)象都是在堆中分配內(nèi)存,其大小可以通過-Xmx和-Xms來控制,堆被劃分為新生代和舊生代,新生代又被進(jìn)一步劃分為Eden和Survivor區(qū)。Survivor被劃分為from space 和 to space組成,結(jié)構(gòu)圖如下:

(2)棧
每個(gè)線程 執(zhí)行每個(gè)方法的時(shí)候都會(huì)在棧中申請(qǐng)一個(gè)棧幀,每個(gè)棧幀包含局部變量區(qū)和操作數(shù)棧。用于存放此次方法調(diào)用過程中的臨時(shí)變量,參數(shù)和中間結(jié)果
(3)本地方法棧
用于支持native方法的執(zhí)行。存儲(chǔ)了每個(gè)native方法調(diào)用的狀態(tài)
(4)方法區(qū)
存放了要加載的類信息,靜態(tài)變量,final類型的常量,屬性和方法信息。JVM用持久代(permanet generation)來存放方法區(qū),可通過-XX:PermSize和 -XX:MaxPermSize來指定最小值和最大值。
(5)程序計(jì)數(shù)器
每個(gè)線程私有,當(dāng)前線程執(zhí)行的字節(jié)碼的行數(shù)。
JAVA堆內(nèi)存分配機(jī)制
java內(nèi)存分配和回收概括地說:就是分代分配,分代回收。對(duì)象將根據(jù)存貨的時(shí)間被分為:young generation, old generation,permanent generation。

yong generation:對(duì)象被創(chuàng)建時(shí),內(nèi)存的分配首先發(fā)生在年輕代(大對(duì)象可以直接創(chuàng)建在old generation),大部分的對(duì)象在創(chuàng)建后很快不再使用,因此很快變得不可達(dá),被young generation 的GC機(jī)制清理掉(IBM的研究表示,98%的對(duì)象都是很快消亡的),這個(gè)GC機(jī)制被稱為Minor GC或者 Young GC;Minor GC并不代表內(nèi)存不足。
young generation分為 3個(gè)區(qū)域, eden區(qū),兩個(gè) survivor區(qū)(from survivor, to survivor),內(nèi)存分配過程如下所示:

1.絕大多數(shù)對(duì)象剛創(chuàng)建被分配在 eden區(qū),其中的大多數(shù)對(duì)象很快就會(huì)消亡,eden區(qū)域是連續(xù)的內(nèi)存空間,在其上分配內(nèi)存極快。
2.最初一次,當(dāng)eden區(qū)滿的時(shí)候,執(zhí)行 minor GC,將消亡的對(duì)象清理掉,并將eden,survivor 1剩余的對(duì)象復(fù)制到到一個(gè)存活區(qū) Survivor 0(此時(shí)Survivor 1是空白的,兩個(gè)Survivor總有一個(gè)是空白的)
3.下次eden滿了,在執(zhí)行一次 minor GC,將消亡的對(duì)象清理掉,存活的對(duì)象復(fù)制到survivor1中,清空eden區(qū)。將survivor 0 中消亡的對(duì)象清理掉,將其中可以晉級(jí)的對(duì)象晉級(jí)到old區(qū),將存活的對(duì)象也復(fù)制到survivor 1中,清空survivor 0
4.當(dāng)被兩個(gè)存活期 來回復(fù)制了幾次之后,(用-XX:maxTenuringThreshold 控制,大于該值進(jìn)入old generation,但是這只是個(gè)最大值,并不代表一定是這個(gè)值,因?yàn)椋簽榱四芨玫剡m應(yīng)不同程序的內(nèi)存狀況,虛擬機(jī)并不是永遠(yuǎn)地要求對(duì)象的年齡必須達(dá)到了MaxTenuringThreshold才能晉升老年代,如果在Survivor空間中相同年齡所有對(duì)象大小的總和大于Survivor空間的一半,年齡大于或等于該年齡的對(duì)象就可以直接進(jìn)入老年代,無須等到MaxTenuringThreshold中要求的年齡。)仍然存活的對(duì)象,將被復(fù)制到old generation。
old generation:對(duì)象如果在young generation 存活了足夠長(zhǎng)的時(shí)間而沒有被清理掉,則會(huì)被復(fù)制到old generation,old generation 的空間一般比young generation大得多,發(fā)生的GC次數(shù)也比年輕代少,當(dāng)年老代內(nèi)存不足時(shí),將執(zhí)行 Major GC,也叫 Full GC;
可以使用-XX:+UseAdaptiveSizePolicy開關(guān)來控制是否采用動(dòng)態(tài)控制策略,如果動(dòng)態(tài)控制,則動(dòng)態(tài)調(diào)整Java堆中各個(gè)區(qū)域的大小以及進(jìn)入老年代的年齡。
如果對(duì)象比較大,young generation空間不足,則大對(duì)象會(huì)直接分配到old generation(大對(duì)象可能提前觸發(fā)GC,應(yīng)盡少使用大對(duì)象,更少用短命的大對(duì)象)。用-XX:PretenureSizeThreshold來控制直接升入老年代的對(duì)象大小,大于這個(gè)值的對(duì)象會(huì)直接分配在老年代上。
可能存在年老代對(duì)象引用新生代對(duì)象的情況,如果需要執(zhí)行Young GC,則可能需要查詢整個(gè)老年代以確定是否可以清理回收,這顯然是低效的。解決的方法是,年老代中維護(hù)一個(gè)512 byte的塊——”card table“,所有老年代對(duì)象引用新生代對(duì)象的記錄都記錄在這里。Young GC時(shí),只要查這里即可,不用再去查全部老年代,因此性能大大提高。
JAVA 不同代GC 機(jī)制
young generation:發(fā)生的GC是minor GC,使用停止-復(fù)制算法進(jìn)行清理,將新生代內(nèi)存分為:較大的eden,和兩個(gè)相等survivor,每次進(jìn)行清理時(shí),把eden 和一個(gè)survivor中存活的對(duì)象 復(fù)制到另一個(gè)survivor中,如果存活的對(duì)象超過了survivor內(nèi)存,則需要通過空間分配擔(dān)保機(jī)制將一部分對(duì)象復(fù)制到old generation,然后清理掉eden,和剛才的survivor??梢酝ㄟ^ -XX:SurvivorRation參數(shù)來調(diào)整eden和survivor區(qū)的內(nèi)存容量比值。默認(rèn)是8,eden:survivor:survivor = 8:1:1。
old generation:發(fā)生major GC。存儲(chǔ)的對(duì)象比young generation多得多,且存在很多大對(duì)象,對(duì)old generation進(jìn)行內(nèi)存清理,如果使用 停止-復(fù)制算法,相當(dāng)?shù)托?,一般使?標(biāo)記-整理算法,標(biāo)記出仍然存活的對(duì)象(存在引用),將所有存活的對(duì)象向一端移動(dòng),以保證內(nèi)存的連續(xù)。
在發(fā)生Minor GC時(shí),虛擬機(jī)會(huì)檢查每次晉升進(jìn)入老年代的大小是否大于老年代的剩余空間大小,如果大于,則直接觸發(fā)一次Full GC,否則,就查看是否設(shè)置了-XX:+HandlePromotionFailure(允許擔(dān)保失?。绻试S,則只會(huì)進(jìn)行MinorGC,此時(shí)可以容忍內(nèi)存分配失?。蝗绻辉试S,則仍然進(jìn)行Full GC(這代表著如果設(shè)置-XX:+Handle PromotionFailure,則觸發(fā)MinorGC就會(huì)同時(shí)觸發(fā)Full GC,哪怕老年代還有很多內(nèi)存,所以,最好不要這樣做)。
old generation GC之 標(biāo)記-清除法:標(biāo)記出所有需要回收的對(duì)象(可達(dá)性分析),標(biāo)記完成后統(tǒng)一清理掉所有被標(biāo)記的對(duì)象。

該算法有兩個(gè)問題:
(1)下頻率問題:標(biāo)記和清除過程效率都不高
(2)空間問題:標(biāo)記清除后會(huì)產(chǎn)生大量的不連續(xù)的內(nèi)存碎片,空間碎片太多會(huì)導(dǎo)致在運(yùn)行過程需要分配較大對(duì)象時(shí)無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集。
old generation GC之 標(biāo)記整理算法:標(biāo)記過程和標(biāo)記清除算法一樣,但是后續(xù)步驟不再對(duì)可回收對(duì)象直接清理,而是讓所有存活對(duì)象都向一端移動(dòng),然后清理掉邊界以外的內(nèi)存。

permanent generation:永久代的垃圾收集分為兩類:廢棄的常量和不再被使用的類。
廢棄常量:比如說我們?cè)诔A砍刂杏胕ntern()添個(gè)字符串常量“a”,但是現(xiàn)在的系統(tǒng)沒有任何一個(gè)String對(duì)象叫“a”,所以這個(gè)常量就是廢棄了。
不再被使用的類:
①該類的所有的實(shí)例都已經(jīng)被回收,Java堆中不存在該類的任何實(shí)例。
②加載類的ClassLoader已經(jīng)被回收
③沒有該類的java.lang.Class對(duì)象被引用,即不能通過反射訪問該類信息。
滿足了上述三個(gè)條件只是滿足了類回收的基本條件,是否回收不用的類需要看設(shè)置的-Xnoclassgc參數(shù)進(jìn)行控制,還可以使用-verbose:class及-XX:+TraceClassLoading、 -XX:+TraceClassUnLoading查看類的加載和卸載信息。
在大量使用反射、動(dòng)態(tài)代理、CGLib等bytecode框架的場(chǎng)景,以及動(dòng)態(tài)生成JSP和OSGi這類頻繁自定義ClassLoader的場(chǎng)景都需要虛擬機(jī)具備類卸載的功能,以保證永久代不會(huì)溢出。
算法分析:
空間分配擔(dān)保:
在執(zhí)行Minor GC前, VM會(huì)首先檢查老年代是否有足夠的空間存放新生代尚存活對(duì)象, 由于新生代使用復(fù)制收集算法, 為了提升內(nèi)存利用率, 只使用了其中一個(gè)Survivor作為輪換備份, 因此當(dāng)出現(xiàn)大量對(duì)象在Minor GC后仍然存活的情況時(shí), 就需要老年代進(jìn)行分配擔(dān)保, 讓Survivor無法容納的對(duì)象直接進(jìn)入老年代, 但前提是老年代需要有足夠的空間容納這些存活對(duì)象. 但存活對(duì)象的大小在實(shí)際完成GC前是無法明確知道的, 因此Minor GC前, VM會(huì)先首先檢查老年代連續(xù)空間是否大于新生代對(duì)象總大小或歷次晉升的平均大小, 如果條件成立, 則進(jìn)行Minor GC, 否則進(jìn)行Full GC(讓老年代騰出更多空間).然而取歷次晉升的對(duì)象的平均大小也是有一定風(fēng)險(xiǎn)的, 如果某次Minor GC存活后的對(duì)象突增,遠(yuǎn)遠(yuǎn)高于平均值的話,依然可能導(dǎo)致?lián)J?Handle Promotion Failure, 老年代也無法存放這些對(duì)象了), 此時(shí)就只好在失敗后重新發(fā)起一次Full GC(讓老年代騰出更多空間).
GC回收對(duì)象確立:
引用計(jì)數(shù):如果有引用這個(gè)對(duì)象的,對(duì)象計(jì)數(shù)器+1,引用失效,計(jì)數(shù)器-1,絕大多數(shù)情況下,這個(gè)算法高效簡(jiǎn)單,但是不能解決對(duì)象之間的循環(huán)引用的關(guān)系,所以沒有被主流語(yǔ)言采用。
可達(dá)性算法:通過一系列的稱為 GC Roots 的對(duì)象作為起點(diǎn), 然后向下搜索; 搜索所走過的路徑稱為引用鏈/Reference Chain, 當(dāng)一個(gè)對(duì)象到 GC Roots 沒有任何引用鏈相連時(shí), 即該對(duì)象不可達(dá), 也就說明此對(duì)象是不可用的, 如下圖: Object5、6、7 雖然互有關(guān)聯(lián), 但它們到GC Roots是不可達(dá)的, 因此也會(huì)被判定為可回收的對(duì)象:

在Java, 可作為GC Roots的對(duì)象包括:
方法區(qū): 類靜態(tài)屬性引用的對(duì)象; 方法區(qū): 常量引用的對(duì)象; 虛擬機(jī)棧(本地變量表)中引用的對(duì)象. 本地方法棧JNI(Native方法)中引用的對(duì)象。
可達(dá)性算法如果對(duì)象到GC Roots不可達(dá)也不是說這個(gè)對(duì)象會(huì)立即被回收,是需要經(jīng)過一個(gè)兩次標(biāo)記過程的,第一次:是在可達(dá)性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈。第二次是判斷是否需要執(zhí)行finalize(),這個(gè)方法也是對(duì)象唯一能夠進(jìn)行自我救贖的機(jī)會(huì)了,但是不推薦使用,因?yàn)檫@個(gè)方法運(yùn)行代價(jià)高,不確定性大,不能保證不同對(duì)象的執(zhí)行順序。如果不需要執(zhí)行該方法,直接就進(jìn)行回收,如果需要執(zhí)行該方法,那么該對(duì)象會(huì)被放在一個(gè)F-Queue里。雖然會(huì)被執(zhí)行,但是不一定保證能夠執(zhí)行成功,因?yàn)橛锌赡軙?huì)在這個(gè)方法執(zhí)行過程中出現(xiàn)死循環(huán)等意外情況,所以虛擬機(jī)并不一定會(huì)等待這個(gè)方法執(zhí)行結(jié)束才進(jìn)行回收。如果在第二次標(biāo)記的時(shí)候,該對(duì)象沒有成功的自我拯救,那么就真的被回收了。
常用JVM配置參數(shù)
-XX:CMSInitiatingPermOccupancyFraction:當(dāng)永久區(qū)占用率達(dá)到這一百分比時(shí),啟動(dòng)CMS回收
-XX:CMSInitiatingOccupancyFraction:設(shè)置CMS收集器在老年代空間被使用多少后觸發(fā)
-XX:+CMSClassUnloadingEnabled:允許對(duì)類元數(shù)據(jù)進(jìn)行回收
-XX:CMSFullGCsBeforeCompaction:設(shè)定進(jìn)行多少次CMS垃圾回收后,進(jìn)行一次內(nèi)存壓縮
-XX:NewRatio:新生代和老年代的比
-XX:ParallelCMSThreads:設(shè)定CMS的線程數(shù)量
-XX:ParallelGCThreads:設(shè)置用于垃圾回收的線程數(shù)
-XX:SurvivorRatio:設(shè)置eden區(qū)大小和survivior區(qū)大小的比例
-XX:+UseParNewGC:在新生代使用并行收集器
-XX:+UseParallelGC :新生代使用并行回收收集器
-XX:+UseParallelOldGC:老年代使用并行回收收集器
-XX:+UseSerialGC:在新生代和老年代使用串行收集器
-XX:+UseConcMarkSweepGC:新生代使用并行收集器,老年代使用CMS+串行收集器
-XX:+UseCMSCompactAtFullCollection:設(shè)置CMS收集器在完成垃圾收集后是否要進(jìn)行一次內(nèi)存碎片的整理
-XX:+UseCMSInitiatingOccupancyOnly:表示只在到達(dá)閥值的時(shí)候,才進(jìn)行CMS回收
(+不能省)
-Xms:設(shè)置堆的最小空間大小。
-Xmx:設(shè)置堆的最大空間大小。
-XX:NewSize設(shè)置新生代最小空間大小。
-XX:MaxNewSize設(shè)置新生代最大空間大小。
-XX:PermSize設(shè)置永久代最小空間大小。
-XX:MaxPermSize設(shè)置永久代最大空間大小。
-Xss:設(shè)置每個(gè)線程的堆棧大小
相關(guān)文章
Java形參和實(shí)參的實(shí)例之Integer類型與Int類型用法說明
這篇文章主要介紹了Java形參和實(shí)參的實(shí)例之Integer類型與Int類型用法說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-10-10
Spring Boot Maven 打包可執(zhí)行Jar文件的實(shí)現(xiàn)方法
這篇文章主要介紹了Spring Boot Maven 打包可執(zhí)行Jar文件的實(shí)現(xiàn)方法,需要的朋友可以參考下2018-02-02
Java8 將List轉(zhuǎn)換為用逗號(hào)隔開的字符串的多種方法
這篇文章主要介紹了Java8 將List轉(zhuǎn)換為用逗號(hào)隔開的字符串的幾種方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Spring中Controller和RestController的區(qū)別詳解
這篇文章主要介紹了Spring中Controller和RestController的區(qū)別詳解,@Controller是標(biāo)識(shí)一個(gè)Spring類是Spring MVC controller處理器,@Controller類中的方法可以直接通過返回String跳轉(zhuǎn)到j(luò)sp、ftl、html等模版頁(yè)面,需要的朋友可以參考下2023-09-09
IntelliJ IDEA 安裝及初次使用圖文教程(2020.3.2社區(qū)版)
這篇文章主要介紹了IntelliJ IDEA 安裝及初次使用(2020.3.2社區(qū)版),本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-03-03
Java跳臺(tái)階實(shí)現(xiàn)思路和代碼
今天小編就為大家分享一篇關(guān)于Java跳臺(tái)階實(shí)現(xiàn)思路和代碼,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來看看吧2019-01-01
Java實(shí)現(xiàn)將彩色PDF轉(zhuǎn)為灰度PDF的示例代碼
本文以Java代碼為例介紹如何實(shí)現(xiàn)將彩色PDF文件轉(zhuǎn)為灰度(黑白)的PDF文件,文中的示例代碼講解詳細(xì),感興趣的小伙伴快跟隨小編一起學(xué)習(xí)一下吧2022-03-03
為什么rest接口返回json建議采用下劃線形式,不要用駝峰
為什么rest接口返回json建議采用下劃線形式,不要用駝峰?今天小編就來為大家說明一下原因,還等什么?一起跟隨小編過來看看吧2020-09-09

