一、JVM 調優核心思想

在深入具體方案之前,必須明確兩個核心思想:

  1. 調優的目的通常是為了解決以下問題:
  • GC 停頓時間過長:應用出現卡頓。
  • 吞吐量下降:單位時間內處理的請求變少。
  • 內存溢出:發生 OutOfMemoryError。
  • CPU 負載過高:頻繁的 GC 或線程競爭導致 CPU 資源緊張。
  1. 一切依賴於數據和場景:調優必須基於監控數據(GC 日誌、堆快照、性能分析工具),並根據應用類型(Web 服務、大數據計算、交易系統)來制定策略。

二、常用 JVM 調優參數

通常將參數分為堆內存、垃圾收集器、GC 日誌、其他性能相關幾大類。

2.1 基礎堆內存參數
# 設置初始堆大小
-Xms512m

# 設置最大堆大小  
-Xmx2048m

# 設置年輕代大小
-Xmn512m

# 設置元空間大小(Java 8+)
-XX:MetaspaceSize=128m
-XX:MaxMetaspaceSize=256m

# 設置永久代大小(Java 7及之前)
-XX:PermSize=128m
-XX:MaxPermSize=256m
  1. -Xms 和 -Xmx
  • 説明:設置堆的初始大小和最大大小。通常將它們設為相同的值,以避免在運行時動態調整堆大小帶來的性能波動。
  • 示例:-Xms4g -Xmx4g (設置堆大小為 4GB)
  1. -Xmn
  • 説明:設置年輕代的大小。整個堆 = 年輕代 + 老年代。增大年輕代會減小老年代。這個參數對 GC 性能影響很大。
  • 示例:-Xmn2g (在 4G 堆中,年輕代佔 2G,老年代佔 2G)。通常設置為整個堆大小的 1/3 到 1/2。
  1. -XX:MetaspaceSize 和 -XX:MaxMetaspaceSize
  • 説明:設置元空間的初始大小和最大大小。在 Java 8 中,永久代被移除,由元空間取代。如果動態生成類較多(如 Spring 的 CGLib),需要限制其大小以防吃光所有內存。
  • 示例:-XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m
  1. -Xss
  • 説明:設置每個線程棧的大小。如果系統有大量線程,過大的棧大小會佔用過多內存。過小則可能引起 StackOverflowError。
  • 示例:-Xss256k (對於大多數 Web 應用,256k 足夠)
2.2 堆內存比例參數
# 新生代與老年代比例(默認約1:2)
-XX:NewRatio=2

# 設置年輕代中 Eden 和 Survivor 的比例
-XX:SurvivorRatio=8        # Eden:S0:S1 = 8:1:1

# 設置晉升老年代的年齡閾值
-XX:MaxTenuringThreshold=15

# 設置大對象直接進入老年代的閾值
-XX:PretenureSizeThreshold=1m

# 設置TLAB(線程本地分配緩衝區)大小
-XX:TLABSize=64k
2.3 垃圾收集器相關

選擇並優化合適的垃圾收集器是調優的核心。

  1. 並行收集器(吞吐量優先)
  • 參數:-XX:+UseParallelGC / -XX:+UseParallelOldGC
  • 説明:JDK 8 的默認收集器,關注高吞吐量。適合後台運算、大數據處理等不需要低延遲的場景。
  • 示例:
# 使用Parallel GC
-XX:+UseParallelGC

# 設置並行GC線程數
-XX:ParallelGCThreads=4

# 設置最大GC暫停時間目標
-XX:MaxGCPauseMillis=100

# 設置吞吐量目標(GC時間與總時間比例)
-XX:GCTimeRatio=99
  1. CMS 收集器(低延遲)
  • 參數:-XX:+UseConcMarkSweepGC
  • 説明:以獲取最短停頓時間為目標,適用於 Web 服務、B/S 系統。在 JDK 14 中被移除。
  • 示例:
# 使用CMS老年代收集器
-XX:+UseConcMarkSweepGC

# 使用ParNew新生代收集器
-XX:+UseParNewGC

# CMS後台線程數
-XX:ConcGCThreads=2

# CMS觸發百分比
-XX:CMSInitiatingOccupancyFraction=75

# 開啓CMS壓縮
-XX:+UseCMSCompactAtFullCollection

# CMS FullGC前進行壓縮
-XX:CMSFullGCsBeforeCompaction=0
  1. G1 收集器(推薦)
  • 參數:-XX:+UseG1GC
  • 説明:JDK 9 及以後的默認收集器,面向服務端、大內存、低延遲應用。它將堆劃分為多個 Region,並優先回收垃圾最多的 Region。
  • 示例:
# 使用G1垃圾收集器
-XX:+UseG1GC

# 設置最大GC暫停時間目標
-XX:MaxGCPauseMillis=200

# 設置區域大小
-XX:G1HeapRegionSize=16m

# 設置併發GC線程數
-XX:ConcGCThreads=4

# 設置混合GC週期收集中最大舊區域數量
-XX:G1OldCSetRegionThresholdPercent=10
  1. ZGC 收集器(極致低延遲)
  • 參數:-XX:+UseZGC
  • 説明:新一代的低延遲垃圾收集器,目標是將停頓時間控制在 10ms 以下。適用於對延遲極其敏感的應用(如金融交易系統)。
  • 示例:
# 啓用 ZGC
-XX:+UseZGC

# 調優參數
-XX:ZAllocationSpikeTolerance=2.0  # 分配峯值容忍度
-XX:ZCollectionInterval=300        # 強制GC間隔(秒)
-XX:ZFragmentationLimit=10         # 碎片化限制
-XX:ZProactive=true                # 啓用主動GC

# 內存映射調優
-XX:ZUncommitDelay=300             # 內存未使用回收延遲(秒)
2.3 GC 日誌相關

沒有日誌,調優就是盲人摸象。必須開啓詳細的 GC 日誌。

  • 詳細 GC 日誌(JDK 8 及之前)
# 開啓GC日誌
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps

# 輸出GC日誌到文件
-Xloggc:/path/to/gc.log

# 開啓GC日誌輪轉
-XX:+UseGCLogFileRotation
-XX:NumberOfGCLogFiles=5
-XX:GCLogFileSize=10M
  • 統一日誌框架(JDK 9+ 推薦)
# 詳細 GC 日誌
-Xlog:gc*=debug:file=/app/logs/gc-%t.log:time,uptime,level,tags:filecount=10,filesize=100M

# 選擇性日誌(生產環境推薦)
-Xlog:gc=info:file=/app/logs/gc.log:time,uptimemillis,level,tags
-Xlog:gc+heap=debug
-Xlog:gc+ergo*=trace
-Xlog:gc+age*=debug
  • 高級監控參數
# 打印命令行參數
-XX:+PrintCommandLineFlags

# 打印GC應用停頓時間
-XX:+PrintGCApplicationStoppedTime

# 打印GC前後堆信息
-XX:+PrintHeapAtGC

# 開啓類加載日誌
-XX:+TraceClassLoading
-XX:+TraceClassUnloading

# 在發生 OOM 時自動生成堆轉儲文件,用於事後分析
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/heapdump.hprof
2.4 JIT 編譯與性能優化
# 分層編譯(JDK 8 默認)
-XX:+TieredCompilation

# 編譯閾值調優
-XX:Tier0InvokeNotifyFreqLog=7
-XX:Tier2BackEdgeThreshold=100000
-XX:Tier3InvocationThreshold=2000
-XX:Tier3MinInvocationThreshold=100

# 代碼緩存大小
-XX:ReservedCodeCacheSize=512m
-XX:InitialCodeCacheSize=64m

# 方法內聯優化
-XX:MaxInlineSize=325
-XX:FreqInlineSize=325
-XX:InlineSmallCode=1000

三、不同場景的配置模板

3.1 開發環境配置
java -Xms512m -Xmx1g \
     -Xmn256m \
     -XX:MetaspaceSize=128m \
     -XX:MaxMetaspaceSize=256m \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=200 \
     -XX:+PrintGC \
     -XX:+PrintGCDateStamps \
     -Xloggc:./logs/gc.log \
     -jar app.jar
3.2 測試環境配置
java -Xms2g -Xmx4g \
     -Xmn1g \
     -XX:MetaspaceSize=256m \
     -XX:MaxMetaspaceSize=512m \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=150 \
     -XX:ParallelGCThreads=4 \
     -XX:ConcGCThreads=2 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=./dumps/ \
     -XX:+PrintGCDetails \
     -XX:+PrintGCDateStamps \
     -Xloggc:./logs/gc.log \
     -jar app.jar
3.3 生產環境配置
java -Xms4g -Xmx8g \
     -Xmn2g \
     -XX:MetaspaceSize=512m \
     -XX:MaxMetaspaceSize=1g \
     -XX:+UseG1GC \
     -XX:MaxGCPauseMillis=100 \
     -XX:ParallelGCThreads=8 \
     -XX:ConcGCThreads=4 \
     -XX:G1ReservePercent=15 \
     -XX:InitiatingHeapOccupancyPercent=35 \
     -XX:+HeapDumpOnOutOfMemoryError \
     -XX:HeapDumpPath=/data/dumps/ \
     -XX:+PrintGCDetails \
     -XX:+PrintGCDateStamps \
     -XX:+UseGCLogFileRotation \
     -XX:NumberOfGCLogFiles=10 \
     -XX:GCLogFileSize=10M \
     -Xloggc:/data/logs/gc.log \
     -jar app.jar
3.4 Docker容器配置
FROM openjdk:8-jre

# 設置JVM參數
ENV JAVA_OPTS="-Xms1g -Xmx2g -XX:+UseG1GC"

# 使用容器感知的JVM
ENV JAVA_TOOL_OPTIONS="-XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap"

CMD java $JAVA_OPTS -jar app.jar

四、實戰調優示例

4.1 電商交易系統調優案例

場景特點:

  • 高併發、低延遲要求
  • 內存使用模式:中等對象分配率,長生命週期對象較多
  • 峯值 QPS:10,000+
  • 服務器配置:16核32G

調優方案:

#!/bin/bash
java -server \
  # 堆內存設置
  -Xms16g -Xmx16g \
  -Xmn8g \
  -XX:SurvivorRatio=8 \
  -XX:MaxTenuringThreshold=10 \
  
  # G1 收集器調優
  -XX:+UseG1GC \
  -XX:MaxGCPauseMillis=100 \
  -XX:InitiatingHeapOccupancyPercent=40 \
  -XX:G1HeapRegionSize=16m \
  -XX:G1ReservePercent=15 \
  -XX:G1MixedGCCountTarget=16 \
  
  # 元空間與棧
  -XX:MetaspaceSize=512m \
  -XX:MaxMetaspaceSize=512m \
  -Xss512k \
  
  # 性能優化
  -XX:+TieredCompilation \
  -XX:ReservedCodeCacheSize=512m \
  -XX:+UseStringDeduplication \
  
  # 監控與診斷
  -XX:+HeapDumpOnOutOfMemoryError \
  -XX:HeapDumpPath=/app/logs/heapdump.hprof \
  -Xlog:gc=info:file=/app/logs/gc.log:time,uptime,level,tags:filecount=10,filesize=100M \
  -XX:ErrorFile=/app/logs/hs_err_pid%p.log \
  
  # 應用JAR
  -jar ecommerce-app.jar
4.2 大數據處理應用調優

場景特點:

  • 高吞吐量要求,延遲不敏感
  • 大內存使用,頻繁創建臨時對象
  • 批量數據處理

調優方案:

#!/bin/bash
java -server \
  # 大堆內存設置
  -Xms32g -Xmx32g \
  -Xmn24g \
  -XX:SurvivorRatio=6 \
  
  # 並行收集器(吞吐量優先)
  -XX:+UseParallelGC \
  -XX:+UseParallelOldGC \
  -XX:ParallelGCThreads=16 \
  -XX:GCTimeRatio=99 \
  -XX:MaxGCPauseMillis=500 \
  
  # 大對象處理
  -XX:PretenureSizeThreshold=2m \
  -XX:+AlwaysPreTouch \
  
  # 性能優化
  -XX:+UseLargePages \
  -XX:LargePageSizeInBytes=2m \
  -XX:+UseCompressedOops \
  -XX:+UseCompressedClassPointers \
  
  # 監控配置
  -Xlog:gc*=info:file=/app/logs/gc.log:time,uptime,level,tags \
  -XX:NativeMemoryTracking=detail \
  
  -jar data-processing-app.jar

五、監控與診斷工具實戰

5.1 命令行工具使用示例
# 1. 實時監控 GC 情況
jstat -gc <pid> 1s

# 2. 查看堆內存摘要
jmap -heap <pid>

# 3. 生成堆轉儲
jmap -dump:live,format=b,file=heap.hprof <pid>

# 4. 查看類加載統計
jstat -class <pid> 1s

# 5. 線程分析
jstack <pid> > thread_dump.txt

# 6. 原生內存跟蹤
jcmd <pid> VM.native_memory summary

# 7. 全面的診斷信息
jcmd <pid> PerfCounter.print
5.2 自動化監控腳本
#!/bin/bash
# monitor_jvm.sh

PID=$1
LOG_DIR="/app/monitor"
INTERVAL=30

while true; do
    TIMESTAMP=$(date +%Y%m%d_%H%M%S)
    
    # GC 統計
    jstat -gc $PID > $LOG_DIR/gc_$TIMESTAMP.log 2>&1
    
    # 線程轉儲(每5分鐘一次)
    if [ $(($(date +%s) % 300)) -eq 0 ]; then
        jstack $PID > $LOG_DIR/thread_dump_$TIMESTAMP.log 2>&1
    fi
    
    # 內存使用
    ps -p $PID -o pid,ppid,pmem,pcpu,rsz,vsz,comm >> $LOG_DIR/memory_$TIMESTAMP.log 2>&1
    
    sleep $INTERVAL
done