JVM調優


JVM調優經歷

系統優化:

一般系統優化思路是這樣的:數據庫->應用->JVM->網絡和操作系統

1. 首先排查是否為數據庫的問題,這個過程中就需要評估自己建的索引是否合理、是否需要引入分布式緩存、是否需要分庫分表等等。

2. 然后考慮應用是否需要擴容(橫向和縱向都會考慮,有可能是系統的壓力過大或者是系統的硬件能力不足導致系統頻繁出現問題)。應用代碼層面上排查並優化,需要審視自己寫的代碼是否存在資源浪費的問題,又或者是在邏輯上可存在優化的地方,比如說通過並行的方式處理某些請求。

3. 再接着,JVM層面上排查並優化,這個過程我們觀察JVM是否存在多次GC問題等等。

4. 最后,網絡和操作系統層面排查,這個過程查看內存/CPU/網絡/硬盤讀寫指標是否正常等等。

 

JVM調優可分為以下步驟:

一、分析系統系統運行情況

實時監控:通過Linux命令、JDK相關命令、實時監控平台查看CPU性能、內存占用、GC回收情況。

事后分析:分析GC日志及堆轉儲快照文件,判斷是否需要優化,確定問題點;

 

一般我們是「遇到問題」之后才進行調優的,而遇到問題后需要利用各種的「工具」進行排查

1. jps:查看JVM進程基礎信息(進程號、主類)

2. jstat:查看Java進程統計類相關的信息,查看各個區內存和GC的情況

3. jinfo:查看JVM的配置信息。

4. jmap:JVM堆內存分析工具常用於把JVM內存使用情況dump到文件,然后再用MAT( Memory Analyzer tool)、VisualVM等工具對文件進行分析

5. jstack:查看JVM線程信息, jstack可以定位到線程堆棧,根據堆棧信息我們可以定位到具體代碼。這個命令用常用於排查死鎖相關的問題

6. jconsole、jvisualvm:帶圖形界面的JVM監控工具,查看JVM基本情況、做棧和堆轉儲等。

7. Arthas: 還有近期比較熱門的Arthas(阿里開源的診斷工具),涵蓋了上面很多命令的功能且自帶圖形化界面。

 

二、確定JVM調優量化目標

調優的最終目的都是為了讓應用程序使用最小的硬件消耗來承載更大的吞吐。

  • 低延遲:GC低停頓和GC低頻率;
  • 高吞吐量;
  • 低內存使用率;

下面展示了一些JVM調優的量化目標參考實例:

  • Heap 內存使用率 <= 70%;
  • Old generation內存使用率<= 70%;
  • avgpause <= 1秒;
  • Full gc 次數0 或 avg pause interval >= 24小時 ;

 

三、選擇合適的垃圾回收器

CPU單核,那么毫無疑問Serial 垃圾收集器是你唯一的選擇。

CPU多核,對系統吞吐量有較高要求,那么選擇PS+PO組合。(

CPU多核,對響應時間要求比較高,JDK版本1.6或者1.7,那么選擇CMS。

CPU多核,對響應時間要求比較高,JDK1.8及以上,JVM可用內存6G以上,那么選擇G1。

 

四、調整JVM參數配置

對比觀察調優前后的差異,不斷的分析和調整,直到找到合適的JVM參數配置,並進行后續跟蹤。

內存空間的分配設置:JVM 內存分配不合理帶來的性能表現並不會像內存溢出問題這么突出,最直接的表現就是頻繁的 GC,這會導致上下文切換等性能問題,從而降低系統的吞吐量、增加系統的響應時間。具體的實現包括調整堆內存空間減少 Full GC、調整年輕代減少 MinorGC、設置合理的 Eden 和 Survivor 區的比例。


一般調優JVM我們認為會有幾種指標可以參考:『吞吐量』、『停頓時間』和『垃圾回收頻率』。基於這些指標,我們就有可能需要調整:

1. 內存區域大小以及相關策略(比如整塊堆內存占多少、新生代占多少、老年代占多少、Survivor占多少、晉升老年代的條件等等)。按經驗來說:IO密集型的可以稍微把「年輕代」空間加大些,因為大多數對象都是在年輕代就會滅亡。內存計算密集型的可以稍微把「老年代」空間加大些,對象存活時間會更長些)

-Xmx:設置堆的最大值、

-Xms:設置堆的初始值、

-Xmn:表示年輕代的大小、

-XX:SurvivorRatio:伊甸區和幸存區的比例

2. 垃圾回收器(選擇合適的垃圾回收器,以及各個垃圾回收器的各種調優參數)

-XX:+UseG1GC:指定 JVM 使用的垃圾回收器為 G1、

-XX:MaxGCPauseMillis:設置目標停頓時間、

-XX:InitiatingHeapOccupancyPercent:當整個堆內存使用達到一定比例,全局並發標記階段 就會被啟動等

 

 

7.2、調整內存大小

現象:垃圾收集頻率非常頻繁。

原因:如果內存太小,就會導致頻繁的需要進行垃圾收集才能釋放出足夠的空間來創建新的對象,所以增加堆內存大小的效果是非常顯而易見的。

注意:如果垃圾收集次數非常頻繁,但是每次能回收的對象非常少,那么這個時候並非內存太小,而可能是內存泄露導致對象無法回收,從而造成頻繁GC。

參數配置:

//設置堆初始值
 指令1:-Xms2g
 指令2:-XX:InitialHeapSize=2048m
 ​
 //設置堆區最大值
 指令1:`-Xmx2g` 
 指令2: -XX:MaxHeapSize=2048m
 ​
 //新生代內存配置
 指令1:-Xmn512m
 指令2:-XX:MaxNewSize=512m

7.3、設置符合預期的停頓時間

現象:程序間接性的卡頓

原因:如果沒有確切的停頓時間設定,垃圾收集器以吞吐量為主,那么垃圾收集時間就會不穩定。

注意:不要設置不切實際的停頓時間,單次時間越短也意味着需要更多的GC次數才能回收完原有數量的垃圾.

參數配置:

//GC停頓時間,垃圾收集器會嘗試用各種手段達到這個時間
 -XX:MaxGCPauseMillis 

7.4、調整內存區域大小比率

現象:某一個區域的GC頻繁,其他都正常。

原因:如果對應區域空間不足,導致需要頻繁GC來釋放空間,在JVM堆內存無法增加的情況下,可以調整對應區域的大小比率。

注意:也許並非空間不足,而是因為內存泄造成內存無法回收。從而導致GC頻繁。

參數配置:

//survivor區和Eden區大小比率
 指令:-XX:SurvivorRatio=6  //S區和Eden區占新生代比率為1:6,兩個S區2:6
 ​
 //新生代和老年代的占比
 -XX:NewRatio=4  //表示新生代:老年代 = 1:4 即老年代占整個堆的4/5;默認值=2

7.5、調整對象升老年代的年齡

現象:老年代頻繁GC,每次回收的對象很多。

原因:如果升代年齡小,新生代的對象很快就進入老年代了,導致老年代對象變多,而這些對象其實在隨后的很短時間內就可以回收,這時候可以調整對象的升級代年齡,讓對象不那么容易進入老年代解決老年代空間不足頻繁GC問題。

注意:增加了年齡之后,這些對象在新生代的時間會變長可能導致新生代的GC頻率增加,並且頻繁復制這些對象新生的GC時間也可能變長。

配置參數:

//進入老年代最小的GC年齡,年輕代對象轉換為老年代對象最小年齡值,默認值7
 -XX:InitialTenuringThreshol=7 

7.6、調整大對象的標准

現象:老年代頻繁GC,每次回收的對象很多,而且單個對象的體積都比較大。

原因:如果大量的大對象直接分配到老年代,導致老年代容易被填滿而造成頻繁GC,可設置對象直接進入老年代的標准。

注意:這些大對象進入新生代后可能會使新生代的GC頻率和時間增加。

配置參數:

//新生代可容納的最大對象,大於則直接會分配到老年代,0代表沒有限制。
  -XX:PretenureSizeThreshold=1000000 

7.7、調整GC的觸發時機

現象:CMS,G1 經常 Full GC,程序卡頓嚴重。

原因:G1和CMS 部分GC階段是並發進行的,業務線程和垃圾收集線程一起工作,也就說明垃圾收集的過程中業務線程會生成新的對象,所以在GC的時候需要預留一部分內存空間來容納新產生的對象,如果這個時候內存空間不足以容納新產生的對象,那么JVM就會停止並發收集暫停所有業務線程(STW)來保證垃圾收集的正常運行。這個時候可以調整GC觸發的時機(比如在老年代占用60%就觸發GC),這樣就可以預留足夠的空間來讓業務線程創建的對象有足夠的空間分配。

注意:提早觸發GC會增加老年代GC的頻率。

配置參數:

//使用多少比例的老年代后開始CMS收集,默認是68%,如果頻繁發生SerialOld卡頓,應該調小
 -XX:CMSInitiatingOccupancyFraction
 ​
 //G1混合垃圾回收周期中要包括的舊區域設置占用率閾值。默認占用率為 65%
 -XX:G1MixedGCLiveThresholdPercent=65 

7.8、調整 JVM本地內存大小

現象:GC的次數、時間和回收的對象都正常,堆內存空間充足,但是報OOM

原因: JVM除了堆內存之外還有一塊堆外內存,這片內存也叫本地內存,可是這塊內存區域不足了並不會主動觸發GC,只有在堆內存區域觸發的時候順帶會把本地內存回收了,而一旦本地內存分配不足就會直接報OOM異常。

注意: 本地內存異常的時候除了上面的現象之外,異常信息可能是OutOfMemoryError:Direct buffer memory。 解決方式除了調整本地內存大小之外,也可以在出現此異常時進行捕獲,手動觸發GC(System.gc())。

配置參數:

 XX:MaxDirectMemorySize


作者:三分惡
鏈接:https://www.zhihu.com/question/362201242/answer/2290090371
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
 
作者:三分惡
鏈接:https://www.zhihu.com/question/362201242/answer/2290090371
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

8、JVM調優實例

以下是整理自網絡的一些JVM調優實例:

8.1、網站流量瀏覽量暴增后,網站反應頁面響很慢

1、問題推測:在測試環境測速度比較快,但是一到生產就變慢,所以推測可能是因為垃圾收集導致的業務線程停頓。

2、定位:為了確認推測的正確性,在線上通過jstat -gc 指令 看到JVM進行GC 次數頻率非常高,GC所占用的時間非常長,所以基本推斷就是因為GC頻率非常高,所以導致業務線程經常停頓,從而造成網頁反應很慢。

3、解決方案:因為網頁訪問量很高,所以對象創建速度非常快,導致堆內存容易填滿從而頻繁GC,所以這里問題在於新生代內存太小,所以這里可以增加JVM內存就行了,所以初步從原來的2G內存增加到16G內存。

4、第二個問題:增加內存后的確平常的請求比較快了,但是又出現了另外一個問題,就是不定期的會間斷性的卡頓,而且單次卡頓的時間要比之前要長很多。

5、問題推測:練習到是之前的優化加大了內存,所以推測可能是因為內存加大了,從而導致單次GC的時間變長從而導致間接性的卡頓。

6、定位:還是通過jstat -gc 指令 查看到 的確FGC次數並不是很高,但是花費在FGC上的時間是非常高的,根據GC日志 查看到單次FGC的時間有達到幾十秒的。

7、解決方案: 因為JVM默認使用的是PS+PO的組合,PS+PO垃圾標記和收集階段都是STW,所以內存加大了之后,需要進行垃圾回收的時間就變長了,所以這里要想避免單次GC時間過長,所以需要更換並發類的收集器,因為當前的JDK版本為1.7,所以最后選擇CMS垃圾收集器,根據之前垃圾收集情況設置了一個預期的停頓的時間,上線后網站再也沒有了卡頓問題。

8.2、后台導出數據引發的OOM

**問題描述:**公司的后台系統,偶發性的引發OOM異常,堆內存溢出。

1、因為是偶發性的,所以第一次簡單的認為就是堆內存不足導致,所以單方面的加大了堆內存從4G調整到8G。

2、但是問題依然沒有解決,只能從堆內存信息下手,通過開啟了-XX:+HeapDumpOnOutOfMemoryError參數 獲得堆內存的dump文件。

3、VisualVM 對 堆dump文件進行分析,通過VisualVM查看到占用內存最大的對象是String對象,本來想跟蹤着String對象找到其引用的地方,但dump文件太大,跟蹤進去的時候總是卡死,而String對象占用比較多也比較正常,最開始也沒有認定就是這里的問題,於是就從線程信息里面找突破點。

4、通過線程進行分析,先找到了幾個正在運行的業務線程,然后逐一跟進業務線程看了下代碼,發現有個引起我注意的方法,導出訂單信息。

5、因為訂單信息導出這個方法可能會有幾萬的數據量,首先要從數據庫里面查詢出來訂單信息,然后把訂單信息生成excel,這個過程會產生大量的String對象。

6、為了驗證自己的猜想,於是准備登錄后台去測試下,結果在測試的過程中發現到處訂單的按鈕前端居然沒有做點擊后按鈕置灰交互事件,結果按鈕可以一直點,因為導出訂單數據本來就非常慢,使用的人員可能發現點擊后很久后頁面都沒反應,結果就一直點,結果就大量的請求進入到后台,堆內存產生了大量的訂單對象和EXCEL對象,而且方法執行非常慢,導致這一段時間內這些對象都無法被回收,所以最終導致內存溢出。

7、知道了問題就容易解決了,最終沒有調整任何JVM參數,只是在前端的導出訂單按鈕上加上了置灰狀態,等后端響應之后按鈕才可以進行點擊,然后減少了查詢訂單信息的非必要字段來減少生成對象的體積,然后問題就解決了。

8.3、單個緩存數據過大導致的系統CPU飈高

1、系統發布后發現CPU一直飈高到600%,發現這個問題后首先要做的是定位到是哪個應用占用CPU高,通過top 找到了對應的一個java應用占用CPU資源600%。

2、如果是應用的CPU飈高,那么基本上可以定位可能是鎖資源競爭,或者是頻繁GC造成的。

3、所以准備首先從GC的情況排查,如果GC正常的話再從線程的角度排查,首先使用jstat -gc PID 指令打印出GC的信息,結果得到得到的GC 統計信息有明顯的異常,應用在運行了才幾分鍾的情況下GC的時間就占用了482秒,那么問這很明顯就是頻繁GC導致的CPU飈高。

4、定位到了是GC的問題,那么下一步就是找到頻繁GC的原因了,所以可以從兩方面定位了,可能是哪個地方頻繁創建對象,或者就是有內存泄露導致內存回收不掉。

5、根據這個思路決定把堆內存信息dump下來看一下,使用jmap -dump 指令把堆內存信息dump下來(堆內存空間大的慎用這個指令否則容易導致會影響應用,因為我們的堆內存空間才2G所以也就沒考慮這個問題了)。

6、把堆內存信息dump下來后,就使用visualVM進行離線分析了,首先從占用內存最多的對象中查找,結果排名第三看到一個業務VO占用堆內存約10%的空間,很明顯這個對象是有問題的。

7、通過業務對象找到了對應的業務代碼,通過代碼的分析找到了一個可疑之處,這個業務對象是查看新聞資訊信息生成的對象,由於想提升查詢的效率,所以把新聞資訊保存到了redis緩存里面,每次調用資訊接口都是從緩存里面獲取。

8、把新聞保存到redis緩存里面這個方式是沒有問題的,有問題的是新聞的50000多條數據都是保存在一個key里面,這樣就導致每次調用查詢新聞接口都會從redis里面把50000多條數據都拿出來,再做篩選分頁拿出10條返回給前端。50000多條數據也就意味着會產生50000多個對象,每個對象280個字節左右,50000個對象就有13.3M,這就意味着只要查看一次新聞信息就會產生至少13.3M的對象,那么並發請求量只要到10,那么每秒鍾都會產生133M的對象,而這種大對象會被直接分配到老年代,這樣的話一個2G大小的老年代內存,只需要幾秒就會塞滿,從而觸發GC。

9、知道了問題所在后那么就容易解決了,問題是因為單個緩存過大造成的,那么只需要把緩存減小就行了,這里只需要把緩存以頁的粒度進行緩存就行了,每個key緩存10條作為返回給前端1頁的數據,這樣的話每次查詢新聞信息只會從緩存拿出10條數據,就避免了此問題的 產生。

8.4、CPU經常100% 問題定位

問題分析:CPU高一定是某個程序長期占用了CPU資源。

1、所以先需要找出那個進行占用CPU高。

 top  列出系統各個進程的資源占用情況。

2、然后根據找到對應進行里哪個線程占用CPU高。

 top -Hp 進程ID   列出對應進程里面的線程占用資源情況

3、找到對應線程ID后,再打印出對應線程的堆棧信息

printf "%x\n"  PID    把線程ID轉換為16進制。
 jstack PID 打印出進程的所有線程信息,從打印出來的線程信息中找到上一步轉換為16進制的線程ID對應的線程信息。

4、最后根據線程的堆棧信息定位到具體業務方法,從代碼邏輯中找到問題所在。

查看是否有線程長時間的watting 或blocked
 如果線程長期處於watting狀態下, 關注watting on xxxxxx,說明線程在等待這把鎖,然后根據鎖的地址找到持有鎖的線程。

8.5、內存飈高問題定位

分析: 內存飈高如果是發生在java進程上,一般是因為創建了大量對象所導致,持續飈高說明垃圾回收跟不上對象創建的速度,或者內存泄露導致對象無法回收。

1、先觀察垃圾回收的情況

jstat -gc PID 1000 查看GC次數,時間等信息,每隔一秒打印一次。
  
 jmap -histo PID | head -20   查看堆內存占用空間最大的前20個對象類型,可初步查看是哪個對象占用了內存。

如果每次GC次數頻繁,而且每次回收的內存空間也正常,那說明是因為對象創建速度快導致內存一直占用很高;如果每次回收的內存非常少,那么很可能是因為內存泄露導致內存一直無法被回收。

2、導出堆內存文件快照

jmap -dump:live,format=b,file=/home/myheapdump.hprof PID  dump堆內存信息到文件。

3、使用visualVM對dump文件進行離線分析,找到占用內存高的對象,再找到創建該對象的業務代碼位置,從代碼和業務場景中定位具體問題。

8.6、數據分析平台系統頻繁 Full GC

平台主要對用戶在 App 中行為進行定時分析統計,並支持報表導出,使用 CMS GC 算法。

數據分析師在使用中發現系統頁面打開經常卡頓,通過 jstat 命令發現系統每次 Young GC 后大約有 10% 的存活對象進入老年代。

原來是因為 Survivor 區空間設置過小,每次 Young GC 后存活對象在 Survivor 區域放不下,提前進入老年代。

通過調大 Survivor 區,使得 Survivor 區可以容納 Young GC 后存活對象,對象在 Survivor 區經歷多次 Young GC 達到年齡閾值才進入老年代。

調整之后每次 Young GC 后進入老年代的存活對象穩定運行時僅幾百 Kb,Full GC 頻率大大降低。

8.7、業務對接網關 OOM

網關主要消費 Kafka 數據,進行數據處理計算然后轉發到另外的 Kafka 隊列,系統運行幾個小時候出現 OOM,重啟系統幾個小時之后又 OOM。

通過 jmap 導出堆內存,在 eclipse MAT 工具分析才找出原因:代碼中將某個業務 Kafka 的 topic 數據進行日志異步打印,該業務數據量較大,大量對象堆積在內存中等待被打印,導致 OOM。

8.8、鑒權系統頻繁長時間 Full GC

系統對外提供各種賬號鑒權服務,使用時發現系統經常服務不可用,通過 Zabbix 的監控平台監控發現系統頻繁發生長時間 Full GC,且觸發時老年代的堆內存通常並沒有占滿,發現原來是業務代碼中調用了 System.gc()。

 

 



作者:極客時間
鏈接:https://www.zhihu.com/question/362201242/answer/1115831177
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。
 
 
作者:Java技術指北
鏈接:https://www.zhihu.com/question/362201242/answer/2081679563
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

1.1 Serial/Serial Old 收集器:

Serial 收集器作用於年輕代中, 采用 復制算法 , 屬於串行回收方式

Serial Old 收集器 采用串行回收, STW機制, 采用 標記-壓縮 算法 ,

"-XX:+UseSerialGC" 手動指定Serial收集器執行內存回收任務

​ "-XX:+PrintGCDetails" 年輕代串行收集器的工作日志開關

1.2 ParNew 收集器

ParnNew 可以說是Serial收集器的多線程版本, ParNew 收集器在 年輕代 中同樣才用的也是復制算法 和 STW 機制 並行回收機制。

ParnNew 收集器的優勢體現在多CPU,多核心的環境中, 在某些注重低延遲的應用場景下ParNew 和 CMS 收集器的組合模式, 在Server 模式下的內存回收效果很好。

使用 "-XX:+UserParNewGC" 手動指定使用ParNew收集器 "

-XX:+UserParallelGC" 表示 年輕代使用並行垃圾回收器, 老年代使用串行收集器

1.3 Parallel/Parallel Old 收集器

Parallel收集器是並行回收 采用復制算法 年輕代 STW , 和ParNew不同Parrllel收集器可以控制程序的吞吐量大小 , 被稱為-吞吐量優先的垃圾收集器.

Parallel Old采用標記整理算法,用於老年代的垃圾回收。

常用參數:

"-XX:+GCTimeRatio:N" 設置執行內存回收的時間所占JVM運行總時間的比例, 1/(1+N) 默認N為99

"-XX:+MaxGCPauseMills" 設置執行內存回收STW 的暫停時間閾值, 若指定該值,則盡可能地在設定的時間內完成內存回收。

"-XX:+UseAdaptiveSizePolicy" 選項用於設置GC的自動分代大小調節策略。

"-XX:UseParallelOldGC"可在年輕代和老年代都是用並行回收收集器, 此收集器重點關注吞吐量

"-XX:ParallelGCThreads" 可用於設置垃圾回收時的線程數量

Parallel Old 收集器采用了 標記-壓縮算法 , 用於老年代垃圾回收 , 並行回收 STW

Parallel 和 Parallel Old 收集器的組合 在Server 模式下的內存回收性能較好。

1.4 CMS(Concurrent-Mark-Sweep) 收集器

基於低延遲的考慮 , 是並行垃圾回收器, 而且是老年代垃圾收集, 低延遲, 采用標記清除算法。會有短暫的STW

基本步驟如下 :

1. 初始標記(Initial Mark) : STW 標記根對象直接關聯、可達的對象  
2. 並發標記(Concurrent Mark) :將不可達對象 標記為垃圾對象 
3. 再次標記(Remark) : STW 確保垃圾對象被成功且正確得標記  
4. 並發清除(Concurrent Sweep): 垃圾回收

常用參數:

"-XX:+UseCMS-CompactAtFullCollection" 用於指定在執行完FullGC 之后 是否對內存空間進行壓縮整理,

"-XX:+CMSFullGCs-BeforeCompaction" 設定在執行多少次FullGC 之后對內存空間進行壓縮整理

"-XX:+CMSInitiatingOccupanyFraction" 設置老年代中的內存使用率達到多少百分比的時候執行內存回收 JDK1.6之前默認值為68% JDK1.6 之后默認92% , CMS垃圾收集器在回收過程中程依然可能會產生垃圾,所以需要設定一個閾值來進行垃圾回收,如果CMS回收失敗,JVM則會啟動老年代串行收集器進行垃圾回收,程序的STW時間會較長, 所以可以在內存增長緩慢的程序里面設置較大閾值,在內存增長快速的程序里面設置較小的閾值, 避免觸發老年代串行收集器。

"-XX:UseConMarkSweepGC" 表示年輕代使用並行收集器,老年代使用CMS 年輕代 並行收集器工作時的線程數量可以使用 "-XX:ParallelGCThreads" 選項指定, 一般最好與CPU的數量相當.

1.5 G1(Garbage-First) 收集器

G1將整個堆划分為若干個大小相等的區間(1-32MB),Region類型分為Unused Region、Eden Region 和 Survivor Region組成了年輕代空間,Old Region, Humongous Region(里面的對象超過每個Region的50%),

每個Region都會有一個Region Set (RS),RS的數據結構是Hash表,里面的數據是CardTable (堆中沒512Byte 映射在 card table 1 byte ) , 簡單說 RS 里面存的是Region種存活對象的指針。 當Region中數據發生變化的時候 ,首先反映到Card Table 中的一個或者對個card上,RS通過掃面內部的Card Table 得知Region中內存使用情況和存活對象,在使用Region過程中,如果一個Region被填滿了,分配內存的線程會重新選擇一個新的Region,空閑Region被組織到一個基於鏈表的數據結構(LinkedList )中,這樣可以快速找到Region.

G1 GC 可以分為 Young GC 和Mixed GC

年輕代GC:當年輕代達到一定的閾值,就開始年輕代的並發收集。將Eden Region中存活對象copy轉移到Survivor的Region中,同時釋放Eden Region 。 存活的時間夠久就移到Old Reagion。

Mixed GC: 當Old Region占比達到一定的比例(通過 -XX:InitantingHeapOccupancyPercent 設置,默認45%)之后,會觸發並行標記,然后就會進行Mixed GC。 Mixed GC 對一個叫做CSet的Region集合進行垃圾回收,其中包含了所有的年輕代Region和選取的一部分回收效益最好的Old Region。

並發標記的過程類似於CMS中的標記過程。

G1 可選優化參數

  1. 年輕代優化
    -XX:G1NewSizePercent Java 堆初始化大小 ,默認是整個Java堆大小的5%,
    -XX:G1MaxNewPercent 最大占用對內存的百分比,默認是60%
    -XX:MaxGCPauseMills 每次目標停頓時間, 默認200ms

可以根據上述三個參數的調整來優化年輕代的垃圾回收

  1. 並行標記階段優化
    並行標記階段, -XX:InitantingHeapOccupancyPercent 決定了什么時候初始化並行標記循環 默認值45%
    -XX:ConcGCThreads 並發GC數量, 是-XX:ParallelGCThreads 的1/4 ,可以通過修改兩個值來改變並線程的數量。
  2. 混合回收階段優化

-XX:+PrintAdaptiveSizePoliy 開啟后會輸出完成的GC生態日志,

-XX:G1HeapWastePercent 表示最大可忍受的垃圾總量,默認是堆空間的5%,如果Mixed GC占用的時間過多,可以將此值調整大一點。

-XX:G1MixedGCCountTarget 默認為8,表示Mixed GC過程中入選的Old Region的最小閾值,用於控制入選的Old Region的數量

-XX:G1OldCSetRegionThresholdPercent 默認值100% ,表示加入到CSet中的Old Region的最大數量。

-XX:G1MixedGCLiveThresholdPercent 默認值 85% ,表示設置的每CSet中每個區間最多存活對象的百分比。

2 JVM的一些常用參數

  1. -XX:+PrintGCDetails 用於記錄GC運行時的詳細信息並輸出。
  2. -verbose:gc -Xloggc:gc.log 結合前一個選項可以在項目根目錄下生成gc.log文件記錄gc詳細信息
  3. -XX:+UseSerialGC 使用串行gc收集器,使用與小於100MB的內存空間場景
  4. -XX:+UserParNewGC 獨占式GC , 多線程GC (未來不可用)
  5. -XX:+UseParallelGC
  6. -XX:+UseParallelOldGC
  7. -XX:+UseConcMarkSweepGC
  8. -XX:+UseG1GC
  9. -XX:+PrintGCApplicationStoppedTime 輸出GC造成應用程序暫停的時間
  10. -XX:+PrintGCApplicationConcurrentTime 與上面參數結合使用
  11. -XX:+ConcGCThreads=4 設置Java應用程序線程並行執行的GC線程數量 若設置的值超過JVM允許GC並行線程的數量則報錯, 默認的並行標記線程數量計算如下 ConGCThreads = Max((ParallelGCThreads#+2)/4,1)
  12. -XX:G1HeapRegionSize G1 GC 獨有,Region大小默認為堆的1/2000 也可以設置 1MB 2MB 4MB 8MB 16MB 32MB 等6個檔次
  13. -XX:G1HeapWastePercent=5 控制G1GC 不會回收的空閑內存比例,默認是堆內存的5% G1 GC在回收過程中會回收所有Region的內存,並持續地進行回收工作,直到內存空閑比例達到次值
  14. -XX:G1MixedGCCountTarget=8 老年代Region 的回收時間通常來說比年輕帶Region回收時間長,此選項設置並行循環之后啟動多少個混合GC 默認值是 8個 設置一個比較大的值可以讓G1 GC在老年代Region回收時多花一些時間,如果一個混合GC停頓的時間很長,說明它要做的使很多,所以可以增大這個值的設置,但這個值過大的話,會造成並行循環等待混合GC完成的時間也相應的增加。
  15. -XX:G1PrintRegionLivenessInfo 開啟這個選項會在標記循環之后輸出詳細信息(診斷選項) 在使用之前需要開啟-XX:UnlockDiagnosticVMOptions 選項, 此選項會打印內存內部每個Region里面存活的對象信息, 包括使用率 RSET大小、回收一個Region的價值(性價比) 15. -XX:G1ReservePercent=10 此選項默認保留對內存的10%,用於某個對象進入下一個階段,預留內存空間不可用於年輕帶
  16. -XX:+G1SummarizeRSetStats 打印每個Region的詳細信息。 此選項和-XX:G1PrintRegionLivenessInfo選項一樣,是一個診斷選項也需要開啟 -XX:UnlockDiagnosticVMOptions 選項 。
  17. -XX:+G1TraceConcRefinement 診斷選項,啟動這個選項,並行Refinement線程相關的信息會被打印,線程啟動和結束時,信息都會被打印。
  18. -XX:G1UseAdaptiveConcRefinement 默認開啟的選項, 它會動態地對每一次GC中-XX:G1ConcRefinementGreenZone、-XX:G1ConcRfinementYellowZone、-XX:-XX:G1ConcRfinementRedZone,的值進行重新計算 並行Refinement線程是持續運行的,並且會隨着update log buffer 積累的數量而動態調節, -XX:G1ConcRefinementGreenZone、-XX:G1ConcRfinementYellowZone、-XX:-XX:G1ConcRfinementRedZone,三個選項是用來根據不同的buffer使用不同的Refinement線程,其作用就是保證Refinement線程盡可能更上update log buffer生產的的步伐
  19. -XX:GCTimeRatio=9 這個選項代表Java應用程序話費時間與GC線程花費時間的比率, 1/(1+GCTimeRatio) 默認值是9 表示花費在GC工作量上的時間占總時間的10%
  20. -XX:+HeapDumpBeforeFullGC/-XX:+HeapDumpAfterFullGC 啟用此選項 , 在Full GC開始之前有一個hprof文件會被創建, 兩個選項同時使用,可以對比Full GC前后的java堆內存,找出內存泄漏以及其他問題,
  21. -XX:InitiatingHeapOccypancyPercent=45 該選項默認值是45 表示G1 GC並行循環初始設置的堆大小值, 這個值決定了一個並行循環是不是要開始執行, 它的邏輯是在一次GC完成后,標膠老年代占用的空間和整個Java堆之間的比例,如果大於這個值,則預約下一次GC開始一個並行循環回收垃圾,從初始標記階段開始。 這個值越小,GC 越頻繁,反之 值越大,
  22. -XX:UseStringDeduplication 該選項啟動String對象的去重工作,默認不啟用。 如果啟用該選項 String1.equals(String2) 如果兩個對象包含相同的內容則返回true
  23. -XX:StringDeduplicationAgeThreshold=3 針對-XX:UseStringDeduplication選項,默認值為3 字符串對象的年齡超過設定的閾值,或者提升到G1 GC老年代Region之后,就會成為字符串去重的候選對象,去重操作只會有一次。
  24. -XX:PrintStringDeduplicationStatistics 可以通過讀取輸出的統計資料來了解是否字符串去重后節約了大量的堆內存空間,默認關閉
  25. -XX:+G1UseAdaptiveHOP JDK9的新選項 默認啟用,通過動態調節標記階段開始的時間,以達到提升應用程序吞吐量的目標, 主要通過盡可能遲地觸發標記循環方式來避免消耗老年代空間,
  26. -XX:MaxGCPauseMills=200 【重要選項】設置G1的目標停頓時間 ,單位為ms 默認值為200ms
  27. -XX:MinHeapFreeRatio=40 設置對內可以空閑的最小的內存空間大小,默認為堆內存的40% 當空閑堆內存大小小於此值的時候,需要判斷-Xms 和 -Xmx 兩個初始化設置值,如果-Xms 和 -Xmx 不一樣,那么就有機會擴展堆內存,否則就無法擴展。
  28. -X:MaxHeapFreeRatio =70 這只最大空閑空間大小,默認為堆內存的70%,當大於這個空閑比率的時候 G1 GC 會自動減少對內存的大小, 需要判斷-Xms 和 -Xmx的大小 如果兩者一樣則有機會減小堆內存,否則無法減小堆內存
  29. -XX:+PrintAdaptiveSizePolicy 這個選項決定是否開啟堆內存大小變化的相應記錄信息打印, 是否打印這些星系到GC日志里面
  30. -XX:+ResizePLAB GC使用的本地線程分配緩存塊采用動態值還是靜態值 默認開啟 31. -XX:+ResizeTLAB Java線程使用的本地線程分配緩存塊采用 動態值還是靜態值。 默認開啟
  31. -XX:+ClassUnloadingWithCncurrent 開啟G1 GC在並行循環卸載類,尤其是在老年代的並行回收階段,默認是開啟的。 這個選項開啟后會在並行循環的重標記階段卸載JVM沒有用到的類, 這些工作也可以放到Full GC 里面去做。是否開啟此選項要看性價比。 如果GC停頓時間比我們設置的最大GC停頓目標時間還長,並且需要卸載的類也不多,建議關閉此選項
  32. -XX:+ClassUnloading 默認是是True 決定JVM是否會卸載無用的類,如果關閉此選項,無論是並行回收循環還是Full GC 都不會再卸載這些類,所以需要謹慎關閉
  33. -XX:+UnloadingDiagnosticVMOptions 是否開啟診斷選項,默認值是 False
  34. -XX:+UnlockExperimentalVMOptions 默認關閉
  35. -XX:+UnlockCommercialFeatures 是都使用Oracle特有特性, 默認關閉
 
 

 

注意:不同應用的JVM調優量化目標是不一樣的。

 

 

 

在「解釋」階段,會有兩種方式把字節碼信息解釋成機器指令碼,一個是字節碼解釋器、一個是即時編譯器(JIT),你了解JVM的JIT優化技術嘛?

JIT優化技術比較出名的有兩種:方法內聯和逃逸分析,不同的JVM版本對JIT的優化都不太相同, 這里也只能算是一個參考

方法內聯:就是把「目標方法」的代碼復制到「調用的方法」中,避免發生真實的方法調用。因為每次方法調用都會生成棧幀(壓棧出棧記錄方法調用位置等等)會帶來一定的性能損耗,所以「方法內聯」的優化可以提高一定的性能。在JVM中也有相關的參數給予我們指定(-XX:MaxFreqInlineSize、-XX:MaxInlineSize)

逃逸分析:則是判斷一個對象是否被外部方法引用或外部線程訪問的分析技術,如果「沒有被引用」,就可以對其進行優化,比如說:

1. 鎖消除(同步忽略):該對象只在方法內部被訪問,不會被別的地方引用,那么就一定是線程安全的,可以把鎖相關的代碼給忽略掉

2. 棧上分配:該對象只會在方法內部被訪問,直接將對象分配在「棧」中(Java默認是將對象分配在「堆」中,是需要通過JVM垃圾回收期進行回收,需要損耗一定的性能,而棧內分配則快很多)

3. 標量替換/分離對象:當程序真正執行的時候可以不創建這個對象,而直接創建它的成員變量來代替。將對象拆分后,可以分配對象的成員變量在棧或寄存器上,原本的對象就無需分配內存空間了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM