JVM中的GC演算法,JVM引數,垃圾收集器分類
一.在JVM中什麼是垃圾?如何判斷一個物件是否可被回收?哪些物件可以作為GC Roots的根
垃圾就是在記憶體中已經不再被使用到的空間就是垃圾.
1.引用計數法:
內部使用一個計數器,當有物件被引用+1,沒有就-1,但是沒有辦法解決迴圈引用的問題,JVM不採用此類回收法
2.列舉根節點可達性分析(GC Root) 它必須是一組活躍的引用
思路:通過一系列名為GC Roots的物件作為起始點,從這個被稱為GC Root的物件開始向下進行搜尋,如果一個物件達到GC Roots
沒有任何的引用鏈相連時,這說明此物件不可用,也即給定一個集合的引用作為根出發,通過引用關係遍歷物件圖,能被遍歷到
的物件就判定為存活,沒有遍歷到的就判定為死亡
3.
1.虛擬機器棧,棧幀中的區域性變數區,也稱為區域性變量表中引用的物件
2.方法區中的類靜態屬性所引用的物件
3.方法區中常量引用的物件
4.本地方法棧中JNI(native方法)所引用的物件
二.如何檢視伺服器JVM引數的預設值,如何檢視正在執行的Java程式的引數?JVM中的常用引數有哪些?可否舉例說明
1.分為三種引數型別
標配引數(-version,-help,-showversion)
X引數(-Xint 解釋執行,-Xcomp 第一次使用就編譯成原生代碼,-Xmixed 混合模式)
XX引數
布林值型別 -XX:+/- 某個屬性值 (+表示開啟,-表示關閉)
鍵值對型別 -XX:某個引數 = 某個值
2.分為兩個步驟
jps -l 檢視到正在執行的Java程式的PID
jinfo -flag -具體引數 PID 檢視當前正在執行的Java程式的當前引數的資訊
jinfo -flags PID 檢視當前正在執行的Java程式的所有配置資訊
3.
boolean型別的引數
PrintGCDetails 是否列印GC的細節
UseSerialGC 是否使用序列垃圾回收器
key-value型別引數
MetaSpaceSize 設定元空間的大小
MaxTenuringThreshould 設定新生代物件經過多少次可以晉升到老年代
檢視預設的引數
java -XX:+PrintFlagsInitial -verion 檢視Java虛擬機器在出廠時候的引數配置
其中 = 表示值是多少/是否開啟對應功能 := 表示已經被JVM獲取手動修改過的引數
java -XX:+PrintCommandLineFlags 檢視JVM預設的GC演算法,jdk1.8 預設server端使用的是序列GC,在1.10之後統一使用G1垃圾收集器
常用引數配置
-Xms 設定初始堆記憶體大小,預設為主實體記憶體的1/64 等價於 -XX:InitialHeapSize
-Xmx 設定堆的最大分配記憶體,預設為主實體記憶體的1/4,等價於 -XX:MaxHeapSize
-Xss 設定單個執行緒的棧的大小,一般預設為512k-1024k,取決於作業系統,Linux/Unix預設為1024k,Windows根據虛擬記憶體大小來決定,默認出廠值為0
等價於 -XX:ThreadStackSize
-Xmn 設定年輕代的大小,一般不用更改
-XX:MetaSpaceSize 設定元空間的大小,元空間的本質和永久代類似,都是對JVM中方法區的實現,二者的區別在於,元空間並不在虛擬機器中,使用的是本地
的主實體記憶體,因此在預設情況下,元空間的大小僅受本地記憶體空間的限制
-XX:PrintGCDetails 打印出GC收集的詳細日誌資訊
-XX:SurvivorRatio 設定Eden區的比例佔多少,S0/S1相同
-XX:NewRatio 設定年輕代和老年代在堆解構的佔比
-XX:NewRatio=2 新生代佔1,老年代佔2,年輕代佔整個堆的1/3
-XX:NewRatio=4 新生代佔1,老年代佔4,年輕代佔整個堆的1/5
-XX:MaxTenuringThreshould 設定新生代物件經過多少次可以晉升到老年代,預設是15次
典型配置案例
-Xms128m -Xmx4096m -Xss1024k -XX:MetaspaceSize=512m -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:+UseSerialGC
public static void show01(){ System.out.println("****** hello GC ******"); // byte[] byteArr = new byte[50*1024*1024]; try { Thread.sleep(Integer.MAX_VALUE); } catch (InterruptedException e) { e.printStackTrace(); } }
修改前的引數
-XX:InitialHeapSize=265650752
-XX:MaxHeapSize=4250412032
-XX:+PrintCommandLineFlags
-XX:+PrintGCDetails
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseParallelGC
修改後的引數
-XX:InitialHeapSize=134217728
-XX:MaxHeapSize=4294967296
-XX:MetaspaceSize=536870912
-XX:+PrintCommandLineFlags
-XX:+PrintGCDetails
-XX:ThreadStackSize=1024
-XX:+UseCompressedClassPointers
-XX:+UseCompressedOops
-XX:-UseLargePagesIndividualAllocation
-XX:+UseSerialGC
列印的GC的日誌收集資訊 規律: [名稱: GC前記憶體佔用 -> GC後記憶體佔用(該區記憶體總大小)]
配置: -Xms10m -Xmx10m -XX:+PrintGCDetails
GC [PSYoungGen: 1366K->496K(2560K)] 1366K->520K(9728K), 0.0005838 secs][Times: user=0.00 sys=0.00, real=0.00 secs] YoungGC前新生代佔用 YoungGC前堆記憶體佔用 YoungGC耗時 YoungGC使用者耗時 系統耗時 實際耗時 YoungGC後新生代佔用 YoungGC後堆記憶體佔用 新生代總大小 JVM堆總大小 FULL GC [PSYoungGen: 464K->0K(2560K)] [ParOldGen: 24K->357K(7168K)] 488K->357K(9728K), [Metaspace: 3075K->3075K(1056768K)], 0.0030993 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] Young區 GC前Young區記憶體佔用 Old區 GC前Old區記憶體佔用 GC前堆記憶體佔用 元空間 GC前記憶體佔用 GC耗時 使用者時間 系統時間 實際時間 GC後Young區記憶體佔用 GC後Old區記憶體佔用 GC後堆記憶體佔用 GC後記憶體佔用 Young區總大小 Old總大小 JVM堆總大小 元空間總大小
四.常用的GC演算法有哪些?可否談一談都有垃圾回收器/垃圾回收演算法?分別適用於哪些場景?可否詳細的說一下為什麼從1.10開始都預設採用G1收集器,談談你的見解?
1.常用的GC演算法有4種
引用計數/複製/標記整理/標記清除
2.有4種垃圾收集器
1.序列垃圾收集器
它是單執行緒環境設計且只使用一個程序進行垃圾回收,會暫停所有的使用者執行緒,所以不適合高併發,快速響應的伺服器環境
2.並行垃圾收集器
多個垃圾執行緒並行工作,此時使用者執行緒是暫停的,適用於科學計算/大資料處理平臺等弱互動場景使用
3.併發垃圾收集器
使用者執行緒和垃圾回收執行緒同時進行,可互動執行,不需要暫停使用者執行緒,網際網路公司使用的較多,可以滿足對互動時間有需求的場景
4.G1垃圾收集器
將堆記憶體分割為不同的區域,然後併發的對其進行垃圾回收操作
3.垃圾回收器的型別/垃圾回收演算法(垃圾收器就是具體實現這些GC演算法並實現記憶體回收,不同版本,不同廠商的虛擬機器實現的差別很大)
垃圾回收器
1.UseSerialGC
2.UseParNewGC
3.UseParallelGC
4.UseConcMarkSweepGC
5.UseParallelOldGC
6.UseG1GC
垃圾收集演算法
1.SerialCopying (Young區)
2.ParallelScavenge(Young區)
3.ParNew(Young區)
4.SerialMSC(Old區)
5.ParallelCompacting(Old區)
6.CMS(Old區)
7.G1(Young&Old)
年輕代(Young區)
序列GC (Serial Copying)
序列GC是最古老,穩定,高效的收集器,只使用一個執行緒去回收,但其在進行垃圾回收的過程中可能會產生較長的停頓(SWT)狀態,雖然
在收集垃圾的過程中需要暫停所有其他的工作執行緒,但是它簡單高效,對於限定的單個CPU來說,沒有執行緒互動的開銷可以獲得最高的單執行緒
垃圾收集的效率,因此Serial垃圾收集器依然是Java虛擬機器在Client模式下預設的新生代收集器
配置: -Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialGC
開啟後會使用Serial + Serial Old的收集組合,表示新生代,老年代都會使用序列垃圾收集器,新生代使用複製演算法,老年代使用標記整理演算法
並行GC(ParNew)
使用多執行緒進行垃圾回收,在垃圾收集時會暫停其他所有的工作執行緒直到垃圾收集結束.ParNew實際上就是Serial收集器在新生代多執行緒
版本,最常見的使用場景是配合老年代的CMS工作,其餘的和Serial收集器完全一樣.ParNew在垃圾收集過程中同樣要暫停其他所有的工作執行緒
,它是很多Java虛擬機器在Server的預設垃圾收集器
配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParNewGC
開啟後會使用ParNew + CMS +Serial Old(備份)的組合,新生代使用複製演算法,老年代使用標記整理演算法,ParNew+Tenured這樣的組合在Java8以後不再被推薦
並行GC(Parallel)/(ParallelScavenge)
ParallelScavenge類似於ParNew也是一個新生代垃圾收集器,也是一個基於多執行緒的垃圾收集器,它是序列收集器正在新生代和老年代的並行化,可以控制
吞吐量,高吞吐量意味著高效的利用CPU的時間,多用於後臺計算而不需要太多互動的任務.自適應調節策略也是ParallelScavenge收集器和ParNew收集器的一
個很大的區別,JVM會根據當前系統執行的情況收集效能監控資訊,動態調整這些引數以提供最適合的停頓時間或最大吞吐量(-XX:MaxGCPauseMillis)
配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseParallelGC
開啟後新生代使用複製演算法,老年代使用標記整理演算法
老年代
CMS收集器(併發標記清除)
是以一種最短回收時間為目標的收集器,適用於大型的B/S系統的伺服器上,重視伺服器的響應速度,希望系統的停頓時間最短,適用於堆記憶體大,CPU核數較多 的服務端應用,也是G1出現之前大型應用首選的垃圾收集器.由於耗時最長的併發標記和併發清除的過程中,垃圾收集執行緒可以和使用者執行緒一起工作,所以整體來看
CMS收集器的記憶體回收和使用者的工作執行緒是併發執行的.
配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseConcMarkSweepGC
開啟後將自動開啟-XX:+UseParNewGC,使用ParNew(Young區) + CMS(區) + Serial Old的收集器組合,Serial Old作為CMS出錯的後備收集器
收集步驟:
1.初始標記 只是標記一下GC Root能直接關聯的物件,速度很快,但任然需要暫停所有的工作執行緒
2.併發標記 進行GC Roots跟蹤過程,和使用者的執行緒一起,不需要暫停工作執行緒,主要標記過程,標記全部物件
3.重新標記 為了修改併發標記的時間,因使用者程序繼續執行而導致標記產生變動的那一部分物件的標記記錄,任然需要暫停所有的工作執行緒,由於在
併發標記時,使用者執行緒依然執行.因此在正式清理前,再做修正
4.併發清除 清除GC Roots不可達物件,和使用者執行緒一起工作,不需要暫停工作執行緒,基於標記的結果,直接清理物件
優缺點:
優點: 併發收集低停頓
缺點: 併發執行,對CPU壓力大(由於併發進行,CMS在收集與應用執行緒會同時增加對堆記憶體的佔用,也就是CMS必須要在老年代堆記憶體用盡之前完成垃圾
回收,否則CMS回收失敗時,觸發擔保機制,序列老年代收集器將會以SWT的方式進行一次GC,從而造成較大的停頓)
採用標記清除演算法會造成大量的碎片(標記清除演算法無法整理空間碎片,老年代空間會隨著應用時長被逐步耗盡最後不得不通過擔保機制對堆內
存進行壓縮,CMS也提供了引數-XX:CMSFullGCsBeForeCompaction(預設為0,每次都進行記憶體整理)來指定多少次CMS
收集後,進行一次壓縮的Full GC)
Serial Old收集器
Serial Old是Serial垃圾收集器的老年代版本,是個單執行緒的收集器,使用標記整理演算法,這個收集器也是主要執行在Client的預設的Java虛擬機器的老年代垃圾
收集器,現在在JDK1.8之後,已經不推薦使用了.
配置:-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:+PrintCommandLineFlags -XX:+UseSerialOldGC
垃圾收集器組合
1.單CPU或小記憶體,單機程式 -XX:+UseSerialGC
2.多CPU追求最大吞吐量,如計算後臺的應用, -XX:+UseParallelGC / -XX:+UseParallelOldGC
3.多CPU追求低停頓時間,需要快速響應,如網際網路應用 -XX:+UseConcMarkSweepGC / -XX:+UseParNewGC
引數 | 新生代垃圾收集器 | 新生代演算法 | 老年代垃圾收集器 | 老年代演算法 |
-XX:UseSerialGC | SerialGC | 複製 | SerialOldGC | 標整 |
-XX:+UseParNewGC | ParNew | 複製 | SerialOldGC | 標整 |
-XX:+UseParallelGC | Parallel[Scavenge] | 複製 | ParallelOldGC | 標整 |
-XX:+UseParallelOldGC | 同上 | 同上 | 同上 | 同上 |
-XX:+UseConcMarkSweepGC | ParNew | 複製 |
CMS+Serial Old收集器的組合 Serial Old作為CMS出錯的後備 |
標清 |
-XX:+UseG1GC | 標清 | 標清 | 標清 | 標清 |
G1垃圾收集器
1.以往垃圾收集器的特點
1.年輕代和老年代必須是各自獨立且連續的記憶體塊
2.年輕代收集使用單eden+S0+S進行復制演算法
3.老年代收集必須掃描整個老年區
4.都已儘可能少而快速地執行GC為設計原則
2.G1收集器的特點
G1是一種面向服務端的垃圾收集器,應用在多處理器和大記憶體容量的環境中,在實現高吞吐量的同時儘可能滿足垃圾收集器暫停時間的特性,此外
還具有如下需求:
1.和CMS一樣可以和應用程式併發執行
2.整理空閒空間速度更快
3.需要更多的時間來預測GC的停頓時間
4.不希望犧牲大量的吞吐效能
5.不需要更大的Java Heap
3.為什麼使用G1收集器
1.G1能夠充分利用多CPU,多核環境硬體優勢,儘量縮短SWT
2.G1整體上採用標記-整理演算法,劇不是通過複製演算法,不會產生記憶體碎片
3.巨集觀上看G1之中不在區分年輕代和老年代.把記憶體劃分成獨立的子區域Region,可近似理解為圍棋棋盤
4.G1收集器將整個的記憶體區域都混在了一起,但其本身依然在小範圍內要進行年輕代和老年代的區分,保留了新生代和老年代,但它們不再是物理隔離,
而是一部分Region的集合不需要Region是連續的,也就是說依然會採用不同的GC方式來處理不同的區域
5.G1雖然也是分代收集器,但整個記憶體區域不存在物理上的年輕代和老年代的區別,也不需要完全獨立的survivor堆做複製準備.G1只有邏輯上的分代
概念,或者說每個分割槽可能隨G1的執行在不同代之間前後切換最大的好處是化整為零,避免了全記憶體的掃描,只需要按照區域進行掃描即可
4.G1的演算法原理
1.G1將堆劃分為若干個區域,仍然屬於分代收集器,這些Region一部分包含新生代,新生代的垃圾收集依然採用暫停所有執行緒的方式將存活的物件拷貝到
老年代或者Survivor區.
2.這些Region中一部分包含老年代,G1收集器通過將物件從一個區域複製到另一個區域完成清理工作,這也意味著,在正常的清理過程中,G1完成了堆的
壓縮,這樣就不再會有CMS記憶體碎片的問題了
3.在G1中還有一種特殊的區域,稱為Humongous區,如果一個物件的空間超過了分割槽容量的50%,G1收集器就認為這是一個巨型的物件,這些巨型物件會
直接的分配在老年代,但如果是一個短期存在的巨型物件,就會對垃圾收集器造成負面的影響,為了解決這類問題,G1專門劃分了一塊Humongous區域專門
用來存放巨型物件,如果H區放不下一個巨型物件,G1就會尋找連續的H區來儲存,為了能夠找到聯絡的H區,有時不得不啟用Full GC
5.回收過程
1.Eden區資料轉移到Survivor區,假如Survivor區的記憶體不夠,Eden區會晉升到Old區
2.Survivor區域的資料會移動到新的Survivor區,部分資料會晉升到Old區
3.最後Eden區回收完畢,GC結束,使用者的程序繼續執行
6.G1常用的配置引數
1.-XX:+UseG1GC 使用G1垃圾收集器
2.-XX:G1HeapRegionSize = n 設定G1區域的大小,值為2的指數冪,範圍是1~32MB,目標是根據Java堆的大小劃分出2048個區域
3.-XX:MaxGCPauseMillis = n 最大GC的停頓時間,這是個軟目標,JVM儘可能停頓小於這個時間
4.-XX:InitiatingHeapOccupancyPercent = n 堆佔用多少就觸發GC,一般是45%
5.-XX:ConcGCThread = n 併發GC使用的執行緒數
6.-XX:G1ReservePercent = n 設定做為空閒空間的預留記憶體百分比,以降低目標發生記憶體溢位的風險,預設值是10%,一般不改
7.和CMS相比有哪些優勢
1.G1不會產生記憶體碎片
2.可以精確的控制停頓,該收集器把整個堆劃分成固定大小的區域,每次會根據允許停頓的時間去收集垃圾最多的區域
G1收集器的目標是取代CMS收集器,它同CMS相比,在以下方面更具有優勢,主要應用在多CPU和大記憶體伺服器環境下,極大的減少垃圾收集的停頓時間,全面提升
伺服器的效能,逐步替代CMS收集器.主要改變的是Eden,Survivor和Tenured等區域不再是連續的了,而是變成一個個大小一樣的Region,每個Region從1M到32M
不等.一個Region可能屬於Eden,Survivor,Tenured任意的記憶體區域.這樣即不會產生記憶體碎片,同時垃圾收集時間上添加了預測機制,使用者可以指定希望的停頓時間.
8.配置
1.生產系統配置 -XX:+UseG1GC -Xms32G -XX:MaxGCPauseMillis=100
2.Spring boot微服務 java -server -Xms1024 -Xmx1024m -XX:+UseG1GC -jar 需要的微服務名稱