一篇帶你入門Java垃圾回收器
第一階段:串行垃圾回收器:jdk1.3.1之前Java虛擬機(jī)僅僅只支持Serial收集器
第二階段:并行垃圾回收器:隨著多核的出現(xiàn),Java引入了并行垃圾回收器,充分利用多核性能提升垃圾回收效率
第三階段:并發(fā)標(biāo)記清理回收器CMS:垃圾回收器可以和應(yīng)用程序同時(shí)運(yùn)行,降低暫停用戶線程執(zhí)行的時(shí)間
第四階段:G1(并發(fā))回收器:初衷是在清理非常大的堆空間的時(shí)候能滿足特定的暫停應(yīng)用程序的時(shí)間,與CMS相比會(huì)有更少的內(nèi)存碎片
1 垃圾回收算法1-1 標(biāo)記清除算法算法概述優(yōu)點(diǎn):回收速度快
缺點(diǎn):造成內(nèi)存碎片,無法分配大的連續(xù)空間。
算法思想在Java9之前,Java默認(rèn)使用的垃圾回收器是ParallelGC,從Java9開始G1作為了默認(rèn)的垃圾回收器
第一個(gè)依舊是標(biāo)記,第二步會(huì)進(jìn)行一個(gè)空間整理,從而不產(chǎn)生碎片。
優(yōu)點(diǎn):避免了內(nèi)存碎片
缺點(diǎn):對(duì)空間的整理使得效率比較低下。
特點(diǎn):
將管理的內(nèi)存分為2塊區(qū)域,from區(qū)域與to區(qū)域,將那些不需要回收的對(duì)象從from區(qū)域拷貝到to區(qū)域。復(fù)制的過程中完成內(nèi)存區(qū)域的整理。之后交換from和to的指向。
優(yōu)點(diǎn):不會(huì)產(chǎn)生內(nèi)存碎片
缺點(diǎn):需要雙倍的內(nèi)存空間,內(nèi)存利用率不高,而且拷貝也需要時(shí)間。
1-4 三種垃圾回收算法總結(jié)
垃圾回收算法 優(yōu)點(diǎn) 缺點(diǎn) 標(biāo)記清除算法(Mark Sweep) 速度較快 產(chǎn)生內(nèi)存碎片 標(biāo)記整理算法(Mark Compact) 沒有內(nèi)存碎片 速度慢 復(fù)制算法(Copy) 沒有內(nèi)存碎片 需要占用雙倍內(nèi)存空間注意:實(shí)際的JVM垃圾回收算法中上面的三種算法是綜合使用的。
2 JVM分代回收算法2-1 概述Garden of Eden:伊甸園 garbage:垃圾
新生代主要由三部分內(nèi)容組成,分別是Eden區(qū),幸存區(qū)from,幸存區(qū)to。 通常情況下只有Eden區(qū)與幸存區(qū)from會(huì)存放數(shù)目,幸存區(qū)to只有垃圾回收時(shí),復(fù)制對(duì)象會(huì)用到。堆內(nèi)存的新生代進(jìn)行一次垃圾回收(Minor GC),大部分對(duì)象都會(huì)都會(huì)被回收。
老年代通常存放一些經(jīng)常被使用的對(duì)象,一個(gè)對(duì)象如果經(jīng)歷多次垃圾回收仍然幸存,那么該對(duì)象會(huì)從新生代放入老年代。只有新生代內(nèi)存不足并且老年代內(nèi)存也不足的時(shí)候才會(huì)觸發(fā)full GC對(duì)老年代的對(duì)象進(jìn)行垃圾回收。
為什么需要進(jìn)行劃分?
實(shí)際環(huán)境中,對(duì)象的生命周期是不同的,老年代的對(duì)象生命周期比較長,可能很長時(shí)間才進(jìn)行一次垃圾回收。新生代的對(duì)象生命周期比較短,垃圾回收比較頻繁。這種分區(qū)法方便采用不同的垃圾回收算法更加有效的進(jìn)行垃圾回收。
2-2 分代垃圾回收示例step1:程序剛剛開始運(yùn)行,產(chǎn)生的對(duì)象先放入Eden區(qū),當(dāng)Eden區(qū)放不下的時(shí)候。
step2:對(duì)Eden區(qū)進(jìn)行Minor GC,并將沒有被垃圾回收的對(duì)象復(fù)制的幸存區(qū)To,然后交換幸存區(qū)To和幸存區(qū)From,第一次垃圾回收的最終的效果如下圖所示:
step3: 第一次Minor GC, Eden區(qū)又有空間可以分配給新的對(duì)象使用,經(jīng)過一段時(shí)間Eden又不夠用了,觸發(fā)第二次Minor GC, 這次垃圾會(huì)檢查Eden區(qū)以及幸存區(qū)From哪些對(duì)象可以存活,并將這些對(duì)象復(fù)制到幸存區(qū)To,然后交換幸存區(qū)To和幸存區(qū)From,這個(gè)時(shí)候Eden區(qū)又空了出來,可以放置新的對(duì)象。
實(shí)際垃圾回收過程中,JVM會(huì)對(duì)每個(gè)對(duì)象經(jīng)過垃圾回收幸存下來的次數(shù)進(jìn)行記錄,比如上圖中,幸存區(qū)的2個(gè)對(duì)象經(jīng)過垃圾回收的次數(shù)分別是1和2。
step4: 當(dāng)一些對(duì)象經(jīng)過垃圾回收的次數(shù)仍然幸存的次數(shù)達(dá)到一個(gè)閾值(說明這個(gè)對(duì)象價(jià)值比較高),那么這個(gè)對(duì)象會(huì)被移動(dòng)到老年代。
極端情況考慮:Eden區(qū),from區(qū),老年區(qū)都已經(jīng)滿了?
此時(shí)會(huì)觸發(fā)Full GC(優(yōu)先Minor GC,Minor GC依舊內(nèi)存不夠)
2-3 分代垃圾回收的總結(jié)對(duì)象首先分配在伊甸園區(qū)域新生代空間不足時(shí),觸發(fā) minor gc,伊甸園和 from 存活的對(duì)象使用 copy復(fù)制到 to 中,存活的對(duì)象年齡加 1并且交換from to
minor gc 會(huì)引發(fā)stop the world,暫停其它用戶的線程,等垃圾回收結(jié)束,用戶線程才恢復(fù)運(yùn)行暫停時(shí)間較短,由于新生代大部分對(duì)象都是垃圾,復(fù)制的對(duì)象很少,所以效率較高
當(dāng)對(duì)象壽命超過閾值時(shí),會(huì)晉升至老年代,最大壽命是15(4bit,對(duì)象頭存儲(chǔ))
當(dāng)老年代空間不足,會(huì)先嘗試觸發(fā) minor gc,如果之后空間仍不足,那么觸發(fā)full gc,STW的時(shí)間更長
Full GC 的stop the world的時(shí)間要比MInor GC時(shí)間長,老年代存活對(duì)象較多加上空間整理時(shí)間,所以停止時(shí)間會(huì)較長。如果Full GC后,空間仍然不足會(huì)觸發(fā)內(nèi)存不足的異常。
2-4 垃圾回收相關(guān)的虛擬機(jī)參數(shù)
垃圾回收器概述
參數(shù)含義 參數(shù) 備注 堆初始大小 -Xms堆最大大小 -Xmx 或 -XX:MaxHeapSize=size新生代大小 -Xmn 或 (-XX:NewSize=size + -XX:MaxNewSize=size ) NewSize是初始大小,MaxNewSize是最大大小。 幸存區(qū)比例(動(dòng)態(tài)) -XX:InitialSurvivorRatio=ratio 和 -XX:+UseAdaptiveSizePolicy 幸存區(qū)的比例,默認(rèn)是8,假設(shè)新生代10M內(nèi)存,8M劃分給Eden區(qū),剩下的二等分,一份from,一份to。 幸存區(qū)比例 -XX:SurvivorRatio=ratio 動(dòng)態(tài)調(diào)整幸存區(qū)比例 晉升閾值 -XX:MaxTenuringThreshold=threshold 用于動(dòng)態(tài)調(diào)整幸存區(qū)比例 晉升詳情 -XX:+PrintTenuringDistribution 用于動(dòng)態(tài)調(diào)整幸存區(qū)比例 GC詳情 -XX:+PrintGCDetails -verbose:gc 打印詳情信息 FullGC 前 MinorGC -XX:+ScavengeBeforeFullGC 默認(rèn)在Full GC 前進(jìn)行一次Minor GC 2-5 垃圾回收案例分析情況1:什么都不放的情況
new generation:新生代 tenured generation:老年代
package cn.itcast.jvm.t2;import java.util.ArrayList;/** * 演示內(nèi)存的分配策略 */public class Demo2_1 { private static final int _512KB = 512 * 1024; private static final int _1MB = 1024 * 1024; private static final int _6MB = 6 * 1024 * 1024; private static final int _7MB = 7 * 1024 * 1024; private static final int _8MB = 8 * 1024 * 1024;//加入Java開發(fā)交流君樣:756584822一起吹水聊天 // -Xms20M -Xmx20M -Xmn10M : 堆初始與最大大小都是20M,新生代的大小為10M. // -XX:+UseSerialGC : 為了學(xué)習(xí)方便,采用這個(gè)垃圾回收器,默認(rèn)的垃圾回收器并不是這個(gè)。 // -XX:+PrintGCDetails -verbose:gc :打印詳細(xì)信息 // -XX:-ScavengeBeforeFullGC :在Full GC 前進(jìn)行 Minor GC. public static void main(String[] args) throws InterruptedException {new Thread(() -> { ArrayList<byte[]> list = new ArrayList<>(); list.add(new byte[_8MB]); list.add(new byte[_8MB]);}).start();System.out.println('sleep....');Thread.sleep(1000L); }}
情況1執(zhí)行結(jié)果
可以看到即使用戶沒有創(chuàng)建對(duì)象,系統(tǒng)對(duì)象也要占據(jù)一部分堆內(nèi)存空間。
Heap def new generation total 9216K, used 2341K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)// 新生代的空間總的大小為9216K,這里沒有把To空間給計(jì)算進(jìn)去,系統(tǒng)任務(wù)To的空間是分配是不可用的,所以不是10M,已經(jīng)使用了2341K,[]內(nèi)部則是內(nèi)存地址范圍。eden space 8192K, 28% used [0x00000000fec00000, 0x00000000fee49420, 0x00000000ff400000)//from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)// 老年代大小為10M,可以看到?jīng)]有任何空間使用 //加入Java開發(fā)交流君樣:756584822一起吹水聊天 the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3394K, capacity 4496K, committed 4864K, reserved 1056768K class space used 378K, capacity 388K, committed 512K, reserved 1048576K
Java的內(nèi)存對(duì)象都是分配在堆上嗎
情況2:新生代堆空間放滿,觸發(fā)GC
public static void main(String[] args) throws InterruptedException {ArrayList<byte[]> list = new ArrayList<>();list.add(new byte[_7MB]); // 系統(tǒng)類占用2341K,加上new的7MB觸發(fā)垃圾回收 }
情況2執(zhí)行結(jié)果
[GC (Allocation Failure) [DefNew: 2342K->696K(9216K), 0.0029193 secs] 2342K->696K(19456K), 0.0029867 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] // GC:minor GC(新生代垃圾回收) FUll GC (老年代垃圾回收)// [Times: user=0.00 sys=0.00, real=0.00 secs] 垃圾回收?qǐng)?zhí)行時(shí)間Heap def new generation total 9216K, used 8110K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 90% used [0x00000000fec00000, 0x00000000ff33d8c0, 0x00000000ff400000) from space 1024K, 67% used [0x00000000ff500000, 0x00000000ff5ae100, 0x00000000ff600000) to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) tenured generation total 10240K, used 0K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 0% used [0x00000000ff600000, 0x00000000ff600000, 0x00000000ff600200, 0x0000000100000000) Metaspace used 3539K, capacity 4536K, committed 4864K, reserved 1056768K //加入Java開發(fā)交流君樣:756584822一起吹水聊天 class space used 395K, capacity 428K, committed 512K, reserved 1048576K
情況三: 新生代內(nèi)存隨著對(duì)象的增多放不下了
public static void main(String[] args) throws InterruptedException {ArrayList<byte[]> list = new ArrayList<>();list.add(new byte[_7MB]);list.add(new byte[_512KB]);list.add(new byte[_512KB]); }
執(zhí)行結(jié)果
新生代放不下,將新生代的對(duì)象放置到老年代。
[GC (Allocation Failure) [DefNew: 2342K->670K(9216K), 0.0022591 secs] 2342K->670K(19456K), 0.0023131 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] [GC (Allocation Failure) [DefNew: 8678K->538K(9216K), 0.0061246 secs] 8678K->8354K(19456K), 0.0061637 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] Heap def new generation total 9216K, used 1132K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 7% used [0x00000000fec00000, 0x00000000fec94930, 0x00000000ff400000) from space 1024K, 52% used [0x00000000ff400000, 0x00000000ff486a00, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 7815K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 76% used [0x00000000ff600000, 0x00000000ffda1f80, 0x00000000ffda2000, 0x0000000100000000) Metaspace used 3539K, capacity 4536K, committed 4864K, reserved 1056768K //加入Java開發(fā)交流君樣:756584822一起吹水聊天 class space used 395K, capacity 428K, committed 512K, reserveed 1048576K
情況四:一開始直接分配大于新生代的內(nèi)存,如果老年代放的下,則直接放到老年代
public static void main(String[] args) throws InterruptedException {ArrayList<byte[]> list = new ArrayList<>();list.add(new byte[_8MB]); }
執(zhí)行結(jié)果
Heap def new generation total 9216K, used 2507K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) eden space 8192K, 30% used [0x00000000fec00000, 0x00000000fee72ca8, 0x00000000ff400000) from space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) to space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) tenured generation total 10240K, used 8192K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) the space 10240K, 80% used [0x00000000ff600000, 0x00000000ffe00010, 0x00000000ffe00200, 0x0000000100000000) Metaspace used 3539K, capacity 4536K, committed 4864K, reserved 1056768K class space used 395K, capacity 428K, committed 512K, reserved 1048576K
小結(jié)
當(dāng)內(nèi)存比較緊張的時(shí)候,即新生代內(nèi)存放不下的時(shí)候,有時(shí)候會(huì)直接將對(duì)象分配到老年代,或者直接在回收次數(shù)較少(未達(dá)到15次)的情況下,直接將新生代對(duì)象弄到老年代。【資料獲取】
2 垃圾回收器2-1 垃圾回收器概述名稱 特點(diǎn) 適合場(chǎng)景 目標(biāo) 新生代 老年代 串行垃圾回收器 單線程 堆內(nèi)存小,適合個(gè)人電腦(cpu個(gè)數(shù))采用復(fù)制的垃圾回收算法 采用標(biāo)記+整理的垃圾回收算法 吞吐量?jī)?yōu)先垃圾回收器 多線程 堆內(nèi)存大,多核CPU 并行,讓單位時(shí)間內(nèi)STW的時(shí)間最短 復(fù)制算法 標(biāo)記+拷貝 響應(yīng)時(shí)間優(yōu)先的垃圾回收器(簡(jiǎn)稱CMS) 多線程 堆內(nèi)存大,多核CPU 并發(fā),盡可能讓單次STW最短 復(fù)制算法 標(biāo)記清除算法產(chǎn)生內(nèi)存碎片需要退化成單線程的垃圾整理回收器CMS垃圾回收器后來被G1垃圾回收器取代。
2-2 串行垃圾回收器開啟串行垃圾回收器的JVM參數(shù)
-XX:+UseSerialGC = Serial + SerialOld// Serial:工作在新生代,采用復(fù)制的垃圾回收算法// SerialOld:工作在老生代,采用標(biāo)記+整理的垃圾回收算法
總結(jié):觸發(fā)垃圾回收時(shí),讓多個(gè)線程在一個(gè)安全點(diǎn)停下來,然后使用單線程的垃圾回收器去進(jìn)行垃圾回收,垃圾回收完成后,再讓其他線程運(yùn)行。
2-3 吞吐量?jī)?yōu)先的垃圾回收器開啟吞吐量?jī)?yōu)先的垃圾回收器的JVM參數(shù)
開啟/關(guān)閉的參數(shù)
默認(rèn)的多線程垃圾回收器,前者是開啟新生代回收器,采用復(fù)制算法,后者是開啟老年代回收器,采用標(biāo)記+拷貝算法。下面選項(xiàng)只要開啟一個(gè),那么另外一個(gè)也會(huì)開啟。
-XX:+UseParallelGC , -XX:+UseParallelOldGC
開啟自適應(yīng)動(dòng)態(tài)調(diào)整新生代的大小,晉升閾值
-XX:+UseAdaptiveSizePolicy
二個(gè)指標(biāo)調(diào)整的參數(shù)(ParallelGC會(huì)根據(jù)設(shè)定的指標(biāo)去調(diào)整堆的大小到達(dá)下面期望設(shè)定的目標(biāo))指標(biāo)1)1/(1+ratio) = 垃圾回收的時(shí)間/總的運(yùn)行時(shí)間
ratio默認(rèn)值時(shí)99,即垃圾回收的時(shí)間不超過總時(shí)間1%。但一般設(shè)為19。【資料獲取】如果達(dá)不到目標(biāo),ParallelGC會(huì)調(diào)整堆內(nèi)存大小來達(dá)到這個(gè)目標(biāo),通常是調(diào)大,這樣垃圾回收的次數(shù)會(huì)減少,從而提高吞吐量
-XX:GCTimeRatio=ratio
指標(biāo)2)每次垃圾回收的時(shí)間限制( 最大暫停的毫秒數(shù))
默認(rèn)值是200ms顯然將堆內(nèi)存空間變小有助于減少每次垃圾回收的時(shí)間
-XX:MaxGCPauseMillis=ms
總結(jié):顯然指標(biāo)1)與指標(biāo)2)是有沖突的。
-XX:ParallelGCThreads=n //垃圾回收并行的線程數(shù)目
小結(jié):
采用多線程方式進(jìn)行垃圾回收,垃圾回收的線程數(shù)目通常根據(jù)CPU的核數(shù)進(jìn)行設(shè)置。在垃圾回收階段,并行的垃圾回收線程會(huì)充分占用CPU。在非垃圾回收階段,用戶線程會(huì)充分利用CPU資源。
2-4 響應(yīng)時(shí)間優(yōu)先的垃圾回收器(CMS垃圾回收器)缺點(diǎn):采用的標(biāo)記清除算法產(chǎn)生內(nèi)存碎片需要退化成單線程的垃圾整理回收器,造成響應(yīng)時(shí)間變長。
開啟的JVM參數(shù)
注意這個(gè)是并發(fā)的采用標(biāo)記清除算法的垃圾回收,這里區(qū)別于之前的垃圾回收器,該垃圾回收器能夠在進(jìn)行垃圾回收的同時(shí)運(yùn)行其他非垃圾回收線程(也存在時(shí)間階段需要停止,但不是所有階段停止)。
老年代并發(fā)的垃圾回收器會(huì)出現(xiàn)失敗的情況,這時(shí)老年代垃圾回收器會(huì)退化成單線程的垃圾回收器(SerialOld)
-XX:+UseConcMarkSweepGC // use concurrent mark sweep(會(huì)產(chǎn)生垃圾碎片) 工作在老年代的垃圾回收器-XX:+UseParNewGC// 工作在新生代的垃圾回收器
重要的初始參數(shù)
-XX:ParallelGCThreads=n// 并行的垃圾回收線程數(shù),通常等于CPU的核心數(shù)(垃圾回收并行階段)-XX:ConcGCThreads=threads // 并發(fā)的線程數(shù)目,通常設(shè)為并行垃圾回收線程數(shù)的1/4(垃圾回收并發(fā)階段)
其他參數(shù)
-XX:CMSInitiatingOccupancyFraction=percent // 執(zhí)行垃圾回收的內(nèi)存占比,預(yù)留空間給浮動(dòng)垃圾-XX:+CMSScavengeBeforeRemark// 在重新標(biāo)記前,對(duì)新生代進(jìn)行垃圾回收,減少并發(fā)清理的垃圾對(duì)象,+開啟,-關(guān)閉
浮動(dòng)垃圾是指并發(fā)清理過程中用戶線程新產(chǎn)生的垃圾,需要等待下次并發(fā)清理。
并發(fā)工作流程概述:
step1:老年代發(fā)生內(nèi)存不存的現(xiàn)象。 step2:ConcMarkSweepGC會(huì)進(jìn)行一個(gè)初始標(biāo)記動(dòng)作(初始標(biāo)記需要STW即阻塞非垃圾回收線程),初始標(biāo)記只標(biāo)記根對(duì)象,所以速度非常快,暫停時(shí)間也非常短。 step3:完成初始標(biāo)記后,之前阻塞的線程又可以運(yùn)行了,這個(gè)時(shí)候垃圾回收線程進(jìn)行并發(fā)標(biāo)記。 step4:并發(fā)標(biāo)記結(jié)束后,需要再次阻塞非垃圾回收線程,進(jìn)行一個(gè)所謂的重新標(biāo)記, step5:重新標(biāo)記完成后,阻塞的線程又可以運(yùn)行了。垃圾回收線程也并發(fā)的清理垃圾對(duì)象。總結(jié):初始標(biāo)記與重新標(biāo)記需要阻塞線程。 在并發(fā)階段,由于垃圾回收線程占用資源,所以系統(tǒng)的吞吐量會(huì)受到一定的影響,但是系統(tǒng)的響應(yīng)速度由于并發(fā)執(zhí)行不會(huì)受到垃圾回收的明顯影響(相比較其他垃圾回收器,STW時(shí)間只需要進(jìn)行初始標(biāo)記與重新標(biāo)記,并且能夠不阻塞其他線程進(jìn)行垃圾的標(biāo)記與清除)。
最后,祝大家早日學(xué)有所成,拿到滿意offer,快速升職加薪,也請(qǐng)多多關(guān)注好吧啦網(wǎng)的其他文章!
相關(guān)文章:
1. React+umi+typeScript創(chuàng)建項(xiàng)目的過程2. ASP.NET Core 5.0中的Host.CreateDefaultBuilder執(zhí)行過程解析3. 三個(gè)不常見的 HTML5 實(shí)用新特性簡(jiǎn)介4. SharePoint Server 2019新特性介紹5. ASP中常用的22個(gè)FSO文件操作函數(shù)整理6. 解決ASP中http狀態(tài)跳轉(zhuǎn)返回錯(cuò)誤頁的問題7. ASP調(diào)用WebService轉(zhuǎn)化成JSON數(shù)據(jù),附j(luò)son.min.asp8. .Net core 的熱插拔機(jī)制的深入探索及卸載問題求救指南9. 無線標(biāo)記語言(WML)基礎(chǔ)之WMLScript 基礎(chǔ)第1/2頁10. 讀大數(shù)據(jù)量的XML文件的讀取問題
