淺談Java虛擬機(jī)(三)—垃圾回收

一、簡(jiǎn)介

? ? (建議讀者了解Java虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)域后再閱讀本章節(jié))

????在上一篇文章《淺談Java虛擬機(jī)(二)—運(yùn)行時(shí)數(shù)據(jù)區(qū)域》中,我們一起學(xué)習(xí)了Java虛擬機(jī)的內(nèi)存區(qū)域劃分以及每個(gè)區(qū)域的作用腰涧,并且知道了其中的虛擬機(jī)棧冗茸、本地方法棧茎毁、程序計(jì)數(shù)器是線程私有的祭犯,它們的生命周期是隨線程誕生和消亡,其中所占用的內(nèi)存會(huì)隨著消亡而自動(dòng)釋放统捶,因此榆芦,Java虛擬機(jī)不需要管理這部分的內(nèi)存

????Java堆與方法區(qū)都是所有線程共享的區(qū)域喘鸟,其中Java堆作為Java虛擬機(jī)內(nèi)存中主要存放對(duì)象的區(qū)域匆绣,當(dāng)某個(gè)對(duì)象不會(huì)再被使用到時(shí),就需要Java虛擬機(jī)維護(hù)并清理這個(gè)對(duì)象所占有的內(nèi)存什黑,否則大量“無用”的對(duì)象堆積在Java堆中會(huì)很容易出現(xiàn)內(nèi)存溢出的現(xiàn)象崎淳,而這個(gè)回收“無用”對(duì)象的機(jī)制就稱為垃圾回收(Garbage Collection,以下簡(jiǎn)稱GC)機(jī)制愕把,實(shí)現(xiàn)這個(gè)機(jī)制的代碼模塊被稱為垃圾收集器拣凹。這里延伸補(bǔ)充一點(diǎn),垃圾回收并不是Java虛擬機(jī)的伴生產(chǎn)物礼华,更不是Java虛擬機(jī)獨(dú)有的特性咐鹤,事實(shí)上,垃圾回收的歷史比Java的誕生都更久遠(yuǎn)圣絮,只是Java使用了這個(gè)機(jī)制來管理內(nèi)存而已祈惶。

? ? 在上一章節(jié)的講解中提到過,方法區(qū)存儲(chǔ)了大量的常量扮匠、靜態(tài)變量捧请,在JDK1.7以前(此處仍然以主流商用虛擬機(jī)Sun HotSpot為例),其中的運(yùn)行時(shí)常量池還包含了字符串常量池棒搜,此時(shí)的方法區(qū)甚至是用永久代來實(shí)現(xiàn)疹蛉,方便GC的分代收集機(jī)制(下面會(huì)講到)回收此區(qū)域的內(nèi)存,然而力麸,Java虛擬機(jī)開發(fā)人員逐漸意識(shí)到可款,方法區(qū)中存放的不論常量、靜態(tài)變量還是字符串克蚂,可回收的價(jià)值都不高(它們大多都是整個(gè)程序運(yùn)行期間一直存在的闺鲸,回收價(jià)值相對(duì)較高的就是字符串),還浪費(fèi)了GC的性能埃叭,于是摸恍,逐步放棄用永久代實(shí)現(xiàn)方法區(qū),在JDK1.7時(shí),把字符串常量池放進(jìn)了Java堆中立镶,在JDK1.8時(shí)壁袄,使用元空間替代永久代實(shí)現(xiàn)方法區(qū),元空間在Java虛擬機(jī)之外媚媒,使用的本地內(nèi)存嗜逻,不易發(fā)生OOM,但同樣會(huì)觸發(fā)GC缭召,只是GC后會(huì)動(dòng)態(tài)擴(kuò)展元空間的內(nèi)存变泄,關(guān)于方法區(qū)更多細(xì)節(jié)可關(guān)注我的上一篇文章。

? ? 即使其他虛擬機(jī)對(duì)方法區(qū)的實(shí)現(xiàn)有所差異恼琼,Java堆仍是GC主要作用的目標(biāo)

? ? GC如此智能屏富,那么我們?yōu)槭裁催€要了解它呢晴竞,答案引用《深入理解Java虛擬機(jī)—周志明》中的原話:

? ? 當(dāng)需要排查各種內(nèi)存溢出、內(nèi)存泄漏問題時(shí)狠半,當(dāng)垃圾收集稱為系統(tǒng)達(dá)到更高并發(fā)量的瓶頸時(shí)噩死,我們就需要對(duì)這些“自動(dòng)化”的技術(shù)實(shí)施必要的監(jiān)控和調(diào)節(jié)

? ? 下面將從GC是如何判斷對(duì)象可回收的,如何回收的(即有哪些回收算法)神年,以及何時(shí)觸發(fā)回收這幾個(gè)方面詳解垃圾回收機(jī)制已维。

二、如何判斷對(duì)象可回收

????垃圾回收的前提就是正確判斷可回收的對(duì)象已日,否則這個(gè)機(jī)制就亂套了垛耳。那么GC是如何判斷一個(gè)對(duì)象是否能回收呢,只要有以下幾種方法:

? ?2.1 引用計(jì)數(shù)法? ??

? ? ? ? 原理很簡(jiǎn)單飘千,就是給每一個(gè)對(duì)象維護(hù)一個(gè)引用計(jì)數(shù)器(這里的引用計(jì)數(shù)器只是一個(gè)算法理念堂鲜,具體實(shí)現(xiàn)方案有很多,例如直接在對(duì)象中維護(hù)一個(gè)整型字段护奈,又比如每一個(gè)Class對(duì)象生成實(shí)例時(shí)為其伴隨生成一個(gè)計(jì)數(shù)器對(duì)象以完成更復(fù)雜的工作缔莲,當(dāng)然,計(jì)數(shù)器對(duì)象本身對(duì)原對(duì)象的引用需要特殊處理霉旗,這里暫不討論具體實(shí)現(xiàn)方案痴奏,明白原理即可),初始值為0厌秒,當(dāng)有任何一個(gè)引用指向該對(duì)象時(shí)读拆,引用計(jì)數(shù)器觸發(fā)自增1操作,反之简僧,當(dāng)引用斷開建椰,引用計(jì)數(shù)器觸發(fā)自減1操作,任何時(shí)刻計(jì)數(shù)器為0的對(duì)象就會(huì)被GC視為不會(huì)再被使用可以清理掉的對(duì)象岛马。

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

????????如上圖所示棉姐,引用計(jì)數(shù)法看似很完美得解決了GC如何判斷對(duì)象是否能回收的問題屠列,但卻有一個(gè)致命缺陷:

引用計(jì)數(shù)法的缺陷

? ? ? ? 如上圖所示,引用計(jì)數(shù)的缺陷就是無法解決循環(huán)引用伞矩,理論上笛洛,圖中的Hello和Hi對(duì)象實(shí)例除了彼此引用,不會(huì)再被任何地方用到乃坤,那么就應(yīng)該被GC清理掉苛让,卻因?yàn)橐糜?jì)數(shù)始終為1無法執(zhí)行清理,如果大量這樣的對(duì)象存在就會(huì)導(dǎo)致內(nèi)存溢出湿诊。

????2.2 可達(dá)性分析算法

? ? ? ? 也稱作GC Root搜索法狱杰,這個(gè)算法的基本思想就是通過一系列被稱為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開始向下搜索引用厅须,搜索所走過的路徑稱為引用鏈仿畸,當(dāng)一個(gè)對(duì)象到GC Roots沒有任何引用鏈相連時(shí),則證明此對(duì)象是不可用的朗和。

? ? ? ? 光看定義可能有些抽象错沽,下面先用一張圖來表示可達(dá)性分析算法,再來解釋其中包含的一些定義:

可達(dá)性分析算法

? ? ? ? 如圖所示眶拉,在Java虛擬機(jī)中維護(hù)了一個(gè)GC Roots集合千埃,里面裝的是該算法認(rèn)為可以作為GC Roots的對(duì)象,以這些對(duì)象作為起點(diǎn)來檢查引用鏈分析一個(gè)對(duì)象是否可用:

????????對(duì)象Object1被其中一個(gè)GC Roots引用忆植,那么可判斷它為存活狀態(tài)放可,同理可推,被Object1所引用的Object2唱逢,Object3也存活吴侦,被Object3引用的Object4存活,至此坞古,該引用鏈上的所有對(duì)象都被判斷為存活备韧;反之,Object5沒有被任何GC Roots引用痪枫,它就被判斷為不可用對(duì)象织堂,Object6、Object7即使被Object5引用奶陈,但由于Object5不可用易阳,因此Object6、Object7也不可用吃粒,這三個(gè)對(duì)象將會(huì)是被回收的對(duì)象潦俺。

? ? ? ? 理解了可達(dá)性分析算法的原理,那么就剩一個(gè)問題:什么對(duì)象會(huì)被作為GC Roots呢?其實(shí)答案顯而易見事示,正在被使用的和長(zhǎng)期存在的對(duì)象早像,這兩種對(duì)象經(jīng)由上一章的講解也很明了,他們就是存在于方法區(qū)的靜態(tài)變量肖爵、常量以及存在于棧(包括Java虛擬機(jī)棧和本地方法棧)中的局部變量卢鹦。

? ? ? ? 再回到引用計(jì)數(shù)法中的循環(huán)引用問題就迎刃而解了,使用可達(dá)性分析算法代替引用計(jì)數(shù)法后劝堪,存在于棧幀c中的局部變量hi和hello就會(huì)作為GC Roots冀自,他們分別指向堆中的Hi和Hello對(duì)象,此時(shí)的Hello和Hi對(duì)象是存活的秒啦,但當(dāng)方法c執(zhí)行完熬粗,棧幀c被彈出,局部變量hi和hello隨即被回收余境,引用斷開荐糜,那么堆中的Hi和Hello對(duì)象再無其他GC Roots引用,即使他們相互持有對(duì)方的引用葛超,也會(huì)被判斷為可回收的對(duì)象。

? ? 2.3 JVM使用哪種方法判斷對(duì)象是否可回收

? ? ? ? 這個(gè)問題直接上代碼驗(yàn)證:

/**? ? ? ??

* @Author: Lv

* @Date: 2020/11/17 10:35

* @Description: GC 測(cè)試類

*/

public class TestGC {

????private static class Hello{

????????private static final int _1MB=1024*1024;

? ? ? ? //這個(gè)屬性是為了占內(nèi)存延塑,方便查看GC日志時(shí)看清楚是否被回收過

? ? ? ? private byte[] size=new byte[2*_1MB];

? ? ? ? private Hi hi;

? ? ? ? private void setHi(Hi hi){

????????????????this.hi=hi;

? ? ? ? }

}

/**

? ? * 其實(shí)創(chuàng)建兩個(gè)Hello對(duì)象互相引用就可以绣张,這里再寫一個(gè)一模一樣

? ? *?的Hi對(duì)象只是為了讓循環(huán)引用更清晰一點(diǎn)

? ? */

? ? private static class Hi{

????????private static final int _1MB=1024*1024;

? ? ? ? private byte[] size=new byte[2*_1MB];

? ? ? ? private Hello hello;

? ? ? ? private void setHello(Hello hello){

????????????????this.hello=hello;

? ? ? ? }

}

public static void main(String[] args) {

????????Hello hello=new Hello();

? ? ? ? Hi hi=new Hi();

? ? ? ? hello.setHi(hi);

? ? ? ? hi.setHello(hello);

? ? ? ? hello=null;//手動(dòng)斷開連接,與棧幀中的局部變量被回收效果一樣

? ? ? ? hi=null;

? ? ? ? System.gc();//這個(gè)方法只是通知JVM垃圾回收关带,至于什么時(shí)候垃圾回收侥涵,要看JVM自身的調(diào)度

? ? }

}

? ? ? ? 在JVM啟動(dòng)配置項(xiàng)里添加參數(shù):-XX:+PrintGCDetails,用來在控制臺(tái)輸出里查看GC日志宋雏,上面代碼執(zhí)行后完整輸出日志如下:

[GC (System.gc()) [PSYoungGen: 8846K->712K(38400K)] 8846K->720K(125952K), 0.0187288 secs] [Times: user=0.00 sys=0.00, real=0.02 secs]

[Full GC (System.gc()) [PSYoungGen: 712K->0K(38400K)] [ParOldGen: 8K->654K(87552K)] 720K->654K(125952K), [Metaspace: 3221K->3221K(1056768K)], 0.0089721 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]

Heap

????PSYoungGen total 38400K, used 333K [0x00000000d5c00000, 0x00000000d8680000, 0x0000000100000000)

????????eden space 33280K, 1% used [0x00000000d5c00000,0x00000000d5c534a8,0x00000000d7c80000)

????????from space 5120K, 0% used [0x00000000d7c80000,0x00000000d7c80000,0x00000000d8180000)

????????to space 5120K, 0% used [0x00000000d8180000,0x00000000d8180000,0x00000000d8680000)

????ParOldGen total 87552K, used 654K [0x0000000081400000, 0x0000000086980000, 0x00000000d5c00000)

????????object space 87552K, 0% used [0x0000000081400000,0x00000000814a38e8,0x0000000086980000)

????????Metaspace used 3228K, capacity 4500K, committed 4864K, reserved 1056768K

????????class space used 351K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

? ? ? ? 這里簡(jiǎn)單梳理一下日志內(nèi)容芜飘,前兩句概述了此次GC的總覽,后面是新生代和老年代(這兩個(gè)分代下面會(huì)講到)各區(qū)域的空間詳細(xì)信息磨总,這里主要講解加粗的頭兩句:

????????開頭的GC和Full GC表示GC的停頓類型(并不是下面要講的分代收集中的觸發(fā)了新生代和老年代垃圾收集嗦明,切勿混淆),如果出現(xiàn)了Full蚪燕,則表示發(fā)生了Stop-The-World(STW)娶牌,意思是執(zhí)行這次GC時(shí),掛起其他所有線程馆纳,只讓GC線程執(zhí)行诗良。

? ? ? ? 緊跟的括號(hào)中System.gc()表示觸發(fā)此次GC的類型,我們這里是手動(dòng)調(diào)用System.gc()觸發(fā)GC鲁驶,所以顯示這個(gè)鉴裹,如果是系統(tǒng)觸發(fā),則不會(huì)有這個(gè)括號(hào)內(nèi)容。

? ??????(以第一句為例)[PSYoungGen径荔,[ParOldGen: 督禽,[Metaspace:這些是發(fā)生GC的區(qū)域名,名稱不固定猖凛,這與具體實(shí)現(xiàn)垃圾回收機(jī)制的垃圾收集器有關(guān)赂蠢,不同的垃圾收集器有不同的命名。這里三個(gè)區(qū)域分別代表新生代辨泳,老年代以及元空間虱岂。

? ?????(以第一句為例)?8846K->712K(38400K)?表示 GC前該區(qū)域已用容量->GC后該區(qū)域已用容量(該區(qū)域總?cè)萘?。

? ? ? ?(以第一句為例)方括號(hào)之外的8846K->720K(125952K) 表示 GC前Java堆已用容量->GC后Java堆已用容量(Java堆總?cè)萘?

? ? ? ?(以第一句為例)最后的?0.0187288 secs 是本次GC的時(shí)長(zhǎng)菠红。至于后面還有個(gè)?[Times: user=0.00 sys=0.00, real=0.02 secs]只有部分垃圾收集器會(huì)給出這樣的更詳細(xì)的時(shí)間第岖,分別是用戶態(tài)消耗的CPU時(shí)間、內(nèi)核態(tài)消耗的CPU時(shí)間以及整個(gè)過程的墻鐘時(shí)間(即不包含非運(yùn)算時(shí)間试溯,例如不包含IO操作耗時(shí))蔑滓,這些概念了解即可,有興趣的可以自查遇绞。

? ? ? ? 了解完日志內(nèi)容含義后键袱,就可以從PSYoungGen: 8846K->712K(38400K)這一句分析出,我們創(chuàng)建的兩個(gè)對(duì)象都在新生代摹闽,且在GC后它們都被回收了蹄咖,這就反證出JVM并未使用引用計(jì)數(shù)法。

? ? ? ? 事實(shí)上付鹿,在主流的商用程序中(Java澜汤,C#),都是使用可達(dá)性分析算法來判定對(duì)象是否存活舵匾。?而引用計(jì)數(shù)法優(yōu)點(diǎn)在于實(shí)現(xiàn)簡(jiǎn)單俊抵,效率也很高,它的使用場(chǎng)景主要在微軟的COM技術(shù)坐梯、使用ActionScript3的FlashPlayer徽诲、Python等等,了解即可吵血。

三馏段、垃圾收集算法

? ? 上面我們已經(jīng)知道JVM采用可達(dá)性分析算法來標(biāo)記一個(gè)對(duì)象是否可以被回收,那么具體如何回收這些被標(biāo)記的對(duì)象呢践瓷,有以下幾種算法(以下都是算法理論院喜,并不是具體實(shí)現(xiàn)):

? ? 3.1 標(biāo)記-清除算法

? ? ? ? 這是最基礎(chǔ)粗暴的一種算法,原理也很簡(jiǎn)單晕翠,當(dāng)堆中的有效內(nèi)存空間被耗盡的時(shí)候喷舀,就會(huì)停止整個(gè)程序砍濒,然后進(jìn)行兩步工作,第一步先把所有的可回收對(duì)象利用上述可達(dá)性分析算法進(jìn)行標(biāo)記硫麻,第二項(xiàng)就是統(tǒng)一回收被標(biāo)記的對(duì)象(這里請(qǐng)注意爸邢,一個(gè)對(duì)象在該過程中只有兩種狀態(tài),一個(gè)是存活拿愧,一個(gè)是可回收杠河,假設(shè)這個(gè)標(biāo)志位為0/1,初始值都為0浇辜,因此這里所說的標(biāo)記可回收對(duì)象的意思可以理解為沿著GC Roots的引用鏈把存活的對(duì)象都標(biāo)記為1券敌,那么相當(dāng)于把可回收對(duì)象標(biāo)記為0),如圖所示:

標(biāo)記-清除算法

? ? ? ? 從圖中可以看出該算法存在一個(gè)很直觀的不足之處:在回收之后柳洋,內(nèi)存中會(huì)存在大量的不連續(xù)內(nèi)存碎片待诅,這樣的碎片太多就會(huì)導(dǎo)致以后在程序運(yùn)行過程中需要為大對(duì)象分配空間時(shí),找不到連續(xù)內(nèi)存來存放熊镣,從而提前觸發(fā)下一次垃圾回收卑雁,甚至可能導(dǎo)致內(nèi)存溢出。

? ? ? ? 標(biāo)記清除算法的另一個(gè)不足之處就是它的效率不高绪囱,畢竟經(jīng)歷標(biāo)記和清除兩次遍歷(第一次遍歷標(biāo)記了存活對(duì)象测蹲,遍歷完成后,相當(dāng)于可回收對(duì)象也被標(biāo)記出來鬼吵,第二次遍歷找到這些可回收對(duì)象進(jìn)行清除)弛房,這兩個(gè)過程加起來會(huì)比較費(fèi)時(shí)。

? ? ? ? 但所有的教材都會(huì)把標(biāo)記清除算法放在講解GC算法的第一個(gè)位置來講而柑,就是因?yàn)樗亲罨A(chǔ)的算法,后面的GC算法都是在它基礎(chǔ)上做的優(yōu)化荷逞。

? ? 3.2 復(fù)制算法

? ??????復(fù)制算法將內(nèi)存等分為兩個(gè)區(qū)域媒咳,所有動(dòng)態(tài)分配的對(duì)象都只能分配在其中一個(gè)區(qū)域,而另外一個(gè)區(qū)域始終保持空閑狀態(tài)种远。

? ? ? ? 當(dāng)正在使用的一塊區(qū)域有效內(nèi)存空間耗盡時(shí)涩澡,GC線程會(huì)將該區(qū)域內(nèi)的存活對(duì)象,全部復(fù)制到另一個(gè)空閑區(qū)域坠敷,且嚴(yán)格按照內(nèi)存地址依次排列妙同,然后更新存活對(duì)象的內(nèi)存引用地址指向新的內(nèi)存地址。同時(shí)膝迎,將原區(qū)域中的內(nèi)存(只剩下了可回收對(duì)象)一次性全部回收粥帚,兩個(gè)區(qū)域就完成了互換。

復(fù)制算法

? ? ? ? 很明顯限次,復(fù)制算法效率比標(biāo)記清除算法要高很多芒涡,全部過程都可以在一次遍歷中完成柴灯,而且不會(huì)產(chǎn)生內(nèi)存碎片,新來的對(duì)象分配內(nèi)存也只需要移動(dòng)堆頂指針费尽,按順序存放即可赠群。

? ? ? ? 缺點(diǎn)也很明顯,那就是永遠(yuǎn)都要浪費(fèi)一半的內(nèi)存空間旱幼,假設(shè)極端條件下查描,內(nèi)存中的對(duì)象全部存活且觸發(fā)了GC,那么活著的對(duì)象集體進(jìn)行無用的復(fù)制轉(zhuǎn)移柏卤,原區(qū)域也沒有可回收的對(duì)象進(jìn)行回收冬三,新的對(duì)象還是沒有地方存放,白白浪費(fèi)了一半的空間闷旧,最后造成了內(nèi)存溢出长豁。由這個(gè)極端情況不難考慮出,復(fù)制算法對(duì)于對(duì)象存活率越高時(shí)忙灼,整體空間使用率和效率就會(huì)顯得越低匠襟。

? ? 3.3 標(biāo)記-整理算法

? ? ? ? 為了應(yīng)對(duì)上述復(fù)制算法的缺點(diǎn),標(biāo)記整理算法出現(xiàn)了该园。該算法標(biāo)記出所有存活的對(duì)象酸舍,然后讓所有存活對(duì)象向一端進(jìn)行移動(dòng),最后直接清理存活對(duì)象端邊界以外的內(nèi)存里初。

標(biāo)記整理算法

? ? ? ? 該算法也僅需一次遍歷啃勉,互補(bǔ)于復(fù)制算法,對(duì)象存活率越高時(shí)双妨,效率越高淮阐,試想一下對(duì)象存活率極高時(shí),存活對(duì)象總在內(nèi)存的一端刁品,觸發(fā)GC也不再需要去移動(dòng)它們泣特,反之,如果對(duì)象存活率很低挑随,每一次GC都需要不斷清理可回收對(duì)象状您,又將新的存活對(duì)象標(biāo)記移動(dòng)到存活的一端,那么效率就很低了兜挨。

? ? 3.4 分代收集算法

? ? ? ? 分代收集算法與其說是新的算法膏孟,不如說是上述算法的整合和再優(yōu)化。使用分代收集算法會(huì)把Java堆分為幾個(gè)區(qū)域拌汇,針對(duì)每個(gè)區(qū)域不同的對(duì)象存活情況使用不同的垃圾回收算法柒桑,以提高整體的效率。

分代收集算法對(duì)Java堆的分區(qū)

? ? ? ? 如圖所示噪舀,該算法總體把Java堆分為兩個(gè)區(qū)域:新生代(Young Generation)和老年代(Old Generation)幕垦,內(nèi)存空間占比1:2丢氢。

? ??????新生代

? ??????幾乎所有新產(chǎn)生的對(duì)象都會(huì)首先進(jìn)入新生代,這里的對(duì)象有一個(gè)特點(diǎn)先改,那就是“朝生夕死”疚察,換句話說就是存活率不高(大量方法的運(yùn)行中創(chuàng)建對(duì)象,方法運(yùn)行完仇奶,局部變量被回收斷開引用)貌嫡,因此這里采用復(fù)制算法,每次只有少量對(duì)象需要被復(fù)制遷移该溯,大量對(duì)象被直接清除岛抄,完美利用復(fù)制算法的機(jī)制,效率很高狈茉。

? ? ? ? 更具體得來說夫椭,新生代被分為伊甸區(qū)(Eden)和生存者區(qū)(Survivor),生存者區(qū)又等分成了From和To兩個(gè)區(qū)域氯庆,三個(gè)分區(qū)的內(nèi)存空間占比為8:1:1(并沒有機(jī)械得使用復(fù)制算法原生定義的兩區(qū)等分蹭秋,而是進(jìn)行了優(yōu)化,因?yàn)樾庐a(chǎn)生的對(duì)象總是很多堤撵,而GC完后存活的對(duì)象卻很少)仁讨,新生成的對(duì)象會(huì)進(jìn)入較大的Eden區(qū)颈畸,當(dāng)Eden區(qū)空間不足會(huì)進(jìn)入其中一個(gè)Survivor區(qū)瘦馍,另一個(gè)Survivor區(qū)就作為復(fù)制算法的空閑區(qū)域,當(dāng)觸發(fā)GC時(shí)茸歧,將Eden和一塊Survivor區(qū)域的所有存活對(duì)象復(fù)制到另一塊Survivor區(qū)域荒给,然后清理到剛存放對(duì)象的區(qū)域丈挟,如此循環(huán)。

????????雖然每次只會(huì)剩少量存活對(duì)象志电,但如果它們始終存在于新生代曙咽,新生代就將無法存放新對(duì)象,這是無法接受的溪北,因此JVM在使用分代收集分代時(shí)對(duì)對(duì)象進(jìn)行了“年齡”標(biāo)記,對(duì)象初始化“年齡”都為0夺脾,每次新生代GC后存活的對(duì)象年齡會(huì)+1之拨,達(dá)到一定數(shù)值后會(huì)被移動(dòng)至老年代(這個(gè)數(shù)值可以配置,通用默認(rèn)值是15咧叭,仍然看具體實(shí)現(xiàn))蚀乔。

? ? ? ? 老年代

  老年代存放的對(duì)象都是存活率較高的對(duì)象(例如緩存對(duì)象、數(shù)據(jù)庫連接對(duì)象菲茬、單例對(duì)象等等)吉挣,因此可以采用標(biāo)記-清除或者標(biāo)記-整理算法派撕,具體采用哪種根據(jù)使用的垃圾收集器來進(jìn)行判斷。

? ? ? ? 上面說幾乎所有對(duì)象都會(huì)首先被分配到伊甸區(qū)睬魂,但也有例外终吼,比如特別大的對(duì)象(例如很長(zhǎng)的String或很大的byte[])會(huì)直接進(jìn)入老年代,因?yàn)樗枰艽蟮倪B續(xù)存儲(chǔ)空間氯哮,這個(gè)閾值也可以使用虛擬機(jī)參數(shù)-XX:PretenureSizeThreshold進(jìn)行配置际跪,內(nèi)存大于配置數(shù)的對(duì)象就會(huì)直接進(jìn)入老年代,默認(rèn)值為3MB喉钢。

四姆打、何時(shí)觸發(fā)垃圾回收

? ? ?JVM在進(jìn)行GC時(shí),并非每次都對(duì)所有內(nèi)存區(qū)域一起回收的肠虽,大部分時(shí)候回收的都是指新生代幔戏。因此GC按照回收的區(qū)域又分了兩種類型,一種是普通GC税课,一種是全局GC闲延,它們所針對(duì)的區(qū)域如下:

? ??普通GC(Minor GC):只針對(duì)新生代區(qū)域的GC。

? ??全局GC(Major GC or Full GC):主要針對(duì)老年代的GC伯复,偶爾伴隨對(duì)新生代的GC以及對(duì)永久代的GC(僅JDK1.8以前慨代,現(xiàn)在已經(jīng)由元空間代替永久代實(shí)現(xiàn)方法區(qū),元空間有自己的內(nèi)存管理啸如,并不需要JVM的GC干預(yù))侍匙,由于老年代相對(duì)來說GC效果不好,而且內(nèi)存使用增長(zhǎng)速度也慢叮雳,因此正常情況下想暗,需要經(jīng)過好幾次普通GC,才會(huì)觸發(fā)一次全局GC帘不。

? ??第一種觸發(fā)GC的方式就是虛擬機(jī)會(huì)自行根據(jù)當(dāng)前內(nèi)存大小说莫,判斷何時(shí)進(jìn)行垃圾回收。即當(dāng)新生對(duì)象進(jìn)入伊甸區(qū)無法申請(qǐng)到足夠內(nèi)存時(shí)就會(huì)觸發(fā)普通GC寞焙,而當(dāng)大對(duì)象或新生代里年齡達(dá)到閾值的對(duì)象無法申請(qǐng)到足夠的老年代內(nèi)存時(shí)將會(huì)觸發(fā)全局GC储狭。

? ??第二種是程序員手動(dòng)調(diào)用 System.gc()方法通知JVM進(jìn)行垃圾回收,且這種方式觸發(fā)的是全局GC捣郊,只是它不能保證垃圾回收一定會(huì)進(jìn)行辽狈,具體什么時(shí)候進(jìn)行是取決于具體的虛擬機(jī)的,不同的虛擬機(jī)有不同的對(duì)策呛牲。

五刮萌、總結(jié)

? ? JVM(Sun HotSpot )采用可達(dá)性分析算法判斷一個(gè)對(duì)象是否可回收,并使用分代收集算法進(jìn)行垃圾回收娘扩,只是Java虛擬機(jī)規(guī)范對(duì)垃圾收集器應(yīng)該如何實(shí)現(xiàn)分代收集算法着茸,并沒有任何的規(guī)定壮锻,所以不同的廠商、不同版本的虛擬機(jī)所提供的垃圾收集器都會(huì)有所不同涮阔,并且一般都會(huì)提供參數(shù)供用戶根據(jù)自己的應(yīng)用特點(diǎn)和要求組合出各個(gè)分年區(qū)域所使用的收集器猜绣。

? ? 另外,如果我們的程序頻繁觸發(fā)全局GC澎语,將會(huì)直接影響程序執(zhí)行效率途事,因此在對(duì)JVM調(diào)優(yōu)的過程中,很大一部分工作就是對(duì)于全局GC的調(diào)節(jié),下一章節(jié)我們將一起學(xué)習(xí)JVM調(diào)優(yōu)擅羞。

《淺談Java虛擬機(jī)(一)—什么是Java虛擬機(jī)》

《淺談Java虛擬機(jī)(二)—運(yùn)行時(shí)數(shù)據(jù)區(qū)域》

《淺談Java虛擬機(jī)(三)—垃圾回收》

《淺談Java虛擬機(jī)(四)—JVM調(diào)優(yōu)》


本系列文章參考文檔:《深入理解Java虛擬機(jī):JVM高級(jí)特性與最佳實(shí)踐》--?周志明

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末尸变,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子减俏,更是在濱河造成了極大的恐慌召烂,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,039評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件娃承,死亡現(xiàn)場(chǎng)離奇詭異奏夫,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)历筝,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,426評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門酗昼,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人梳猪,你說我怎么就攤上這事麻削。” “怎么了春弥?”我有些...
    開封第一講書人閱讀 165,417評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵呛哟,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我匿沛,道長(zhǎng)扫责,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,868評(píng)論 1 295
  • 正文 為了忘掉前任逃呼,我火速辦了婚禮鳖孤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘抡笼。我一直安慰自己苏揣,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,892評(píng)論 6 392
  • 文/花漫 我一把揭開白布蔫缸。 她就那樣靜靜地躺著腿准,像睡著了一般际起。 火紅的嫁衣襯著肌膚如雪拾碌。 梳的紋絲不亂的頭發(fā)上吐葱,一...
    開封第一講書人閱讀 51,692評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音校翔,去河邊找鬼弟跑。 笑死,一個(gè)胖子當(dāng)著我的面吹牛防症,可吹牛的內(nèi)容都是我干的孟辑。 我是一名探鬼主播,決...
    沈念sama閱讀 40,416評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼蔫敲,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼饲嗽!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起奈嘿,我...
    開封第一講書人閱讀 39,326評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤貌虾,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后裙犹,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體尽狠,經(jīng)...
    沈念sama閱讀 45,782評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,957評(píng)論 3 337
  • 正文 我和宋清朗相戀三年叶圃,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了袄膏。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,102評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡掺冠,死狀恐怖沉馆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赫舒,我是刑警寧澤悍及,帶...
    沈念sama閱讀 35,790評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站接癌,受9級(jí)特大地震影響心赶,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜缺猛,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,442評(píng)論 3 331
  • 文/蒙蒙 一缨叫、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧荔燎,春花似錦耻姥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,996評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春婉商,著一層夾襖步出監(jiān)牢的瞬間似忧,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,113評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工丈秩, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留盯捌,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,332評(píng)論 3 373
  • 正文 我出身青樓蘑秽,卻偏偏與公主長(zhǎng)得像饺著,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子肠牲,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,044評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容