手把手教你一次GC調(diào)優(yōu)!
你知道的越多,不知道的就越多,業(yè)余的像一棵小草!
你來(lái),我們一起精進(jìn)!你不來(lái),我和你的競(jìng)爭(zhēng)對(duì)手一起精進(jìn)!
編輯:業(yè)余草
chenxiao.blog.csdn.net
推薦:https://www.xttblog.com/?p=5300
JVM參數(shù)調(diào)優(yōu)
一、調(diào)優(yōu)基本概念
在調(diào)整性能時(shí),JVM 有三個(gè)組件。
堆大小調(diào)整 垃圾收集器調(diào)整 JIT 編譯器調(diào)整
大多數(shù)調(diào)優(yōu)選項(xiàng)都與調(diào)整堆大小和選擇的垃圾收集器有關(guān)。
同樣,JIT 編譯器對(duì)性能也有很大影響,但是這個(gè)對(duì)程序員自身要求非常高。
通常,在調(diào)優(yōu) Java 應(yīng)用程序時(shí),重點(diǎn)是以下兩個(gè)主要目標(biāo)之一:
響應(yīng)性:應(yīng)用程序或系統(tǒng)對(duì)請(qǐng)求的數(shù)據(jù)進(jìn)行響應(yīng)的速度,對(duì)于專注于響應(yīng)性的應(yīng)用程序,長(zhǎng)的暫停時(shí)間是不可接受的,重點(diǎn)是在短時(shí)間內(nèi)做出回應(yīng)。 吞吐量:側(cè)重于在特定時(shí)間段內(nèi)最大化應(yīng)用程序的工作量,對(duì)于專注于吞吐量的應(yīng)用程序,高暫停時(shí)間是可接受的。
一般而言,系統(tǒng)瓶頸核心還是在應(yīng)用代碼,一般情況下無(wú)需過(guò)多調(diào)優(yōu),而且 JVM 本身在不斷優(yōu)化的過(guò)程。
二、常用 JVM 參數(shù)
文章圍繞 Java8,下面的一些參數(shù),在 JDK9 之后就被淘汰了,加紅進(jìn)行了凸顯。
| 參數(shù) | 說(shuō)明 |
|---|---|
| -XX:+AlwaysPreTouch | JVM啟動(dòng)時(shí)分配內(nèi)存,非使用時(shí)再分配 |
| -XX:ErrorFile= filename | 崩潰日志 |
| -XX:+TraceClassLoading | 跟蹤類加載信息 |
| -XX:+PrintClassHistogram | 按下Ctr+ Break后,打印類的信息 |
| -Xmx -Xms | 最大堆和最小堆 |
| -xx:permSize, -xx:metaspaceSize | 永久代/元數(shù)據(jù)空間 |
| -XX:+HeapDumpOnOutOfMemoryError | 0OM時(shí)導(dǎo)出堆到文件 |
| -XX:+HeapDumpPath | OOM時(shí)堆導(dǎo)出的路徑 |
| -XX:+OnOutOfMemoryError | JVM啟動(dòng)時(shí)分配內(nèi)存,非使用時(shí)再分配在OOM時(shí),執(zhí)行一個(gè)腳本 |
java-XX:+PrintFlagsFinal?-version??打印所有的-XX參數(shù)和默認(rèn)值
「通用GC參數(shù)」
| 參數(shù) | 說(shuō)明 |
|---|---|
| -XX:ParallelGCThread | 并行GC線程數(shù)量 |
| -XX:ConcGCThreads | 并發(fā)GC線程數(shù)量 |
| -XX:MaxGCPauseMillis | 最大停頓時(shí)間,單位毫秒。GC盡力保證回收時(shí)間不超過(guò)設(shè)定值 |
| -XX:GCTimeRatio | 0-100的取值范圍,表示垃圾收集時(shí)間占總時(shí)間的比,默認(rèn)99,即最大允許1%時(shí)間進(jìn)行GC |
| -XX. SurvivorRatio | 設(shè)置Eden區(qū)大小和 Survivor區(qū)大小的比例, 8表示兩個(gè) Survivor:Eden=2:8,即一個(gè) Survivor占年輕代的1/10 |
| -XX:NewRatio | 新生代和老年代的比,4表示新生代老年代=1:4,即年輕代占堆的1/5 |
| -verbose:gc, -XX;+printGC | 打印GC的簡(jiǎn)要信息 |
-XX:+PrintGCDetails | 打印GC詳細(xì)信息 |
-XX:+PrintGCTimeStamps | 打印CG發(fā)生的時(shí)間戳 |
| -Xloggc:log/gc.log | 指定 GC log的位置,以文件輸出 |
| -XX:ParallelGCThread | 每次一次GC后,都打印堆信息 |
三、GC調(diào)優(yōu)思路
「首先準(zhǔn)備環(huán)境」
創(chuàng)建一個(gè)springboot的空項(xiàng)目,在啟動(dòng)類部分添加一下代碼,打包,放在阿里云服務(wù)器上運(yùn)行。
測(cè)試環(huán)境:JVM配置為1核2G,JAVA8,固定設(shè)置堆大小 1G
import?org.springframework.boot.SpringApplication;
import?org.springframework.boot.autoconfigure.SpringBootApplication;
import?java.util.Random;
import?java.util.concurrent.Executors;
import?java.util.concurrent.TimeUnit;
@SpringBootApplication
public?class?DemoApplication?{
????public?static?void?main(String[]?args)?{
????????SpringApplication.run(DemoApplication.class,?args);
????????//?每333毫秒創(chuàng)建150線程,每個(gè)線程創(chuàng)建一個(gè)512kb的對(duì)象,最多一秒同時(shí)存在450線程,占用內(nèi)存225m,查看6C的情況
????????Executors.newScheduledThreadPool(1).scheduleAtFixedRate(()?->?{
????????????new?Thread(()?->?{
????????????????for?(int?i?=?0;?i?150;?i++)?{
????????????????????try?{
????????????????????????//??不干活,專門創(chuàng)建512kb的小對(duì)象
????????????????????????byte[]?temp?=?new?byte[1024?*?512];
????????????????????????Thread.sleep(new?Random().nextInt(100));?//?隨機(jī)睡眠200毫秒秒以內(nèi)
????????????????????}?catch?(InterruptedException?e)?{
????????????????????????e.printStackTrace();
????????????????????}
????????????????}
????????????}).start();
????????},?1000,?333,?TimeUnit.MILLISECONDS);
????}
}
在運(yùn)行之前要記得指定一下堆的大小
java?-Xmx1024m?-jar?xxx.jar
啟動(dòng)是正常的。

上面已經(jīng)能看到端口號(hào)了,但是我們這里用 jcmd 象征的看一下。

我們看一下堆的使用情況。

里面的數(shù)據(jù)都是動(dòng)態(tài)的。
我們看一下調(diào)優(yōu)的思路。
分析場(chǎng)景。例如:?jiǎn)?dòng)速度慢;偶爾出現(xiàn)響應(yīng)慢于平均水平或者出現(xiàn)卡頓 確定目標(biāo)。內(nèi)存占用、低延時(shí)、吞吐量 收集日志。通過(guò)參數(shù)配置收集GC日志;通過(guò)JDK工具查看GC狀態(tài)(例如jstat 實(shí)時(shí)查看) 分析日志。使用工具輔助分析日志,查看GC次數(shù),GC時(shí)間(事后分析) 調(diào)整參數(shù)。切換垃圾收集器或者調(diào)整垃圾收集器參數(shù)
前兩點(diǎn)的話,要和具體場(chǎng)景結(jié)合起來(lái)。文章主要展示整個(gè)過(guò)程該怎么走(相當(dāng)于工具說(shuō)明書),具體問(wèn)題要具體分析。
java?-Xmx1024m?-Xloggc:/opt/gc.log?-jar?xxx.jar
我們這里添加了日志的收集。

我們?nèi)?duì)應(yīng)目錄找一下日志,里面的參數(shù)我們都介紹過(guò),這個(gè)不難看懂。

但是文件內(nèi)容是非常非常多的,這樣一點(diǎn)點(diǎn)的看可能有點(diǎn)困難,我們一邊把文件下載下來(lái),用 GCViewer 這個(gè)開源的工具來(lái)做這件事情。
GCViewer 工具,輔助分析 GC 日志文件https://github.com/chewiebug/GCViewer。

打開下載到本地的gc.log文件。

再看右下角的信息,主要是一些匯總信息。各種數(shù)據(jù)的分析,比如平均,最大,最小等。



至于首頁(yè)的圖形,怎么看,可以直接參考文檔:https://github.com/chewiebug/GCViewer。
紅線意思是什么等等,都寫的很詳細(xì)。

下面看一下 jstat 動(dòng)態(tài)監(jiān)控 GC 統(tǒng)計(jì)信息,采用的格式是間隔 1000 毫秒統(tǒng)計(jì)一次,每 10 行數(shù)據(jù)后輸出列標(biāo)題。
jstat?-gc?-h10?$(jcmd?|?grep?"demo-0.0.1-SNAPSHOT.jar"?|?awk?'{print?$1}')?1000
里面的參數(shù),我們說(shuō)過(guò)了,可以參考我的歷史文章:

下面看一下切換垃圾收集器或者調(diào)整垃圾收集器參數(shù)相關(guān)信息:
「垃圾收集器 Parallel參數(shù)調(diào)優(yōu)」
這是JDK默認(rèn)的收集器–吞吐量?jī)?yōu)先
| 參數(shù) | 說(shuō)明 |
|---|---|
| -XX:+UseParallelGC | 新生代使用并行回收收集器 |
| -XX:+Use ParalleloldGC | 老年代使用并行回收收集器 |
| -XX:ParallelGCThreads | 設(shè)置用于垃圾回收的線程數(shù) |
| -XX:+UseAdaptiveSizePolicy | 打開自適應(yīng)GC策略 |
自適應(yīng)GC策略是指自動(dòng)調(diào)整分代分區(qū)的大小。UseAdaptiveSizePolicy自適應(yīng)默認(rèn)開啟。
「垃圾收集器CMS參數(shù)調(diào)優(yōu)」
關(guān)于CMS
響應(yīng)時(shí)間優(yōu)先2 Parallel gc無(wú)法滿足應(yīng)用程序延遲要求時(shí)再考慮使用CMS垃圾收集器。 新版建議用G1垃圾收集器

「垃圾收集器G1參數(shù)調(diào)優(yōu)」
關(guān)于G1:
兼顧吞吐量和響應(yīng)時(shí)間 超過(guò)50%的Java堆被實(shí)時(shí)數(shù)據(jù)占用。 建議大堆(大小約為6GB或更大) 且GC延遲要求有限的應(yīng)用(穩(wěn)定且可預(yù)測(cè)的暫停時(shí)間低于0.5秒)。

「運(yùn)行時(shí)JIT編譯器優(yōu)化參數(shù)」
JIT編譯指的是字節(jié)碼編譯為本地代碼(匯編)執(zhí)行,只有熱點(diǎn)代碼才會(huì)編譯為本地代碼。解釋器執(zhí)行節(jié)約內(nèi)存,反之可以使用編譯執(zhí)行來(lái)提升效率。

最后由于版本不斷更新,JVM 參數(shù)和具體說(shuō)明,建議需要時(shí)參考 oracle 官網(wǎng)的手冊(cè)。
說(shuō)實(shí)話,如果沒有一個(gè)具體的線上場(chǎng)景,很難去演示調(diào)優(yōu)的過(guò)程,這里能做到的就是,給出調(diào)優(yōu)的思路、相關(guān)工具的使用,以及調(diào)優(yōu)用到的參數(shù)信息。
參數(shù)的調(diào)整多少都會(huì)對(duì) JVM 的運(yùn)行有影響,比如 Parallel 參數(shù)調(diào)優(yōu)中-XX:ParallelGCThreads設(shè)置線程的數(shù)量,這個(gè)在不同配置的服務(wù)器上測(cè)試得到的結(jié)果會(huì)有差別,你可以在自己服務(wù)器上試試看。
