JVM入門

journey發表於2023-03-30

1、JVM模板

-Xms4096M -Xmx4096M -Xmn3072M -Xss1M  -XX:MetaspaceSize=256M 
-XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC 
-XX:CMSInitiatingOccupancyFaction=92 -XX:+UseCMSCompactAtFullCollection 
-XX:CMSFullGCsBeforeCompaction=0 -XX:+CMSParallelInitialMarkEnabled 
-XX:+CMSScavengeBeforeRemark -XX:+DisableExplicitGC -XX:+PrintGCDetails -Xloggc:gc.log 
-XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=/usr/local/app/oom

年輕代
-XX:MaxTenuringThreshold,預設值是15,多少次垃圾回收進入老年代
動態年齡判斷
-XX:PretenureSizeThreshold 大物件,直接進入老年代
S區放不下
空間分配擔保機制,老年代可用空間 > 新生代所有存活物件,yong gc,老年代可用空間 > 歷史yong gc平均大小,yong gc,否則full gc

老年代
-XX:CMSInitiatingOccupancyFraction,大於該值

碎片整理
-XX:+UseCMSCompactAtFullCollection

執行多少次Full GC進行一次記憶體碎片整理
-XX:CMSFullGCsBeforeCompaction=0

-XX:+CMSParallelInitialMarkEnabled 初始標記開啟多執行緒併發執行,預設是true

-XX:+CMSScavengeBeforeRemark 在CMS的重新標記階段之前,先儘量執行一次young gc(避免大量掃描)

-XX:+DisableExplicitGC 是防止System.gc()去隨便觸發GC,高峰情況下,呼叫System.gc()會發生Full GC

-XX:MetaspaceSize 預設20M,反射 jdk,cglib動態生成類,推薦512MB

-Xms -> ms是memory start簡稱,-Xmx mx是memory max的簡稱

-XX:+PrintTLAB 加這個引數可以看到 TLAB的分配,其中 refills 申請TLAB次數,slow allocs : 慢速分配的次數

XX:+ExplicitGCInvokesConcurrent 和 -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses 引數來將 System.gc 的觸發型別從 Foreground 改為 Background

CMS GC 共分為 Background 和 Foreground 兩種模式,前者就是我們常規理解中的併發收集,可以不影響正常的業務執行緒執行,但 Foreground Collector 卻有很大的差異,他會進行一次壓縮式 GC。此壓縮式 GC 使用的是跟 Serial Old GC 一樣的 Lisp2 演算法,其使用 Mark-Compact 來做 Full GC,一般稱之為 MSC(Mark-Sweep-Compact),它收集的範圍是 Java 堆的 Young 區和 Old 區以及 MetaSpace。由上面的演算法章節中我們知道 compact 的代價是巨大的,那麼使用 Foreground Collector 時將會帶來非常長的 STW

Java7 之後常量池等字面量(Literal)、類靜態變數(Class Static)、符號引用(Symbols Reference)等幾項被移到 Heap 中

2、檢視JVM中的引數

java -XX:+PrintFlagsFinal -version
看JVM所有可以設定的引數

3、String.intern()

簡單理解就是擴充常量池的一個方法;當一個String例項str呼叫intern()方法時,Java查詢常量池中是否有相同Unicode的字串常量,
如果有,則返回其引用,如果沒有,則在常量池中新增一個Unicode等於str的字串並返回它的引用(所以注意 s1.intern()是沒有用的,需要的是s1 = s1.intern())

jdk1.7之後,字串常量池已經轉移到堆區,常量還是在後設資料區中

4、棧記憶體溢位

棧是執行緒私有,它的生命週期和執行緒相同。每個方法在執行的同時都會建立一個棧幀用於儲存區域性變數表、運算元棧、動態連結、方法出口等資訊,方法呼叫
的過程就是棧幀入棧和出棧的過程
在Java虛擬機器規範中,對虛擬機器棧定義了兩種異常:
1、如果執行緒請求的棧深度大於虛擬機器所允許的深度,將丟擲StackOverflowError異常
2、如果虛擬機器棧可以動態擴充套件,並且擴充套件時無法申請到足夠記憶體,丟擲OutOfMemeoryError異常
棧對應執行緒,棧幀對應方法

5、JVM記憶體區域

執行緒共享 : 堆、後設資料區
執行緒私有化 : 虛擬機器棧、本地方法棧和程式計數器

後設資料空間和永久代類似,都是對JVM規範中方法區的實現。不過後設資料空間與永久代之間最大的區別在於 : 後設資料空間並不在虛擬機器中,而是使用本地記憶體。因此,預設情況下,後設資料空間的大小僅受本地記憶體限制

本地方法棧與虛擬機器棧發揮的作用是類似的,區別是虛擬機器棧為虛擬機器執行Java方法(也就是位元組碼)服務,而本地方法棧為虛擬機器適應到的Native方法服務

程式計數器 : 當前執行緒鎖執行的位元組碼的行號指示器,為了執行緒切換後能恢復到正確的執行位置,每個執行緒都有一個獨立的程式計數器,各個執行緒之間計數器互不影響,獨立儲存

6、xx.java檔案是怎麼載入到JVM中的

xx.java -> xx.class -> 類載入器 -> JVM
一個類從加到到使用,一般會經歷下面的過程 :
載入 -> 驗證 -> 準備 -> 解析 -> 初始化 -> 使用 -> 解除安裝
載入 : 全限定查詢此類位元組碼檔案,建立一個Class物件
驗證 : 是否符合規範
準備 : 靜態變數初始化,比如說 public static int flushInterval; 為0,不包含final修飾的static,final在編譯的時候已經分配了
類變數會分配在方法區中,例項變數是隨著物件一起分配到Java堆中
解析 : 符號引用變為直接引用
初始化 : 靜態方法和靜態程式碼塊執行(比如MySQL Driver staic方法向DriverManager 註冊驅動)
什麼時候初始化類呢 ?
1、new 類()
2、Class.forName()
3、classLoader.loadClass

7、類載入器和雙親委派機制

啟動類載入器 : lib包下核心類庫,C++寫
擴充套件類載入器 : 預設是 jre/lib/ext,可以透過 -Djava.ext.dir 指定
應用類載入器 : ClassPath環境變數所指定的路徑中的類,java -cp 指定classpath路徑
自定義類載入器
雙親委派機制,從下往上找,防備下層的類載入器把父類的類重寫了,比如說String,沙箱安全機制

第三方包載入方式,反向委派機制,說清楚就是核心程式碼在rt.jar中,實現類使用者自已定義,比如SPI,這種情況下我們就需要一種特殊的類載入器來載入第三方的類庫,而執行緒上下文類載入器(雙親委派的破壞者),就是很好的選擇,Thread.currentThread().getContextClassLoader(),也就是
classpath中的jar

Tomcat類載入器 :
BootstarpClassLoader -> ExtClassLoader -> AppClassLoader -> CommonClassLoader

CommonClassLoader 並行分為 CatalinaClassLoader 和 ShareClassLoader
ShareClassLoader 下面 WepAppClassLoader

CatalinaClassLoader 用於隔離 Tomcat本身和Web
ShareClassLoader 本質是兩個Web應用之間怎麼共享類,並且不能重複載入相同的類

Tomcat的自定義類載入WebAppClassLoader打破了雙親委派機制,它首先自己嘗試去載入某個類,如果找不到再代理給父類載入器,其目的是優先載入Web應用自己定義的類。具體實現就是重新ClassLoader的兩個方法:findClass和loadClass,loadClass呼叫了findClass方法

破壞雙親委派的點在於,findClass中是優先使用 WebAppClassLoader 來載入,而不是一直雙親委派依次向上找,找不到再向上找
loadClass 其實還是遵循 啟動類載入器、擴充套件類載入的,沒有,使用 WebAppClassLoader,在沒有向上找

8、為什麼要分代?以及不同代不同的垃圾回收機制?

分代其實為了關聯不同的垃圾回收機制,年輕代大部分被回收,所以複製演算法,存活少,複製活的物件到S區,剩餘直接清理

老年代存活物件多,所以標記+清除+整理

9、方法區內會不會進行垃圾回收?

首先,該類的所有例項物件都已經從Java堆記憶體裡被回收
其次,載入這個類的ClassLoader已經被回收
最後,對該類的Class物件沒有任何引用
只要滿足以上條件,方法區類的後設資料資訊就會被回收

使用 URLClassLoader 進行類載入器,最後進行URLClassLoader close方法,透過重寫 finalize 列印方法檢視是否被回收
最好的方式就是讓所有的不管是載入的clazz,還是ClassLoader,還是例項化的物件都為null,然後close ClassLoader,就肯定不會後設資料區溢位

10、誰才能作為GC Root?

區域性變數和靜態變數,為什麼例項變數不行?例項變數是不是屬於堆物件中,一定是需要外界引用,外界引用它只能是區域性變數和靜態變數

11、引用型別

強、軟、弱和虛引用
強是什麼?就是寧可JVM記憶體溢位,我也要存在
軟是什麼?記憶體不足,壯烈犧牲
弱是什麼?只要GC(前提是沒有強引用哈),就要銷燬
虛是什麼?虛引用的主要作用是跟蹤物件被垃圾回收的狀態,僅僅是提供了一種確保物件被finalize以後,做某些事情的機制,虛引用必須和引用佇列同時使用

12、ParNew + CMS(STW)

ParNew對應新生代,複製演算法,不能工作
CMS對應老年代,標記 + 清理,系統一邊工作一邊清理
1、初始標記,快,從GC Root出發
2、併發標記,慢,相當於是遞迴追蹤,無所謂,程式是可以執行的
3、重新標記,快,增量標記
4、併發清理,慢,垃圾回收
不使用的場景,大機器記憶體,比如說64G,給JVM 32G,回收需要很長時間

13、為啥老年代的Full GC要比新生代的Yong GC慢很多,一般在10倍以上?

新生代 :
新生代執行速度其實很快,因為直接從GC Roots觸發就追蹤哪些物件是或的就行了,新生代物件存活是很少的,這個速度是極快的,不需要追蹤多少物件
其實年輕代GC幾乎沒什麼好調優的,因為他的執行邏輯非常簡單,就是Eden一旦滿了無法放心物件就觸發一次GC,一般來說,真要對年輕代GC調優,
只要你給系統分配足夠的記憶體即可,核心點點在於堆記憶體的分配、新生代記憶體的分配

老年代 :
存活物件很多,併發清理階段,不是一次性回收一大批記憶體,而是找零零散散在各個地方的垃圾物件,完事了,還要一次碎片整理

14、什麼時候會觸發Concurrent Mode Failure ?

說白了其實就是比如說在併發清理的時候,我在清理資料,你比如說新生代發生yong gc,你要往老年代放資料,抱歉,放不了,我正在垃圾回收呢?
所以現在這個情況就會出現 Concurrent Mode Failure,然後CMS切換為Serial Old,直接禁止程式執行

15、G1(Garbage-First,G1)

優先回收垃圾,不會等到空間全部佔滿,然後進行回收

把Java堆記憶體拆分為多個大小相等的Region(最多2048個region),G1新生代和老年代是邏輯上的概念,設定一個垃圾回收的預期停頓時間
計算回收價值,比如說1個Region中的垃圾物件有10M,回收需要1s;另外一個Region,垃圾20M,200ms
核心理念 : 最少回收時間和最多回收物件的Region進行垃圾回收

大物件在G1中比較特殊,有特殊的region來儲存,在新生代或者老年代發生gc的時候,會順帶大物件回收

-XX:UseG1GC
-XX:G1HeapRegionSize
-XX:G1MaxNewSizePercent 5%~60%
-XX:MaxGCPauseMillis 預設200ms
-XX:InitiatingHeapOccupancyPercent 45%
-XX:G1MixedGCCountTarget 混合回收的過程中,最後一個階段執行幾次混合回收,預設是8次
-XX:G1HeapWastePercent 預設值是5%,混合回收,一旦空閒出來Region數量達到了堆的5%,立即停止混合回收
-XX:G1MixedGCLiveThresholdPercent 預設值是85%,確定要回收的Region的時候,必須是存活物件低於85%的Region才可以進行回收,如果大於不會放到CSet中
-XX:GCTimeRatio GC與應用的耗費時間比,如果G1 GC時間與應用執行的時間佔比不超過10%的時候,不需要動態擴充套件

使用場景 : 大記憶體的場景
Mixed GC(混合回收),G1特有
新生代分割槽、自由分割槽、老年代分割槽和大物件分割槽
G1不能手動指定分割槽個數
停頓預測模型 + 動態調整機制保障我們GC百分之九十能夠保持在某個停頓時間內的關鍵

16、常用命令

jstack -l pid 要在某個使用者啟動情況下執行,檢視執行緒情況
jstat -gc pid period count 看每個代的容量變化
jmap -histo pid 物件分佈

17、有哪些溢位?

棧溢位、堆溢位、後設資料溢位、堆外記憶體溢位

棧溢位 : 遞迴
堆溢位 : 就是記憶體不能再建立物件
後設資料溢位 : JDK反射或者CGLIB反射,根本原因是ClassLoader一直載入類,最終後設資料空間放滿
堆外記憶體溢位 :

場景1 : 瞬時大量請求,DirectByteBuffer,其實會是堆和堆外有一個對映關係,建立大量堆外記憶體,不能建立了溢位
場景2 : 新生代設定不合理,每次,young gc,一些請求未執行完畢,當然就會進入老年代,而老年代設定的又比較大,這一直建立堆外記憶體,而且老年代比較大,又不回收
最終就會導致堆外記憶體溢位
怎麼解決呢?每次分配新的堆外記憶體的時候,都會呼叫System.gc()去提醒JVM主動執行一下gc去回收掉一些垃圾沒人引用的DirectByteBuffer物件,釋放堆外記憶體空間
但是不能設定 -XX:+DisableExplicitGC

18、G1為什麼要滿足新生代的動態擴充套件?

動態調整記憶體分割槽的佔比,來滿足回收時間。如果不做動態調整,那麼GC時間過長,就沒法滿足停頓時間。動態增加、減少,才能調整到一個合理的值

擴充套件機制 : 新生代分割槽列表 + 自由分割槽 + 擴充套件分割槽

19、停頓預測模型

衰減標準差演算法,簡單來說其實就是執行緒加權(距離本地預測越近的GC影響比重就佔的越高才行)

20、TLAB

LAB(Thread Local Allocation Buffer,指標碰撞法來分配物件,dummy物件填充碎片空間)
物件分配很複雜,需要鎖整個堆,效率低下。所以G1採取的就是本地緩衝區的思想來分配的,每個執行緒都有一個自己執行緒的本地分配緩衝區
這個緩衝區,保證了一個執行緒過來的時候,儘可能的走這個TLAB來分配物件,能夠快速分配,並實現了無鎖化分配

TLAB是和執行緒物件的,一個進行啟動的執行緒是有數的,幾個執行緒就鎖幾次堆,使用CAS來分配TLAB
TLAB太小,會導致TLAB快速被填滿,從而導致物件不走TLAB分配,效率變差
如果TLAB過大,造成記憶體碎片

假如TLAB滿了(refill_waste,可以浪費空閒空間,如果剩餘小於等於這個值,認為已經滿了),無法分片物件了,會怎麼處理?
在G1中,就是說,如果說無法分片物件了,就優先去申請一個新的TLAB來儲存物件
如果無法申請新的TLAB,才有可能會走堆記憶體加鎖,然後直接在堆上分配物件的邏輯

一邊執行一邊調整refillwaste和TLAB大小
系統程式 -> 分配物件 -> 物件需要空間大於refill_waste(堆記憶體直接分配) -> TLAB剩餘記憶體夠分配物件嗎?(直接TLAB分配)
-> 申請一個新的TLAB ->分配物件

21、快速分配和慢速分配

簡說快速分配其實就是走TLAB,慢速分配不走TLAB(慢速分配需要加鎖,甚至可能要涉及到GC的過程,所以速度非常慢)

慢速分配
1、去建立新的TLAB
2、擴充套件分割槽
3、垃圾回收

ObjSize > regionSize/2的時候,就成為大物件,同時設定TLAB的最大值,限定在最大為regionsize/2,這樣子,大物件就一定
大於TLAB的大小,所有可以直接走慢速分配,為什麼呢?很簡單,一個大物件就佔滿了TLAB,會造成其他普通的物件進入慢速分配

使用TLAB進行快速分配的過程,第一次進入慢速分配,擴充套件空間失敗的時候,就是ygc活著mixed gc,再次進行慢速分配,有可能還會執行gc,
在分配過程中執行的這個ygc或者mixed gc,慢速分配也失敗的時候,就會進入最終的嘗試,最終嘗試執行兩次full gc,一次不回收軟引用,
一次回收軟引用

22、為了提升GC的效率,G1設定了哪些核心機制?

1、Rset記憶集 Memember Set
G1回收 young gc -> mixed gc -> full gc

新生代物件不一定只有新生代引用,有可能會有老年代的物件引用新生代的物件。那麼我們直接在觸發新生代gc的時候,我們在老年代的裡面有一些物件也在我們的
引用鏈中。RSet記錄了誇代引用的引用關係,在gc的時候,可以快速藉助記憶集 + GC Roots搞定同代引用及跨代引用的可達物件分析問題

RSet記憶集的維度是對每一個region,都搞一塊記憶體,儲存region裡面所有的物件被引用關係

RSet中只儲存老年代到新生代的引用(young gc使用,不要在young gc的時候遍歷老年代)
老年代到老年代的引用,mixed gc只是選擇部分的region進行回收,如果透過RSet是最快的,而不是GC Root一直遞迴找

2、點陣圖(BitMap),G1採取了點陣圖的方式描述記憶體是否使用了
3、卡表,G1中,卡表是用一個位元組(8位)來描述512位元組的空間的使用情況,及記憶體被誰使用了。JVM使用了RSet+卡表來作為分代模型中,跨帶引用關係不好判定,不好追蹤問題的解決思路

耗時操作 : 初始標記、併發標記、重新標記和併發清理,最耗時其實就是引用關係不好判定,記憶體是否使用不好判定

首先,初始標記過程,要從GC Roots觸發,標記所有直接被引用的關係是吧?然後再併發標記階段,追蹤所有間接被引用的物件,如果出現跨代引用,比如我新生代物件
被老年代引用了,我肯定不能被回收啊,那麼RSet就避免了對老年代的遍歷

引用關係怎麼找?RSet裡肯定不能直接記錄哪個物件引用了哪個物件,不然一個系統1000w個物件,引用關係還特別複雜,可能要記錄很多遍,那豈不是一個RSet比整個系統
佔用的記憶體還要大?所以,這個時候cardTable就出現了,cardTable裡面可以用一個位元組來描述512位元組記憶體的引用關係,那麼RSet裡面直接記錄cardTable相關內容這一樣可以節省很多記憶體
比如,我發現老年代有一塊512B的空間裡的物件引用了新生代的一個物件,RSet直接記錄這個512B的空間開標裡面的位置就OK了

點陣圖和併發標記是息息相關的,簡單來說,就是在併發標記階段,可以藉助點陣圖描述記憶體使用情況,避免記憶體使用衝突的問題,也避免GC執行緒無效遍歷一些未使用的記憶體

新生代引用老年代,一般不需要記錄為什麼?
我們要知道,gc回收過程是沒有老年代單獨的回收的,所以如果要回收老年代的時候,肯定是會帶著一次新生代回收,或者直接full gc,所以不需要記錄

卡表其實就是點陣圖思想的一種實現方式,只是粒度不同罷了。點陣圖用每一位來描述對應資料的裝他,卡表,其實就是按照點陣圖的思想,用一個字來描述512位元組的記憶體狀態,
引用等相關資料,Rset記錄的是卡表地址

23、RSet更新

如果引用關係發生了改變,RSet是否需要更新,應該怎麼更新?
要知道每個Region中是由一個RSet的,物件更新,如果加鎖的方式更新RSet,會造成效能問題

JVM核心是 : 物件分配和垃圾回收
物件分配是不需要RSet的,因為我們在分配一個物件的時候,值需要看記憶體夠不夠,剩餘空間是否能夠分配一個物件,分配完成以後,直接更新一下記憶體的使用情況就OK了,並不需要藉助RSet
RSet本身就是為了垃圾回收的時候更加方便,不需要遍歷所有空間而設計的,所以RSet即使需要更新,而我們沒有把他更新,其實也無所謂,因為不影響程式執行,只是在垃圾回收的時候,我一定需要更新RSet,不然就會報錯

G1的髒資料佇列非同步消費,更新RSet。G1設計了一個佇列,叫做DCQ(Dirty Card Queue)。在每次引用關係變更的時候,就把這個變更操作,發動一個訊息到DCQ裡面,然後又一個專門的執行緒去非同步消費(refine執行緒)

針對DCQ G1是設計了二級快取來解決併發衝突的
第一層快取是線上程這一層,也就是說,DCQ其實是屬於每一個執行緒的,也就是說,每一個工作執行緒都會關聯一個DCQ,每個執行緒在執行了引用更新操作的時候,都會往自己的那個DCQ裡面寫入變更資訊。DCQ預設長度是256,如果寫滿了,就重新申請一個新的DCQ,並把這個老DCQ提交到第二級快取,也就是一個DCQ Set裡面去,叫做二級快取為DCQS
refine直接從DCQS取DCQ去消費的,如果DCQS已經不能再放下更多的DCQ了,此時工作執行緒就會自己處理,自己去處理DCQ,更新RSet

24、G1垃圾回收

如果老年代物件佔用達到了某個閾值,就會觸發老年代的回收,ParNew + CMS直接Full GC,G1中是觸發mixed gc

首先會進行ygc,老年代存活物件越來越多,會進入mixed gc,mixed gc會從老年代中選擇部分回收價值比較高的region進行回收,滿足使用者設定的MaxGCPauseMillis值,當mixed gc之後,物件還是無法分片成功的時候,觸發full gc,
full gc會暫停程式執行,對整個堆進行全面的垃圾回收,包括新生代、老年代和大物件等

25、G1 Young GC

並行處理
STW -> 選擇需要回收的分割槽全部新生代region -> GC Roots + RSet -> 把直接引用的物件field放入一個set,-> 放入survior -> 繼續遍歷field set -> 複製到survior中

序列處理
弱引用、軟引用和虛引用追蹤 -> 複製到survivor區 -> 一些G1最佳化,比如字串去重 -> 重建RSet -> 清理垃圾(嘗試回收大物件) -> 嘗試擴充套件分割槽(GC佔用時間達到程式10%) -> 調整新生代數量 -> 要看GC對DCQS的處理,調整DCQS的幾個閾值 -> 判斷是否開啟併發標記,如果判斷透過(老年代45%的使用率)就啟動併發標記

第一次YGC的時候,應該是 -Xmx * 5%的年輕代使用量的時候,大概是這個值

26、Mixed回收

1、初始標記
一定會伴隨一次YGC,在YGC的時候會做一個判斷,是否要開啟併發標記,如果需要開啟併發標記,那麼本次YGC的整體過程,會額外做一些事情,
把GC Roots直接引用的老年代的物件也標記起來,作為併發標記的其實物件的一部分

初始標記的時候會發生YGC,YGC結束之後,在survivor區的物件會作為一部分的併發標記的起始物件(跟物件,可以理解為GC Roots物件)
所以,在初始標記階段中,必然會發生一次YGC

2、併發標記
Survivor區儲存的物件 + GC Roots引用的老年代物件集合RSet進行標記,並引入點陣圖 + 三色標記法來做物件是否存活的標記
白色大表死亡、黑色代表存活、灰色代表存活但其子物件尚未標記完全
引用變化,物件變為灰色,重新標記,防止誤刪除

SATB多次併發標記過程中,出現的引用變化都會記錄下來

3、最終標記 STW
主要是對併發標記階段由於系統執行造成的錯標漏表情況修正處理。本質上是把所有SATB佇列裡面的物件重新做遍歷標記處理。最終把物件全表標記為黑色
或者保持原本的白色

4、預回收
存活物件計數的階段

5、混合回收階段,會選擇一些分割槽,成為Cset(Collect Set),回收
需要注意的是,在選擇CSet的時候,是按照一定的選擇演算法來選擇CSet的(-XX:G1MixedGCLiveThresholdPercent存活小於85%才會放入到CSet中)。我們在上一個階段,已經統計出來了Region的存活物件的數量,垃圾物件的數量,
那麼其實CSet的選擇就是根據這個基礎來做的

回收時間 約定於 物件的複製時間(從垃圾region轉移到空閒region)

混合回收是否要執行的條件 :
-XX:G1HeapWastePercent 預設是5%,即,在併發標記結束之後,如果說我們選擇的CSet中可以被回收的垃圾佔用堆記憶體空間的佔比大於5%,才會進行混合回收
的回收節點,否則本次是不開啟垃圾回收的過程的,即使併發標記,重新標記都完成,也不開啟垃圾回收的過程

MixedGC的最後一步,垃圾回收過程,-XX:G1MixedGCCountTarget,預設是8,會最多透過8次對CSet進行分配回收

G1OldCSetRegionThredShouldPercent,引數是10,表示,每次在執行混合回收的時候,回收掉的分割槽數量不能超過整個堆記憶體分割槽數量的10%

27、Full GC

TLAB分配 -> 擴充套件TLAB進行分配 -> 申請新的TLAB -> 從自由分割槽新的region給新生代 -> 堆外記憶體擴充套件分割槽給新生代 -> 垃圾回收(YGC、MixedGC) ->
FULL GC(第一次) -> FULL GC(第二次,回收軟引用)

FULL GC的一些最佳化點總結
1、使用多執行緒進行垃圾回收,CMS一塊記憶體衝突比較多(整理、壓縮),所以單執行緒;G1天生分割槽多執行緒(整理、壓縮)
2、標記過程採用多執行緒並行處理
3、採取了任務竊取策略,提升整體的效率
4、採取了單個執行緒的那些分割槽整體壓縮的處理,提升空閒region的產生的可能性

YGC和FULL GC會進行字串去重

-Xms10M -Xmx10M -Xss1M  -XX:MetaspaceSize=256M 
-XX:MaxMetaspaceSize=256M -XX:+UseParNewGC -XX:+UseConcMarkSweepGC 
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintTLAB 
-XX:+PrintGCDetails -Xloggc:gc.log 
-XX:+HeapDumpOnOutOfMemoryError  -XX:HeapDumpPath=/Users/qiaozhanwei/IdeaProjects/jdk

// -XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest 這兩個選項是開啟詳情日誌
-XX:InitialHeapSize=128M -XX:MaxHeapSize=128M -XX:+UseG1GC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintTLAB -XX:+UnlockExperimentalVMOptions -XX:G1LogLevel=finest -XX:MaxGCPauseMillis=20 -Xloggc:gc.log

-Xmx256M -XX:+UseG1GC -XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:MaxGCPauseMillis=20 -Xloggc:gc.log

JDK9 中G1被設定為預設的垃圾回收器

如感興趣,點贊加關注哦!

相關文章