JVM參數配置
在我們整個JVM調優中,JVM的參數配置也必不可少,當我們使用給定的一些參數啓動JVM,就可以在系統運行時打印相關日誌,有利於出現分析實際問題。
-XX:+PrintGC
:使用這個參數,虛擬機啓動後,只要遇到GC就會打印日誌。其中-XX
說明增加配置,+
代表啓用配置,如果不寫或者寫減號代表不啓用配置-XX:+UseSerialGC
:配置串行回收器,垃圾回收會有單獨的一個線程去負責垃圾回收,串行垃圾回收器是垃圾回收中的一種。-XX:+PrintGCDetails
:打印GC詳細信息,包括各個區的情況-Xms
:設置java程序啓動時初始堆大小-Xmx
:設置java程序能獲得的最大堆大小-XX:+PrintCommandLineFlags
:可以將隱式或者顯示傳給虛擬機的參數輸出-XX:+HeapDumpOnOutOfMemoryError
:發生OOM時生成dump文件-XX:HeapDumpPath=/You/path
:生成的dump文件存放路徑
在實際工作中,我們可以直接將初始的堆大小與最大堆大小設置相等, 這樣的好處是可以減少程序運行時的垃圾回收次數,從而提高性能。
1. Heap內存分配測試
- 編寫測試類
public class JvmMemoryDistributeDemo {
public static void main(String[] args) {
// 運行時參數:-Xms5m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
// 查看GC信息
System.err.println("maxMemory:"+Runtime.getRuntime().maxMemory());
System.err.println("freeMemory:"+Runtime.getRuntime().freeMemory());
System.err.println("totalMemory:"+Runtime.getRuntime().totalMemory());
byte[] bytes = new byte[1 * 1024 * 1024];
System.err.println("*************分配了1MB的內存*************");
System.err.println("maxMemory:"+Runtime.getRuntime().maxMemory());
System.err.println("freeMemory:"+Runtime.getRuntime().freeMemory());
System.err.println("totalMemory:"+Runtime.getRuntime().totalMemory());
byte[] bytes4 = new byte[4 * 1024 * 1024];
System.err.println("*************分配了4MB的內存*************");
System.err.println("maxMemory:"+Runtime.getRuntime().maxMemory());
System.err.println("freeMemory:"+Runtime.getRuntime().freeMemory());
System.err.println("totalMemory:"+Runtime.getRuntime().totalMemory());
}
}
- 配置運行時JVM參數
- 在方法上鼠標右鍵,選擇修改配置
- 添加JVM參數
- 應用保存
- 運行結果
# 打印JVM配置參數
[0.004s][warning][gc] -XX:+PrintGCDetails is deprecated. Will use -Xlog:gc* instead.
-XX:InitialHeapSize=5242880 -XX:MaxHeapSize=20971520 -XX:+PrintCommandLineFlags -XX:+PrintGCDetails -XX:ReservedCodeCacheSize=251658240 -XX:+SegmentedCodeCache -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseSerialGC
# 使用串行收集器
[0.010s][info ][gc] Using Serial
# 堆空間地址,大小爲20MB
[0.010s][info ][gc,heap,coops] Heap address: 0x00000007fec00000, size: 20 MB, Compressed Oops mode: Zero based, Oop shift amount: 3
# 第一次GC ,是Young GC ,由於分配失敗 ,執行暫停年輕代
[0.173s][info ][gc,start ] GC(0) Pause Young (Allocation Failure)
# 串行收集 ,收集前爲1640K,收集後爲192K
[0.174s][info ][gc,heap ] GC(0) DefNew: 1640K->192K(1856K)
# 老年代收集前爲0K,收集後爲679K
[0.174s][info ][gc,heap ] GC(0) Tenured: 0K->679K(4096K)
# 元數據區沒有變化
[0.174s][info ][gc,metaspace ] GC(0) Metaspace: 5691K->5691K(1056768K)
# 本次GC共收集了1MB的垃圾,耗時1.946ms
[0.174s][info ][gc ] GC(0) Pause Young (Allocation Failure) 1M->0M(5M) 1.946ms
# User:進程在用戶態(User Mode)所花費的時間,只統計本進程所使用的時間,注意是指多核
# Sys:進程在覈心態(Kernel Mode)花費的CPU時間量,指的是內核中的系統調用所花費的時間,只統計本進程所使用的時間
# Real:從開始到結束所花費的時間
[0.174s][info ][gc,cpu ] GC(0) User=0.01s Sys=0.00s Real=0.00s
# 第二次GC,Young GC
[0.191s][info ][gc,start ] GC(1) Pause Young (Allocation Failure)
[0.193s][info ][gc,heap ] GC(1) DefNew: 1762K->192K(1856K)
[0.193s][info ][gc,heap ] GC(1) Tenured: 679K->1985K(4096K)
[0.193s][info ][gc,metaspace ] GC(1) Metaspace: 5759K->5759K(1056768K)
[0.193s][info ][gc ] GC(1) Pause Young (Allocation Failure) 2M->2M(5M) 1.616ms
[0.193s][info ][gc,cpu ] GC(1) User=0.00s Sys=0.00s Real=0.01s
# 第三次GC,Young GC
[0.202s][info ][gc,start ] GC(2) Pause Young (Allocation Failure)
maxMemory:20316160
freeMemory:3621424
totalMemory:6094848
*************分配了1MB的內存*************
maxMemory:20316160
freeMemory:2572832
totalMemory:6094848
*************分配了4MB的內存*************
maxMemory:20316160
freeMemory:4847976
totalMemory:12546048
# 第四次GC,發生Full GC
[0.203s][info ][gc,start ] GC(3) Pause Full (Allocation Failure)
# 第一階段,標記存活的的對象
[0.203s][info ][gc,phases,start] GC(3) Phase 1: Mark live objects
[0.204s][info ][gc,phases ] GC(3) Phase 1: Mark live objects 1.419ms
# 第二階段,計算新對象地址
[0.204s][info ][gc,phases,start] GC(3) Phase 2: Compute new object addresses
[0.204s][info ][gc,phases ] GC(3) Phase 2: Compute new object addresses 0.221ms
# 第三階段,調整指針
[0.204s][info ][gc,phases,start] GC(3) Phase 3: Adjust pointers
[0.205s][info ][gc,phases ] GC(3) Phase 3: Adjust pointers 0.737ms
# 第四階段,移動對象
[0.205s][info ][gc,phases,start] GC(3) Phase 4: Move objects
[0.205s][info ][gc,phases ] GC(3) Phase 4: Move objects 0.083ms
[0.205s][info ][gc ] GC(3) Pause Full (Allocation Failure) 3M->3M(5M) 2.565ms
[0.205s][info ][gc,heap ] GC(2) DefNew: 1453K->0K(1856K)
[0.205s][info ][gc,heap ] GC(2) Tenured: 1985K->3353K(4096K)
[0.205s][info ][gc,metaspace ] GC(2) Metaspace: 6129K->6129K(1056768K)
[0.205s][info ][gc ] GC(2) Pause Young (Allocation Failure) 3M->3M(7M) 3.603ms
[0.205s][info ][gc,cpu ] GC(2) User=0.01s Sys=0.00s Real=0.00s
# 所有GC完成,堆內存變化情況
[0.208s][info ][gc,heap,exit ] Heap
# 年輕代
[0.208s][info ][gc,heap,exit ] def new generation total 2560K, used 91K [0x00000007fec00000, 0x00000007feec0000, 0x00000007ff2a0000)
# 年輕代的eden區
[0.208s][info ][gc,heap,exit ] eden space 2304K, 3% used [0x00000007fec00000, 0x00000007fec16c48, 0x00000007fee40000)
# 年輕代的from區
[0.208s][info ][gc,heap,exit ] from space 256K, 0% used [0x00000007fee40000, 0x00000007fee40000, 0x00000007fee80000)
# 年輕代的to區
[0.208s][info ][gc,heap,exit ] to space 256K, 0% used [0x00000007fee80000, 0x00000007fee80000, 0x00000007feec0000)
# 老年代
[0.208s][info ][gc,heap,exit ] tenured generation total 9692K, used 7449K [0x00000007ff2a0000, 0x00000007ffc17000, 0x0000000800000000)
[0.208s][info ][gc,heap,exit ] the space 9692K, 76% used [0x00000007ff2a0000, 0x00000007ff9e67f0, 0x00000007ff9e6800, 0x00000007ffc17000)
# 元數據區
[0.208s][info ][gc,heap,exit ] Metaspace used 6241K, capacity 6319K, committed 6528K, reserved 1056768K
# 壓縮空間,即Compressed class space
[0.208s][info ][gc,heap,exit ] class space used 536K, capacity 570K, committed 640K, reserved 1048576K
2.內存溢出測試
- 編寫測試代碼
public class JvmOutOfMemoryDemo {
// 一直持有,讓GC釋放不掉
private static final List<byte[]> holderList = new ArrayList<>();
public static void main(String[] args) {
// TODO 第一次測試
// 分配最小內存和最大內存一直,設置串行收集
// -Xms20m -Xmx20m -XX:+PrintGCDetails -XX:+UseSerialGC -XX:+PrintCommandLineFlags
// 導致多次GC,甚至Full GC,最終拋出
// Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
// Exception in thread "Monitor Ctrl-Break" java.lang.OutOfMemoryError: Java heap space
while (true){
byte[] bytes = new byte[1 * 1024];
// 持續添加1MB的字節數組,導致內存溢出
holderList.add(bytes);
}
}
}
- 運行結果
# 這裏省略N次GC日誌,直接看堆信息
[0.305s][info ][gc,heap,exit ] Heap
[0.305s][info ][gc,heap,exit ] def new generation total 6144K, used 6141K [0x00000007fec00000, 0x00000007ff2a0000, 0x00000007ff2a0000)
[0.305s][info ][gc,heap,exit ] eden space 5504K, 99% used [0x00000007fec00000, 0x00000007ff15fff8, 0x00000007ff160000)
[0.305s][info ][gc,heap,exit ] from space 640K, 99% used [0x00000007ff160000, 0x00000007ff1ff5a0, 0x00000007ff200000)
[0.305s][info ][gc,heap,exit ] to space 640K, 0% used [0x00000007ff200000, 0x00000007ff200000, 0x00000007ff2a0000)
[0.305s][info ][gc,heap,exit ] tenured generation total 13696K, used 13695K [0x00000007ff2a0000, 0x0000000800000000, 0x0000000800000000)
[0.305s][info ][gc,heap,exit ] the space 13696K, 99% used [0x00000007ff2a0000, 0x00000007ffffff60, 0x0000000800000000, 0x0000000800000000)
[0.305s][info ][gc,heap,exit ] Metaspace used 6265K, capacity 6315K, committed 6528K, reserved 1056768K
[0.305s][info ][gc,heap,exit ] class space used 543K, capacity 570K, committed 640K, reserved 1048576K
# 最終拋出內存溢出異常
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread "main"
Exception in thread "Monitor Ctrl-Break" java.lang.OutOfMemoryError: Java heap space
- 增加如下配置,再次測試
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/yunnasheng/Desktop
- +HeapDumpOnOutOfMemoryError:發生OOM時生成dump文件
- HeapDumpPath=/Users/yunnasheng/Desktop:生成dump文件路徑地址 運行程序,查看生成的文件會發現有個後綴爲
.hprof
的文件,這個文件就是dump文件
yunnasheng@yunnashengdeMacBook-Pro ~/Desktop pwd
/Users/yunnasheng/Desktop
yunnasheng@yunnashengdeMacBook-Pro ~/Desktop ls
java_pid9167.hprof
3.分析dump文件
利用Eclipse
的Memory Analyze
插件分析dump文件
- 打開dump文件 下載好插件以後,把 dump文件——
java_pid9167.hprof
拖拽到我們項目的resources
目錄下
2. 打開後的界面如下
從圖中內容可看出,總共20MB的內存,a疑點就佔用了17.1MB,內存泄露的疑點可能就在這裏。
直方圖
- 點擊左上角的
histogram
按鈕如圖所示,可看出有byte[]
1900萬個對象
2. 合併GC Roots 因爲只有強引用纔會導致GC無法釋放空間,最終導致OOM。所以我們只需要關注強引用的數據。
- 過濾數據
- 點開看詳情 發現有一個
com.lb.gc.JvmOutOfMemoryDemo
類下的holderList java.util.ArrayList
有1萬7千多個引用並且佔用了1700多萬個堆。 因爲這個list一直沒有被釋放,所以會導致應用程序發生 OOM
樹形圖
- 同時我們也可以使用樹形圖來查看佔用率,可以看到這個
class com.lb.gc.JvmOutOfMemoryDemo @ 0x7ff614e28
佔用率是非常高的,達到 88.75%
一般情況下我們不會等到內存溢出之後,纔去分析內存溢出的原因和問題, 都會進行定時的巡檢,這就需要使用jmap命令進行導出了
實時監控導出
jmap
jmap -heap pid
來查看運行時的JVM配置
jdk1.8以上,需要用
jhsdb jmap --heap --pid 36362
代替jmap -heap pid
命令hsdb
是HotSpot Debugger的簡稱
由於Mac系統下執行jmap有好多問題,這裏就用Linux來演示了
- 查看JVM配置
jmap -heap 2058
[lb@centos-linux ~]$ jmap -heap 2058
Attaching to process ID 2058, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.271-b09
using thread-local object allocation.
Parallel GC with 2 thread(s)
Heap Configuration:
MinHeapFreeRatio = 0
MaxHeapFreeRatio = 100
MaxHeapSize = 1048576000 (1000.0MB)
NewSize = 10485760 (10.0MB)
MaxNewSize = 349175808 (333.0MB)
OldSize = 20971520 (20.0MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
PS Young Generation
Eden Space:
capacity = 8388608 (8.0MB)
used = 3422128 (3.2635955810546875MB)
free = 4966480 (4.7364044189453125MB)
40.794944763183594% used
From Space:
capacity = 1048576 (1.0MB)
used = 1032320 (0.9844970703125MB)
free = 16256 (0.0155029296875MB)
98.44970703125% used
To Space:
capacity = 1048576 (1.0MB)
used = 0 (0.0MB)
free = 1048576 (1.0MB)
0.0% used
PS Old Generation
capacity = 20971520 (20.0MB)
used = 6143672 (5.859062194824219MB)
free = 14827848 (14.140937805175781MB)
29.295310974121094% used
5304 interned Strings occupying 409096 bytes.
[lb@centos-linux ~]$
- 導出dump文件
jmap -dump:format=b,file=app.hprof 2058
[lb@centos-linux ~]$ jmap -dump:format=b,file=app.hprof 2058
Dumping heap to /home/lb/app.hprof ...
Heap dump file created
[lb@centos-linux ~]$ ls
app.hprof libs servers soft
[lb@centos-linux ~]$