1. 程式人生 > >jvm層gc調優

jvm層gc調優

jvm的記憶體結構

java 8 jvm官方文件
https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

執行時資料區

官方文件:https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-2.html#jvms-2.5
執行時資料區有:方法區、虛擬機器棧、本地方法棧、堆、程式計數器

計數器pc register

jvm支援多執行緒同時執行,每一個執行緒都有自己的pc register,執行緒正在執行的方法叫做當前方法,如果是java程式碼pc register裡面存放的就是當前正在執行的指令的地址,如果是c程式碼,則為空

虛擬機器棧jvm stacks

java虛擬機器棧(java virtual machine stacks)是執行緒私有的,它的生命週期與執行緒相同。虛擬機器棧描述的是java方法執行的記憶體模型;每隔方法在執行的同事都會建立一個棧幀,用於儲存區域性變量表、運算元棧、動態連結、方法出口等資訊,每一個方法從呼叫直至執行完成的過程,就對應著一個棧幀在虛擬機器中入棧到出棧的過程。

堆heap

java堆是java虛擬機器所管理的記憶體中最大的一塊。堆是被所有執行緒共享的一塊記憶體區域,在虛擬機器啟動時穿件。此記憶體區域的唯一目的就是存放物件例項,幾乎所有的物件例項都在這裡分配記憶體。java堆可以處於物理上不連續的記憶體空間中,只要邏輯上是連續的就可以。

方法區Method Area

方法區域java堆一樣,是哥哥執行緒共享的記憶體區域,它用於儲存已被虛擬機器載入的類資訊、常量、靜態變數、即時編譯器編譯後的程式碼等資料。雖然java虛擬機器規範把方法區描述為堆的一個邏輯部分,但是它卻又一個別名叫做non-heap(非堆),目的是與java堆區分開來。

常量池 run-time constant pool

執行時常量池是方法區的一部分。class檔案中除了有類的版本、欄位、方法、介面等描述資訊外,還有一項資訊是常量池(constant pool table),用於存放編譯期生成的各種字面量和符號引用,這部分內容將在類載入後進入方法區的執行時常量池中存放。

本地方法棧native method stacks

本地方法棧與虛擬機器棧所發揮的作用是非常相似的,他們之間的區別不過是虛擬機器棧為虛擬機器執行java方法(也就是位元組碼)服務,而本地方法棧則為用虛擬機器使用到的native方法服務。

堆區

堆區分為老年代old和新生代(Young),新生代又分為eden和s0和s1區。

非堆區

metaspace=class、package、method、field、位元組碼、常量池、符號引用等。

css:32位指標的class

通過新增虛擬機器引數:+UseCompressedClassPointers使用ccs,通過:jstat -gc pid檢視CCS

codecache:JIT編譯後的原生代碼、JNI使用的C程式碼

關於一些名詞的解釋

常用引數

-Xms -Xmx
-XX:NewSize -XX:MaxNewSize
-XX:NewSize -XX:MaxNewSize
-XX:NewRatio -XX:SurvivorRatio
-XX:MetaspaceSize -XX:MaxMetaspaceSize
-XX:+UseCompressedClassPointers
-XX:CompressedClassSpaceSize
-XX:InitialCodeCacheSize
-XX:ReservedCodeCacheSize

垃圾回收演算法

思想

列舉根節點,做可達性分析

根節點

類載入器、Thread、虛擬機器棧的本地變量表、static成員、常量引用、本地方法棧的變數等等

標記清除

演算法

演算法分為“標記”和“清除”兩個階段:首先標記處所有需要回收的物件,在標記完成後統一回收所有

缺點

效率不高。標記和清除兩個過程的效率都不高。產生碎片。碎片太多會導致提前GC

複製演算法

它將可用記憶體按容量分為大小相等的兩塊,每次只使用其中的一塊,當這一塊的記憶體用完了,就將還存活的物件複製到另外一塊上面,然後再把已使用過的記憶體空間一次清理掉。

優缺點

實現簡單。執行高效,但是空間利用率低。

標記整理

演算法

標記過程仍然與“標記-清除”演算法一樣,但後續步驟不是直接對可回收物件進行清理,而是讓所有存活的物件都向一端移動,然後直接清理掉端以外的記憶體

優缺點

沒有了記憶體碎片,但是整理記憶體比較耗時

分代回收演算法

Young區用複製演算法
Old區用標記清除或者標記整理

物件分配

物件優先在Eden區分配
大物件直接進入老年代:-XX:PretenureSizeThreshold
長期存活物件進入老年代:-XX:MaxTenuringThreshold -XX:+PrintTenuringDistribution -XX:TargetSurvivorRatio

垃圾收集器

知乎上優秀的回答:https://www.zhihu.com/question/35164211

垃圾收集器

序列收集器Serial:Serial、Serial Old
並行收集器Parallel:Parallel Scavenge、Parallel Old,吞吐量
併發收集器Concurrent:CMS、G1、停頓時間

並行vs併發

並行(Parallel):指多條垃圾收集執行緒並行工作,但此時使用者執行緒仍然處於等待狀態。適合科學計算、後臺處理等弱互動場景
併發(Concurrent):指使用者執行緒與垃圾收集執行緒同時執行(但不一定是並行的,可能會交替執行),垃圾收集執行緒在執行的時候不會停頓使用者程式的執行。適合對響應時間有要求的場景,比如web。

停頓時間vs吞吐量

停頓時間:垃圾收集器做垃圾回收中斷應用程式執行時間。-XX:MaxGCPauseMillis
吞吐量:花在垃圾收集的時候和花在應用時間的佔比 .
XX:GCTimeRatio=,垃圾收集的時間佔比:1/1+n

並行收集器

吞吐量優先
-XX:+UseParallelGC,-XX:+UseParallelOldGC
Server模式下的預設收集器
檢視:jinfo -flag UseParallelOldGC pid

並行收集器

響應時間優先
CMS:XX:+UseConcMarkSweepGC -XX:+UseParNewGC
G1:-XX:+UseG1GC
檢視:jinfo -flag UseConcMarkSweepGC pid

垃圾收集器搭配

在這裡插入圖片描述
可以從老年代記起:SerialOld最厲害,可以搭配所有的新生代收集器;
ParallelOld只能搭配Parallel新生代的,也就是Parallel Scavenge;
CMS 只能搭配Serial和ParNew;
最後G1只能自己和自己玩。

如何選擇垃圾收集器

優先調整堆的大小讓伺服器自己來選擇
如果記憶體小於100M,使用序列收集器
如果是單核,並且沒有時間停頓的要求,序列或者讓jvm自己選
如果允許停頓時間操作1秒,選擇並行或者讓jvm自己選
如果響應時間最重要,並且不能操作1秒,使用併發收集器

GC調優指南:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/toc.html
如何選擇垃圾收集器:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/collectors.html

Parallel Collector

-XX:+UseParallelGC 手動開啟,Server預設開啟
-XX:ParallelGCThreads= 多少個GC執行緒
CPU>8 N=5/8
CPU< 8 N=CPU
-XX:MaxGCPauseMillis=
-XX:GCTimeRatio=
-Xmx

動態記憶體調整

-XX:YoungGenerationSizeIncrement=也就是每次gc之後發現之前的記憶體不夠,那麼就會動態增加記憶體
-XX:TenuredGenerationSizeIncrement=
-XX:AdaptiveSizeDecrementScaleFactor=

CMS Collector

特點

併發收集
低停頓 低延時
老年代收集器

CMS垃圾收集過程

1、CMS initial mark:初始標記root,STW
2、CMS concurrent mark:併發標記
3、CMS-concurrent-preclean:併發預清理
4、CMS remark:重新標記,STW
5、CMS concurrent sweep:併發清除
6、CMS-concurrent-reset:併發重置

CMS的特點

CPU敏感
浮動垃圾
空間碎片

CMS的相關引數

-XX:ConcGCThreads:併發的GC執行緒數
-XX:+UseCMSCompactAtFullCollection:FullGC之後做壓縮
-XX:CMSFullGCsBeforeCompaction:多少次FullGC之後壓縮一次
-XX:CMSInitiatingOccupancyFraction:觸發FullGC
-XX:+UseCMSInitiatingOccupancyOnly:是否動態調整
-XX:+CMSScavengeBeforeRemark:FullGC之前先做YGC
-XX:+CMSClassUnloadingEnabled:啟用回收Perm區

iCMS

使用於單核或者雙核

G1 Collector

G1垃圾收集器介紹https://zhuanlan.zhihu.com/p/22591838

簡介:

The first focus of G1 is to provide a solution for users running applications that require large heaps with limited GC latency.This means heap sizes of around 6GB or larger,and a stable and predictable pause time below 0.5 senconds.

G1屬於新生代和老生代收集器

G1的幾個概念

Region
SATB:Snapshot-At-The-Beginning,它是通過Root Tracing得到的,GC開始時候存活物件的快照。
RSet:記錄了其他Region中的物件引用本Region中物件的關係,屬於points-into結構(誰引用了我的物件)

YoungGC

新物件進入Eden區
存活物件拷貝到Survivor區
存活時間達到年齡閾值是,物件晉升到Old區

MixedGC

不是FullGC,回收所有的Young和部分Old
global concurrent marking

global concurrent marking

1、initial marking phase:標記GC Root,STW
2、Root region scanning phase:標記存活Region
3、Concurrent marking phase:標記存活的物件
4、Remark phase:重新標記,STW
5、Cleanup phase:部分STW

MixedGC時機

initiationHeapOccupancyPercent:堆佔有率達到這個數值則觸發global concurrent marking,預設45%
G1HeapWastePercent:在global concurrent marking結束之後,可以知道區有多少空間要被回收,在每次YGC之後和再次發生Mixed GC之前,會檢查垃圾佔比是否達到此引數,只有達到了,下次才會發生Mixed GC

MixedGC相關引數

G1MixedGCLiveThresholdPercent:Old 區的region被回收時候的存活物件佔比
G1MixedGCCountTarget:
一次global concurrent marking之後,最多執行Mixed GC的次數
G1OldCSetRegionThresholdPercent:一次Mixed GC中能被選入CSet的最多old區的region數量

常用引數

-XX:+UseG1GC 開啟G1
-XX:G1HeapRegionSize=n,region的大小,1~32M,2048個
-XX:MaxGCPauseMilles=200最大停頓時間
-XX:G1NewSizePercent,-XX:G1MaxNewSizePercent
-XX:G1ReservePercent=10, 保留防止to space溢位
-XX:ParallelGCThreads=n STW執行緒數
-XX:ConcGCThreads=n 併發執行緒數=1/4*併發

最佳實踐

年輕代大小

避免使用-Xmn、-XX:NewRatio等顯示設定Young區大小,會覆蓋停頓時間目標

暫停時間目標

暫停時間不要太苛刻,其吞吐量目標是90%的應用程式的時間和10%的垃圾回收時間,太苛刻會直接影響到吞吐量

關於MixedGC調優

-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent、-XX:G1HeapWastePercent
-XX:G1MixedGCCountTarget
-XX:G1OldCSetRegionThresholdPercent
MixedGC調優推薦:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations
G1垃圾收集器介紹https://zhuanlan.zhihu.com/p/22591838

是否需要切換到G1

50%以上的堆被存活物件佔用
物件分配和晉升的速度變化非常大
垃圾回收的時間特別長、超過了一秒

視覺化gc日誌分析工具

列印日誌相關引數

-XX:+PrintGCDetails -XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:$CATALINA_HOME/logs/gc.log :列印日誌的位置
-XX:+PrintHeapAtGC
-XX:+PrintTenuringDistribution

cms日誌

https://blogs.oracle.com/poonam/understanding-cms-gc-logs

g1日誌

https://blogs.oracle.com/poonam/understanding-g1-gc-logs

線上工具:http://gceasy.io/

GCViewer

https://github.com/chewiebug/GCViewer
直接啟動java -jar 啟動就可以了
主要關注吞吐量和停頓時間這些值

Tomcat的gc調優實戰

調優步驟:

  • 列印gc日誌
  • 根據gc日誌得到關鍵性指標
  • 分析gc原因,調優jvm引數
    初始化引數:
    -XX:+PrintGCDetails -XX:+PrintGCTimeStamps
    -XX:+PrintGCDateStamps -XX:+DisableExplicitGC
    -Xloggc:$CATALINA_HOME/logs/gc.log :列印日誌的位置
    -XX:+PrintHeapAtGC
    -XX:+PrintTenuringDistribution
    關於DisableExplicitGC這個,有必要了解一下,如果啟動了這個引數,不能顯示呼叫gc,System.gc()這個方法就沒用了,那麼在某一些使用到堆外記憶體的框架如netty中,就會存在記憶體洩漏的風險。
    參考:https://blog.csdn.net/aitangyong/article/details/39403031

Parallel collector調優

parallel gc調優指南:

  • 除非確定,否則不要設定最大堆記憶體
  • 優先設定吞吐量目標
  • 如果吞吐量目標達不到,調大最大記憶體,不能讓os使用swap,如果仍然達不到,降低目標。
  • 吞吐量能達到,GC時間太長、設定停頓時間的目標。
    在官方文件的這個地方:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/ergonomics.html#sthref15
    介紹了parallell gc調優的兩種方式:
    1、設定最大停頓時間XX:MaxGCPauseMillis
    2、設定吞吐量XX:GCTimeRatio
    這兩個引數只適用於paraller collector
    而在介紹Parallel collector的的文章這裡:https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html#CHDCFBIF
    說了另外兩種方式:
    3、設定記憶體動態變化:XX:YoungGenerationSizeIncrement、XX:TenuredGenerationSizeIncrement、XX:AdaptiveSizeDecrementScaleFactor,每次gc之後,都會看gc前後的記憶體,從而增加或者減少記憶體
    4、通過檢視gc的情況,設定記憶體大小,可以設定Xms(initial heap size) 、Xmx(maximum heap size)和Xmn(新生代的大小)還有MetaspaceSize和MaxMetaspaceSize

CMS GC調優

由於jdk1.8之後更加推薦使用G1,所以cms的調優自己看官網:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/cms.html#concurrent_mark_sweep_cms_collector

G1 GC調優

g1gc調優指南:

年輕代大小

避免使用-Xmn、-XX:NewRatio等顯示設定Young區大小,會覆蓋停頓時間目標

暫停時間目標

暫停時間不要太苛刻,其吞吐量目標是90%的應用程式的時間和10%的垃圾回收時間,太苛刻會直接影響到吞吐量

關於MixedGC調優

-XX:InitiatingHeapOccupancyPercent
-XX:G1MixedGCLiveThresholdPercent、-XX:G1HeapWastePercent
-XX:G1MixedGCCountTarget
-XX:G1OldCSetRegionThresholdPercent
其實這裡的介紹就是官網的介紹
官網:
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/g1_gc_tuning.html#recommendations
這裡介紹了調優的三種方式:
1、避免設定固定的記憶體例如Xms(initial heap size) 、Xmx(maximum heap size)和Xmn(新生代的大小)還有MetaspaceSize和MaxMetaspaceSize,但是可以必要條件下可以調這些引數
2、設定最大停頓時間例如;MaxGCPauseMillis=100
3、對於mixed gc引數的設定,如:
-XX:InitiatingHeapOccupancyPercent、
-XX:G1MixedGCLiveThresholdPercent、
-XX:G1HeapWastePercent、
-XX:G1MixedGCCountTarget 、
-XX:G1OldCSetRegionThresholdPercent

ps:最後思考一點,fullgc和young gc是相對parallel gc,而mixed是相對g1gc的嗎?