垃圾回收
早期Python虛擬機(jī)采用
(資料圖片)
當(dāng)對(duì)象被引用時(shí),引用計(jì)數(shù)器++,取消引用時(shí)--
無(wú)法解決問(wèn)題:相互循環(huán)引用
通過(guò)一系列成為GC Roots的根對(duì)象作為起始節(jié)點(diǎn)集,從GCRoots開(kāi)始向下搜索,搜索的路徑為”引用鏈“,如果沒(méi)有引用鏈,也就是不可達(dá),說(shuō)明對(duì)象不可能再被使用
在Java技術(shù)體系中,固定可作為GCRoots的對(duì)象包括:
在JVMStack中引用的對(duì)象在方法區(qū)中類(lèi)靜態(tài)屬性引用的變量在方法區(qū)中常量引用的對(duì)象在本地方法棧中JNI引用的對(duì)象JVM內(nèi)部引用,基本數(shù)據(jù)類(lèi)型對(duì)應(yīng)的Class對(duì)象、常駐異常對(duì)象、系統(tǒng)類(lèi)加載器所有被同步鎖所持有的對(duì)象反應(yīng)JVM內(nèi)部情況的JMXBean、JVMTI中注冊(cè)的回調(diào)、本地代碼緩存等根據(jù)所選擇的垃圾收集器、當(dāng)前回收區(qū)域,采用其他對(duì)象臨時(shí)性的加入jmap -dump:format=b,live,file=11.bin
強(qiáng)、軟(SoftReference)、弱(WeakReference)
軟引用:
必須配合引用隊(duì)列:虛(PPhantomRefernce)、終結(jié)(FinalReference)
終結(jié):使用finallize方法
利用可達(dá)性算法,不可達(dá)進(jìn)行第一次標(biāo)記。
標(biāo)記后進(jìn)行篩選:是否有必要執(zhí)行finalize()方法,如果沒(méi)有重寫(xiě)或者已經(jīng)被調(diào)用過(guò),那么就視為沒(méi)有必要執(zhí)行
如果有必要執(zhí)行,那么會(huì)放入一個(gè)F-Queued的隊(duì)列(引用隊(duì)列)中,并在稍后由一條由虛擬機(jī)自動(dòng)建立的,低調(diào)度優(yōu)先級(jí)的Finalizer線程去執(zhí)行他們的finalize()方法。
為了防止出現(xiàn)阻塞,F(xiàn)-Queue不會(huì)等待方法執(zhí)行結(jié)束,否則會(huì)讓其他對(duì)象永久處于等待,造成內(nèi)存回收子系統(tǒng) 崩潰。
如果finalize()成功執(zhí)行,重新于引用鏈上任一對(duì)象建立關(guān)系即可:比如把this賦值給某個(gè)類(lèi)變量或者對(duì)象的成員變量,那么在第二次標(biāo)記時(shí),他將會(huì)被移出”即將回收“的集合。如果此時(shí)對(duì)象仍然沒(méi)有逃脫,那么就要被回收了。
Systen.gc()還是系統(tǒng)自動(dòng)回收,都只會(huì)調(diào)用一次finalize()方法
主要回收兩個(gè)內(nèi)容:廢棄常量和不再使用類(lèi)型。
判斷一個(gè)常量是否廢棄相對(duì)簡(jiǎn)單:該常量曾經(jīng)進(jìn)入常量池中,但現(xiàn)在又沒(méi)有一個(gè)對(duì)象的值和該常量相同,也就是說(shuō)沒(méi)有對(duì)象引用該常量,且虛擬機(jī)中也沒(méi)有其他地方引用該字面量,則該常量可以被垃圾回收,清理出常量池。
類(lèi)、方法、字段的符號(hào)引用也類(lèi)似。
但是判斷一個(gè)類(lèi)是否廢棄需要滿(mǎn)足下面三個(gè)條件:
該類(lèi)的所有實(shí)例都已被回收(包括派生類(lèi))加載該類(lèi)的類(lèi)加載器已經(jīng)被回收,這個(gè)條件除非是經(jīng)過(guò)精心設(shè)計(jì)的可替換類(lèi)加載器的場(chǎng)景,如OSGi、JSP的重加載等,否則通常很難達(dá)成該類(lèi)對(duì)應(yīng)的java.lang.Class對(duì)象沒(méi)有在任何地方被引用,無(wú)法在任何地方通過(guò)u哦反射訪問(wèn)該類(lèi)的方法。對(duì)于是否被零星回收,HotSpot提供了 -Xnoclassgc 參數(shù)進(jìn)行控制,還可以使用 -XX:+TraceClassLoading、-XX:TraceClassUnLoading查看類(lèi)加載和寫(xiě)在信息(前兩個(gè)可以在Product版JVM中使用,最后一種需要FastDebug版的JVM支持)。
根據(jù)對(duì)象生命周期的不同特點(diǎn)分為新生代和老年代
新生代:處理不常用的對(duì)象
老年代:處理常用的對(duì)象
在新生代中,每次垃圾收集時(shí)都發(fā)現(xiàn)有大批對(duì)象死去,而每次回收后存活的少量對(duì)象將會(huì)逐步晉升到老年代釋放
新創(chuàng)建的對(duì)象選擇伊甸園空間
當(dāng)伊甸園空間滿(mǎn)了以后會(huì)進(jìn)行垃圾護(hù)手,新生代的垃圾回收叫做Minor GC,回收后將存活對(duì)象賦值到幸存區(qū),并將壽命+1
再將to和form交換指向位置
在第一次的回收基礎(chǔ)上,對(duì)幸存區(qū)的某一個(gè)進(jìn)行取消引用
此時(shí)to中的1已經(jīng)被取消引用,所以直接刪除
最后from和to交換位置、放入新對(duì)象
當(dāng)年齡達(dá)到一定闕值后放入老年代
當(dāng)新生代內(nèi)存不夠時(shí),會(huì)嘗試促發(fā)FullGC進(jìn)行老年代的垃圾回收,準(zhǔn)確來(lái)說(shuō)是整個(gè)堆空間
-Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc
代碼:
package org.example;import java.io.IOException;public class Main { public static int a=5; public static void main(String[] args) throws InterruptedException { Thread.sleep(10000000000l); return ; }}
E:F:T=8:1:1,其中T默認(rèn)是已使用內(nèi)存
既然剛啟動(dòng),那為什么EdenSpace會(huì)有占用呢?
類(lèi)加載過(guò)程思考:當(dāng)new的對(duì)象占用內(nèi)存如果超過(guò)了幸存區(qū)內(nèi)存會(huì)發(fā)生什么??jī)?nèi)存溢出?動(dòng)態(tài)占比分配?試驗(yàn)代碼:package org.example; import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; public class Main { public static int a=5; public static void main(String[] args) throws InterruptedException, IOException { System.in.read(); ArrayList
bytes=new ArrayList<>(); System.out.println("添加對(duì)象中"); bytes.add(new byte1024*1024*5); bytes.forEach(System.out::println); System.in.read(); bytes.add(new byte1024*1024*5); bytes.forEach(System.out::println); System.in.read(); bytes.add(new byte1024*1024*5); bytes.forEach(System.out::println); bytes.add(new byte1024*1024*5); bytes.forEach(System.out::println); return ; } }結(jié)果:D:\Program\jdk\bin\java.exe -Xms20M -Xmx20M -Xmn10M -XX:+UseSerialGC -XX:+PrintGCDetails -verbose:gc "-javaagent:D:\Program\IntelliJ IDEA 2022.3.1\lib\idea_rt.jar=61656:D:\Program\IntelliJ IDEA 2022.3.1\bin" -Dfile.encoding=UTF-8 -classpath H:\JVM\gc\target\classes org.example.Main 0.003sgc -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead. 0.010sgc Using Serial 0.010sgc,init Version: 17.0.1+12-39 (release) 0.010sgc,init CPUs: 8 total, 8 available 0.010sgc,init Memory: 13810M 0.010sgc,init Large Page Support: Disabled 0.010sgc,init NUMA Support: Disabled 0.010sgc,init Compressed Oops: Enabled (32-bit) 0.010sgc,init Heap Min Capacity: 20M 0.010sgc,init Heap Initial Capacity: 20M 0.010sgc,init Heap Max Capacity: 20M 0.010sgc,init Pre-touch: Disabled 0.010sgc,metaspace CDS archive(s) mapped at: [0x0000000800000000-0x0000000800bc0000-0x0000000800bc0000), size 12320768, SharedBaseAddress: 0x0000000800000000, ArchiveRelocationMode: 0. 0.011sgc,metaspace Compressed class space mapped at: 0x0000000800c00000-0x0000000840c00000, reserved size: 1073741824 0.011sgc,metaspace Narrow klass base: 0x0000000800000000, Narrow klass shift: 0, Narrow klass range: 0x100000000 10.160sgc,start GC(0) Pause Young (Allocation Failure) 10.165sgc,heap GC(0) DefNew: 8192K(9216K)->1023K(9216K) Eden: 8192K(8192K)->0K(8192K) From: 0K(1024K)->1023K(1024K) 10.165sgc,heap GC(0) Tenured: 0K(10240K)->1443K(10240K) 10.165sgc,metaspace GC(0) Metaspace: 2679K(2880K)->2679K(2880K) NonClass: 2400K(2496K)->2400K(2496K) Class: 279K(384K)->279K(384K) 10.165sgc GC(0) Pause Young (Allocation Failure) 8M->2M(19M) 4.780ms 10.165sgc,cpu GC(0) User=0.00s Sys=0.02s Real=0.01s 10.384sgc,start GC(1) Pause Young (Allocation Failure) 10.390sgc,heap GC(1) DefNew: 9215K(9216K)->482K(9216K) Eden: 8192K(8192K)->0K(8192K) From: 1023K(1024K)->482K(1024K) 10.390sgc,heap GC(1) Tenured: 1443K(10240K)->2463K(10240K) 10.390sgc,metaspace GC(1) Metaspace: 4881K(5120K)->4881K(5120K) NonClass: 4357K(4480K)->4357K(4480K) Class: 523K(640K)->523K(640K) 10.390sgc GC(1) Pause Young (Allocation Failure) 10M->2M(19M) 5.666ms 10.390sgc,cpu GC(1) User=0.00s Sys=0.00s Real=0.01s 26.252sgc,start GC(2) Pause Full (System.gc()) 26.252sgc,phases,start GC(2) Phase 1: Mark live objects 26.257sgc,phases GC(2) Phase 1: Mark live objects 5.380ms 26.257sgc,phases,start GC(2) Phase 2: Compute new object addresses 26.259sgc,phases GC(2) Phase 2: Compute new object addresses 2.149ms 26.259sgc,phases,start GC(2) Phase 3: Adjust pointers 26.262sgc,phases GC(2) Phase 3: Adjust pointers 3.118ms 26.263sgc,phases,start GC(2) Phase 4: Move objects 26.263sgc,phases GC(2) Phase 4: Move objects 0.855ms 26.264sgc,heap GC(2) DefNew: 8083K(9216K)->0K(9216K) Eden: 7600K(8192K)->0K(8192K) From: 482K(1024K)->0K(1024K) 26.264sgc,heap GC(2) Tenured: 2463K(10240K)->3900K(10240K) 26.264sgc,metaspace GC(2) Metaspace: 7956K(8256K)->7956K(8256K) NonClass: 7083K(7232K)->7083K(7232K) Class: 872K(1024K)->872K(1024K) 26.264sgc GC(2) Pause Full (System.gc()) 10M->3M(19M) 12.256ms 26.264sgc,cpu GC(2) User=0.02s Sys=0.00s Real=0.01s 添加對(duì)象中 [B@3b07d329 49.675sgc,start GC(3) Pause Young (Allocation Failure) 49.678sgc,heap GC(3) DefNew: 8192K(9216K)->35K(9216K) Eden: 8192K(8192K)->0K(8192K) From: 0K(1024K)->35K(1024K) 49.678sgc,heap GC(3) Tenured: 3900K(10240K)->9020K(10240K) 49.678sgc,metaspace GC(3) Metaspace: 8137K(8384K)->8137K(8384K) NonClass: 7263K(7360K)->7263K(7360K) Class: 874K(1024K)->874K(1024K) 49.678sgc GC(3) Pause Young (Allocation Failure) 11M->8M(19M) 2.957ms 49.678sgc,cpu GC(3) User=0.00s Sys=0.02s Real=0.00s 96.458sgc,start GC(4) Pause Young (Allocation Failure) 96.458sgc GC(4) Pause Young (Allocation Failure) 14M->14M(19M) 0.078ms 96.458sgc,cpu GC(4) User=0.00s Sys=0.00s Real=0.00s 96.458sgc,start GC(5) Pause Full (Allocation Failure) 96.458sgc,phases,start GC(5) Phase 1: Mark live objects 96.467sgc,phases GC(5) Phase 1: Mark live objects 8.235ms 96.467sgc,phases,start GC(5) Phase 2: Compute new object addresses 96.469sgc,phases GC(5) Phase 2: Compute new object addresses 2.466ms 96.469sgc,phases,start GC(5) Phase 3: Adjust pointers 96.473sgc,phases GC(5) Phase 3: Adjust pointers 4.087ms 96.473sgc,phases,start GC(5) Phase 4: Move objects 96.474sgc,phases GC(5) Phase 4: Move objects 0.946ms 96.475sgc,heap GC(5) DefNew: 5571K(9216K)->0K(9216K) Eden: 5535K(8192K)->0K(8192K) From: 35K(1024K)->0K(1024K) 96.475sgc,heap GC(5) Tenured: 9020K(10240K)->8999K(10240K) 96.475sgc,metaspace GC(5) Metaspace: 8213K(8448K)->8092K(8448K) NonClass: 7339K(7424K)->7244K(7424K) Class: 874K(1024K)->848K(1024K) 96.475sgc GC(5) Pause Full (Allocation Failure) 14M->8M(19M) 16.586ms 96.475sgc,cpu GC(5) User=0.02s Sys=0.00s Real=0.02s [B@3b07d329 [B@682a0b20 96.477sgc,start GC(6) Pause Young (Allocation Failure) 96.477sgc GC(6) Pause Young (Allocation Failure) 13M->13M(19M) 0.086ms 96.477sgc,cpu GC(6) User=0.00s Sys=0.00s Real=0.00s 96.477sgc,start GC(7) Pause Full (Allocation Failure) 96.477sgc,phases,start GC(7) Phase 1: Mark live objects 96.485sgc,phases GC(7) Phase 1: Mark live objects 8.022ms 96.485sgc,phases,start GC(7) Phase 2: Compute new object addresses 96.487sgc,phases GC(7) Phase 2: Compute new object addresses 1.451ms 96.487sgc,phases,start GC(7) Phase 3: Adjust pointers 96.491sgc,phases GC(7) Phase 3: Adjust pointers 4.088ms 96.491sgc,phases,start GC(7) Phase 4: Move objects 96.492sgc,phases GC(7) Phase 4: Move objects 0.856ms 96.492sgc,heap GC(7) DefNew: 5217K(9216K)->5121K(9216K) Eden: 5217K(8192K)->5121K(8192K) From: 0K(1024K)->0K(1024K) 96.492sgc,heap GC(7) Tenured: 8999K(10240K)->8962K(10240K) 96.492sgc,metaspace GC(7) Metaspace: 8097K(8448K)->8028K(8448K) NonClass: 7247K(7424K)->7189K(7424K) Class: 849K(1024K)->838K(1024K) 96.492sgc GC(7) Pause Full (Allocation Failure) 13M->13M(19M) 15.333ms 96.492sgc,cpu GC(7) User=0.02s Sys=0.00s Real=0.02s 96.492sgc,start GC(8) Pause Full (Allocation Failure) 96.492sgc,phases,start GC(8) Phase 1: Mark live objects 96.498sgc,phases GC(8) Phase 1: Mark live objects 6.004ms 96.498sgc,phases,start GC(8) Phase 2: Compute new object addresses 96.500sgc,phases GC(8) Phase 2: Compute new object addresses 1.182ms 96.500sgc,phases,start GC(8) Phase 3: Adjust pointers 96.504sgc,phases GC(8) Phase 3: Adjust pointers 4.837ms 96.505sgc,phases,start GC(8) Phase 4: Move objects 96.507sgc,phases GC(8) Phase 4: Move objects 2.185ms 96.507sgc,heap GC(8) DefNew: 5121K(9216K)->5121K(9216K) Eden: 5121K(8192K)->5121K(8192K) From: 0K(1024K)->0K(1024K) 96.507sgc,heap GC(8) Tenured: 8962K(10240K)->8450K(10240K) 96.507sgc,metaspace GC(8) Metaspace: 8028K(8448K)->8028K(8448K) NonClass: 7189K(7424K)->7189K(7424K) Class: 838K(1024K)->838K(1024K) 96.507sgc GC(8) Pause Full (Allocation Failure) 13M->13M(19M) 14.864ms 96.507sgc,cpu GC(8) User=0.02s Sys=0.00s Real=0.02s 96.509sgc,heap,exit Heap 96.509sgc,heap,exit def new generation total 9216K, used 5405K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000) 96.509sgc,heap,exit eden space 8192K, 65% used [0x00000000fec00000, 0x00000000ff1475c8, 0x00000000ff400000) 96.509sgc,heap,exit from space 1024K, 0% used [0x00000000ff500000, 0x00000000ff500000, 0x00000000ff600000) 96.509sgc,heap,exit to space 1024K, 0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000) 96.509sgc,heap,exit tenured generation total 10240K, used 8450K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000) 96.509sgc,heap,exit the space 10240K, 82% used [0x00000000ff600000, 0x00000000ffe40b90, 0x00000000ffe40c00, 0x0000000100000000) 96.509sgc,heap,exit Metaspace used 8034K, committed 8448K, reserved 1056768K 96.509sgc,heap,exit class space used 839K, committed 1024K, reserved 1048576K Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at org.example.Main.main(Main.java:21) 進(jìn)程已結(jié)束,退出代碼1
最開(kāi)始放到新生代,但是當(dāng)繼續(xù)new的時(shí)候,直接放入了老年代.
重復(fù)實(shí)驗(yàn),發(fā)現(xiàn)如果幸存區(qū)空間充足,對(duì)象會(huì)放入幸存區(qū)并且延壽,否則直接將待延壽對(duì)象晉升到老年代,當(dāng)老年代空間不足時(shí),拋出oom異常.
package org.example; import java.io.IOException; import java.lang.reflect.Array; import java.util.ArrayList; public class Main { public static int a=5; public static void main(String[] args) throws InterruptedException, IOException { System.in.read(); ArrayList
剛剛想到問(wèn)題并實(shí)現(xiàn)了,下面就是這個(gè)類(lèi)似的.?
沒(méi)錯(cuò),這種情況如果老年代空間足夠(前提是新生代不夠用)直接晉升
如果超過(guò)老年代的話,很明顯就是OOM,不過(guò)會(huì)提前做個(gè)自救工作,GC
當(dāng)某個(gè)線程發(fā)生OOM時(shí),會(huì)影響主線程嗎?
綜上,不會(huì)影響
-XX:+uSEsERIALgc=Serial+SerialOld
Serial:作用于新生代,標(biāo)記復(fù)制
SerialOld:作用于老年代,標(biāo)記整理
吞吐量:運(yùn)行用戶(hù)代碼時(shí)間/(運(yùn)行用戶(hù)代碼時(shí)間+運(yùn)行垃圾收集時(shí)間)
多線程堆內(nèi)存較大,多核cpu讓單位時(shí)間內(nèi),stw的時(shí)間最短#JDK8下默認(rèn)開(kāi)啟-XX:+UseParallelGC ~ -XX:+UseParallelOldGC#需要手動(dòng)開(kāi)啟#自適應(yīng)新生代大小-XX:+UseAdaptiveSizePolicy#根據(jù)目標(biāo)調(diào)整吞吐量目標(biāo),如果達(dá)不到則調(diào)整堆的大小 1/(1+ratio) => 垃圾回收時(shí)間/總的運(yùn)行時(shí)間,結(jié)果表示每100分鐘執(zhí)行n分鐘-XX:GCTimeRatio=ratio#GC暫停時(shí)間-XX:MaxGCPauseMillis=ms-XX:ParallelGCThreads=n
默認(rèn)線程數(shù)為cpu核數(shù)
這里本來(lái)是OS的內(nèi)容,在這里再提一下
并行-XX:+UseConcMarkSweepGc-XX:+UserParNewGC#老年代補(bǔ)救-XX:SerialOld#并行線程數(shù),CPU核數(shù)-XX:ParallelGCThreads=n#并發(fā)線程數(shù),一般設(shè)置為并行線程數(shù)的1/4-XX:ConGCThreads=threads-XX:CMSInitiatingOccupancyFraction=parent-XX:+CMSScavengeBeforeRemark
,也就是說(shuō)當(dāng)核心數(shù)≥4時(shí),并發(fā)回收垃圾手機(jī)線程只占用不超過(guò)25%的處理器運(yùn)算資源,并且伴隨著核心數(shù)增多而下降
當(dāng)核心數(shù)<4時(shí),對(duì)用戶(hù)線程的影響變大 擴(kuò)展:增量式并發(fā)收集器無(wú)法處理 浮動(dòng)垃圾 ,有可能出現(xiàn)CMF(并發(fā)mode失?。亩鴮?dǎo)致另一次STW的Full GC產(chǎn)生GC Roots主要在全局性的引用和執(zhí)行上下文中,且目前Java應(yīng)用越做越龐大,類(lèi)、常量很多,逐個(gè)檢查耗費(fèi)時(shí)間較長(zhǎng)。
所有收集器根節(jié)點(diǎn)枚舉必須STW可達(dá)性分析算法耗時(shí)最長(zhǎng)的查找引用鏈過(guò)程已經(jīng)可以做到與用戶(hù)線程一起并發(fā)根節(jié)點(diǎn)枚舉必須在保證一致性的快照中才得以進(jìn)行——枚舉期間,根節(jié)點(diǎn)對(duì)象的引用關(guān)系不發(fā)生變化目前主流的虛擬機(jī)均采用準(zhǔn)確式垃圾收集(虛擬機(jī)可以知道內(nèi)存中某個(gè)位置的數(shù)據(jù)具體是什么類(lèi)型),當(dāng)用戶(hù)線程停頓下來(lái)以后,并不需要一個(gè)不漏地檢查完所有執(zhí)行上下文和全局的應(yīng)用位置,虛擬機(jī)應(yīng)當(dāng)時(shí)又辦法直接得到哪些地方存放著用戶(hù)引用的。
在HotSpot中,使用一組稱(chēng)為OopMap的數(shù)據(jù)結(jié)構(gòu)來(lái)解決準(zhǔn)確式垃圾回收的問(wèn)題,在類(lèi)加載動(dòng)作完成的時(shí)候,虛擬機(jī)會(huì)把對(duì)象內(nèi)什么偏移量上是什么類(lèi)型的數(shù)據(jù)計(jì)算出來(lái),在即使編譯過(guò)程中,也會(huì)在特定位置記錄棧、寄存器中哪些位置是引用。
OopMap協(xié)助虛擬機(jī)快速準(zhǔn)確完成GC Roots的枚舉,但是可能導(dǎo)致引用關(guān)系變化,換句話說(shuō)
導(dǎo)致OopMap內(nèi)容發(fā)生變化的指令非常多,如果每個(gè)指令都生成對(duì)應(yīng)的OopMap,那么會(huì)需要大量的額外存儲(chǔ)空間,這樣垃圾收集伴隨的空間成本將無(wú)比高昂
實(shí)際上HotSpot的確沒(méi)有為每條指令生成OopMap,只是在特定的位置記錄了這些信息你,這些位置被稱(chēng)為安全點(diǎn)。
安全點(diǎn)的設(shè)定,決定了用戶(hù)程序執(zhí)行時(shí)并非在代碼指定流的任意位置都能停下來(lái)進(jìn)行垃圾收集,必須到達(dá)安全點(diǎn)才能暫停。
安全點(diǎn)的選定:
不能太少,也不能讓收集器的等待時(shí)間過(guò)長(zhǎng)不能太頻繁增大運(yùn)行時(shí)的內(nèi)存負(fù)荷以”是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征“為標(biāo)準(zhǔn)進(jìn)行選定- 指令序列復(fù)用
- 方法調(diào)用 - 循環(huán)跳轉(zhuǎn) - 異常跳轉(zhuǎn)
安全點(diǎn)機(jī)制只能保證程序執(zhí)行時(shí),在不太長(zhǎng)的時(shí)間內(nèi)就會(huì)遇到可進(jìn)入垃圾手機(jī)的過(guò)程的安全點(diǎn)。
不執(zhí)行的時(shí)候呢?
——沒(méi)有分配處理器時(shí)間 Sleep or Blocked,此時(shí)線程無(wú)法響應(yīng)虛擬機(jī)中斷請(qǐng)求,不能再走到安全的地方終端掛起自己,虛擬機(jī)也顯然不可能持續(xù)等待線程重新杯激活分配處理器時(shí)間。
為了解決這一問(wèn)題,引入了安全區(qū)域(Safe Region)
安全區(qū)域是指能夠確保在某一段代碼片段中,引用關(guān)系不會(huì)發(fā)生變化,因此在這個(gè)區(qū)域中任意地方開(kāi)始垃圾收集都是安全的。(背擴(kuò)展拉伸了的安全點(diǎn))
當(dāng)用戶(hù)線程執(zhí)行刀安全區(qū)域里的代碼時(shí),首先會(huì)標(biāo)識(shí)自己已經(jīng)進(jìn)入了安全區(qū)域,虛擬機(jī)在進(jìn)行垃圾收集時(shí)不必去管此類(lèi)線程,當(dāng)線程離開(kāi)安全區(qū)域時(shí),檢測(cè)虛擬機(jī)是否已經(jīng)完成了根節(jié)點(diǎn)枚舉(或垃圾收集過(guò)程中其他需要暫停用戶(hù)線程的階段),如果完成,線程就當(dāng)作沒(méi)事發(fā)生過(guò)繼續(xù)執(zhí)行,否則一直等待,知道收到可以離開(kāi)安全區(qū)域的信號(hào)為止
分代收集理論提到了為解決 對(duì)象跨代引用所帶來(lái)的問(wèn)題,垃圾收集器在新生代中建立了名為記憶集的數(shù)據(jù)結(jié)構(gòu)-
記憶集
用于避免把整個(gè)老年代加進(jìn)GCRoots掃描范圍事實(shí)上不只是新生代、老年代之間才可有跨代引用的問(wèn)題,所有設(shè)計(jì)部分區(qū)域收集(Partial GC)行為的垃圾收集器(G1,ZGC,Shenandoah)都會(huì)面臨相同的問(wèn)題記憶集是一種用于記錄從非收集區(qū)域指向收集區(qū)域的指針集合的抽象數(shù)據(jù)結(jié)構(gòu)。- 如果不考慮成本,最簡(jiǎn)單的實(shí)現(xiàn) 可以用非收集區(qū)域中所有含跨代引用的對(duì)象數(shù)組來(lái)實(shí)現(xiàn)這個(gè)數(shù)據(jù)結(jié)構(gòu)Class RemebereSetd{ Object[] setOBJECT_INTERGKENERATIONAL_REFRENCE_SIZE }
全部記錄,無(wú)論是空間占用還是維護(hù)成本都相當(dāng)高昂。
其實(shí)在垃圾收集的場(chǎng)景中,收集器只需要通過(guò)記憶集判斷出某一塊非收集區(qū)域是否存在指向了收集區(qū)域的指針即可。
在設(shè)計(jì)記憶集的時(shí)候,可以選擇更為粗獷的記錄粒度來(lái)節(jié)省記憶集的存儲(chǔ)和維護(hù)成本。
字長(zhǎng)精度每個(gè)記錄精確刀一個(gè)字長(zhǎng)(處理器尋址位數(shù),32/64 bit,該精度決定了機(jī)器訪問(wèn)物理內(nèi)存地址的指針長(zhǎng)度),該字包含跨代指針對(duì)象精度每個(gè)記錄精確到一個(gè)對(duì)象,該對(duì)象里有字段包含跨代指針卡精度 每個(gè)記錄一塊內(nèi)存區(qū)域,該區(qū)域內(nèi)有對(duì)象含有跨代指針其中“卡精度”采用卡表的方式實(shí)現(xiàn)記憶集,目前最常用。卡表對(duì)記憶集的實(shí)現(xiàn):
定義了記憶集的記錄精度定義了與堆內(nèi)存的映射關(guān)系卡表與記憶集的關(guān)系,類(lèi)似于HashMap和Map的關(guān)系
最簡(jiǎn)單的一種形式:字節(jié)數(shù)組,一下代碼時(shí)默認(rèn)的卡表標(biāo)記邏輯
CARD_TABLE [this address >>9] = 0;
該數(shù)組的每一個(gè)元素都對(duì)應(yīng)(注意是對(duì)應(yīng),也就是說(shuō)存放的是地址)著其標(biāo)志的內(nèi)容區(qū)域中一塊特定大小的內(nèi)存塊,該內(nèi)存塊成為“卡頁(yè)”。
一般來(lái)說(shuō) 卡頁(yè) 的大小都是 2^n字節(jié)數(shù),HotSpot使用的卡頁(yè)是2^9,也就是512字節(jié)(地址>>9 == 地址/512)
如果卡表標(biāo)識(shí)內(nèi)存區(qū)域的其實(shí)地址是0x0000的話,數(shù)組CARD_TABLE的第0、1、2號(hào)元素,分別對(duì)應(yīng)了地址范圍為0.00000x01FF、0x02000x03FF、0x04FF~0x05FF的卡頁(yè)內(nèi)存塊.(每塊大小為512)
一個(gè)卡頁(yè)的內(nèi)存中通常包含不止一個(gè)對(duì)象,只要卡頁(yè)內(nèi)有一個(gè)(或更多)對(duì)象的字段存在著跨代指針,那就將對(duì)應(yīng)卡表的數(shù)組元素的值標(biāo)識(shí)為1,成這個(gè)元素變臟(Dirty),沒(méi)有則標(biāo)識(shí)為0。在垃圾收集發(fā)生時(shí),只要篩選出卡表中變臟的元素,就能輕易得出哪些卡頁(yè)內(nèi)存塊中包含跨代指針,把他們加入GC Roots中一并掃描。
寫(xiě)屏障用來(lái)解決卡表元素維護(hù)問(wèn)題。
卡表元素何時(shí)變臟在其他分代區(qū)中對(duì)象引用了本區(qū)域?qū)ο髸r(shí),其對(duì)應(yīng)卡表元素就應(yīng)該變臟變臟時(shí)間點(diǎn)原則上應(yīng)該發(fā)生在引用類(lèi)型字段賦值的那一刻,而處理這類(lèi)問(wèn)題就要用到寫(xiě)屏障。寫(xiě)屏障可以看作在虛擬機(jī)層面對(duì)”引用類(lèi)型字段賦值“這個(gè)動(dòng)作的AOP切面,在引用對(duì)象負(fù)值時(shí)產(chǎn)生一個(gè)環(huán)形通知,供程序進(jìn)行額外動(dòng)作,因此寫(xiě)屏障分為寫(xiě)前屏障與寫(xiě)后屏障(將環(huán)繞通知看作前置通知+后置通知),HotSpot虛擬機(jī)很多哦收集器都有使用到寫(xiě)屏障,但是直到G1收集器出現(xiàn)之前,都只用到了寫(xiě)后屏障。
// 寫(xiě)后屏障更新卡表void oop_field_store(oop* filed,oop new_value){ //引用字段賦值操作 *field = new_value; //寫(xiě)后屏障,在這里完成卡表狀態(tài)更新 post_write_barrier(field,new_value);}
應(yīng)用寫(xiě)屏障后,虛擬機(jī)就會(huì)為所有賦值操作生成相對(duì)應(yīng)的指令,一旦收集器在寫(xiě)屏障中增加了更新卡表操作,無(wú)論更新的是不是老年代對(duì)新生代對(duì)象的引用,每次只要是對(duì)引用進(jìn)行更新,都會(huì)產(chǎn)生額外的開(kāi)銷(xiāo),不過(guò)和Minor GC是掃描整個(gè)老年代相比還是較小。
在高并發(fā)場(chǎng)景下,卡表還面臨”偽共享“問(wèn)題
處理并發(fā)底層細(xì)節(jié)時(shí)一種經(jīng)常需要考慮的問(wèn)題,現(xiàn)代代中央處理器的緩存系統(tǒng)中以緩存行(Cache Line)為單位存儲(chǔ),當(dāng)線程修改互相獨(dú)立的變量時(shí),如果這些變量恰好共享同一個(gè)緩存行,就會(huì)彼此影響(寫(xiě)回、無(wú)效化、同步)而導(dǎo)致性能降低,這就是偽共享問(wèn)題
偽共享相關(guān)文章: JAVA系列:緩存一致性協(xié)議( MESI協(xié)議)_NIO4444的博客-CSDN博客偽共享(False Sharing) - 知乎 (zhihu.com)真實(shí)字節(jié)二面:什么是偽共享? - 知乎 (zhihu.com) Java 偽共享的原理深度解析以及避免方法_劉Java的博客-CSDN博客【深入理解JVM】3、CPU儲(chǔ)存器+MESI+CPU偽共享+CPU亂序問(wèn)題及代碼論證+volatile+synchrnized【面試必備】_Hello-zhou的博客-CSDN博客
避免偽共享問(wèn)題:
不采用無(wú)條件的寫(xiě)屏障,先檢查卡表標(biāo)記,只有當(dāng)該卡表元素未被標(biāo)記時(shí)才將其標(biāo)記變臟if( CARD_TABLE this address >>9 !=0 ){ CARD_TABLE this address >>9=0; }在JDK7之后,HotSpot虛擬機(jī)增加了一個(gè)新的參數(shù) -XX:+UseCondCardMark,用來(lái)決定是否開(kāi)啟卡表更新條件的判斷。開(kāi)啟會(huì)增加一次額外判斷的開(kāi)銷(xiāo),但能夠避免偽共享問(wèn)題,是否打開(kāi)要根據(jù)實(shí)際運(yùn)行情況來(lái)進(jìn)行測(cè)試權(quán)衡。使用場(chǎng)景:
同時(shí)注重吞吐量、低延遲,默認(rèn)的暫停目標(biāo)時(shí)200超大堆內(nèi)存,會(huì)將堆劃分為多個(gè)大小相等的Region整體上時(shí)標(biāo)記+整理算法,兩個(gè)對(duì)象之間是復(fù)制算法-XX:+UseG1GC-XX:G1HeapRegionSize=size-XX:MaxGCPauseMillis=time