JVM 隨著 Oracle 的快速發(fā)布策略红选,會經(jīng)常發(fā)生變化腰池。所以在一些版本行之有效的參數(shù)名眉,可能在另一些新的版本里并不起作用弦讽∥畚荆可以通過這個命令查看當前 JVM 默認的參數(shù)配置
java -XX:+PrintFlagsFinal
但是 JVM 的參數(shù)配置項很多,長長的列表上坦袍,很多配置項不需要我們修改十厢。所以可以過濾并只關(guān)注我們需要的參數(shù)等太。
還有一個命令可用
java -XX:+PrintCommandLineFlags -version
gaopengdeMacBook-Pro-2:script gaopeng$ java -XX:+PrintCommandLineFlags -version
-XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
java version "1.8.0_221"
Java(TM) SE Runtime Environment (build 1.8.0_221-b11)
Java HotSpot(TM) 64-Bit Server VM (build 25.221-b11, mixed mode)
可以看到 JVM 采用了并行收集器捂齐,堆內(nèi)存的初始化/最大值配置等。
一 gc 日志
線上機器的 GC 日志缩抡,需要在運行時加入相關(guān)的 JVM 參數(shù)奠宜,然后得到一個時間周期內(nèi)的 GC 日志。GC 日志直接的可讀性相對較低瞻想,需要了解日志各項內(nèi)容的含義压真。
在本地測試的時候,我用了如下的 demo 代碼:
public class PrintGCDetailsDemo {
public static void main(String[] args) {
int _1m = 1024*1024;
byte[] data = new byte[_1m];
data = null;
System.gc();
}
}
運行這段代碼之前蘑险,在對應(yīng)的 JVM 啟動參數(shù)上添加變更:
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-XX:+PrintHeapAtGC
-Xloggc:/Users/gaopeng/gc.log
打印出詳細的 GC 日志滴肿,并輸出為文件。
要查看 GC 日志的詳細內(nèi)容佃迄。
Java HotSpot(TM) 64-Bit Server VM (25.221-b11) for bsd-amd64 JRE (1.8.0_221-b11), built on Jul 4 2019 04:36:22 by "java_re" with gcc 4.2.1 (Based on Apple Inc. build 5658) (LLVM build 2336.11.00)
Memory: 4k page, physical 8388608k(224720k free)
/proc/meminfo:
CommandLine flags: -XX:InitialHeapSize=134217728 -XX:MaxHeapSize=2147483648 -XX:+PrintGC -XX:+PrintGCDateStamps -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+PrintHeapAtGC -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:+UseParallelGC
{Heap before GC invocations=1 (full 0):
PSYoungGen total 38400K, used 3022K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
eden space 33280K, 9% used [0x0000000795580000,0x0000000795873838,0x0000000797600000)
from space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
to space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
ParOldGen total 87552K, used 0K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
object space 87552K, 0% used [0x0000000740000000,0x0000000740000000,0x0000000745580000)
Metaspace used 2937K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 320K, capacity 388K, committed 512K, reserved 1048576K
2020-10-21T16:40:16.802-0800: 0.107: [GC (System.gc()) [PSYoungGen: 3022K->544K(38400K)] 3022K->552K(125952K), 0.0012104 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap after GC invocations=1 (full 0):
PSYoungGen total 38400K, used 544K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
from space 5120K, 10% used [0x0000000797600000,0x0000000797688000,0x0000000797b00000)
to space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
ParOldGen total 87552K, used 8K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
object space 87552K, 0% used [0x0000000740000000,0x0000000740002000,0x0000000745580000)
Metaspace used 2937K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 320K, capacity 388K, committed 512K, reserved 1048576K
}
{Heap before GC invocations=2 (full 1):
PSYoungGen total 38400K, used 544K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
from space 5120K, 10% used [0x0000000797600000,0x0000000797688000,0x0000000797b00000)
to space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
ParOldGen total 87552K, used 8K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
object space 87552K, 0% used [0x0000000740000000,0x0000000740002000,0x0000000745580000)
Metaspace used 2937K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 320K, capacity 388K, committed 512K, reserved 1048576K
2020-10-21T16:40:16.803-0800: 0.108: [Full GC (System.gc()) [PSYoungGen: 544K->0K(38400K)] [ParOldGen: 8K->396K(87552K)] 552K->396K(125952K), [Metaspace: 2937K->2937K(1056768K)], 0.0036883 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]
Heap after GC invocations=2 (full 1):
PSYoungGen total 38400K, used 0K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
eden space 33280K, 0% used [0x0000000795580000,0x0000000795580000,0x0000000797600000)
from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
to space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
ParOldGen total 87552K, used 396K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
object space 87552K, 0% used [0x0000000740000000,0x0000000740063038,0x0000000745580000)
Metaspace used 2937K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 320K, capacity 388K, committed 512K, reserved 1048576K
}
Heap
PSYoungGen total 38400K, used 998K [0x0000000795580000, 0x0000000798000000, 0x00000007c0000000)
eden space 33280K, 3% used [0x0000000795580000,0x0000000795679b20,0x0000000797600000)
from space 5120K, 0% used [0x0000000797600000,0x0000000797600000,0x0000000797b00000)
to space 5120K, 0% used [0x0000000797b00000,0x0000000797b00000,0x0000000798000000)
ParOldGen total 87552K, used 396K [0x0000000740000000, 0x0000000745580000, 0x0000000795580000)
object space 87552K, 0% used [0x0000000740000000,0x0000000740063038,0x0000000745580000)
Metaspace used 2949K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 322K, capacity 388K, committed 512K, reserved 1048576K
關(guān)于如何查看 gc 日志泼差,就不在這里展開,有單獨的篇章呵俏。youngGC 次數(shù)頻繁堆缘,對系統(tǒng)的影響一般不會很大。需要注意的是 fullGC普碎。
二 dump 內(nèi)存快照
除了 gc 日志吼肥,還有一類文件是需要我們注意的。
.hprof
即內(nèi)存轉(zhuǎn)儲快照麻车。何時生成快照文件缀皱,需要在 JVM 中設(shè)置并重啟。
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/gaopeng/
如上的 JVM 配置表示在系統(tǒng)內(nèi)存溢出時保存內(nèi)存快照文件到指定目錄下动猬。除了這個快照生成時機啤斗,還有兩個配置項可用
HeapDumpAfterFullGC
HeapDumpBeforeFullGC
這兩個配置項,是在使用
java -XX:+PrintFlagsFinal
查看系統(tǒng)全局配置時找到的枣察,默認都是關(guān)閉狀態(tài)争占。
我使用一個會導(dǎo)致內(nèi)存溢出的 case:
public class HeapOOM {
static class OOMObject {
}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while(true) {
list.add(new OOMObject());
}
}
}
不斷創(chuàng)建對象燃逻,并且也沒有丟棄。然后設(shè)置較小的堆空間臂痕。
-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/Users/gaopeng/
運行程序伯襟,很快就會報錯
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/bin/java -verbose:gc -Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/Users/gaopeng/ "-javaagent:/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar=54021:/Applications/IntelliJ IDEA.app/Contents/bin" -Dfile.encoding=UTF-8 -classpath /Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/deploy.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/cldrdata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/dnsns.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jaccess.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jfxrt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/localedata.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/nashorn.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/zipfs.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/javaws.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfxswt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/management-agent.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/plugin.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/ant-javafx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/dt.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/javafx-mx.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/jconsole.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/packager.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/sa-jdi.jar:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/tools.jar:/Users/sina/netty/target/classes:/Users/gaopeng/.m2/repository/io/netty/netty-all/4.1.51.Final/netty-all-4.1.51.Final.jar com.gaop.netty.bio.HeapOOM
[GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->5041K(19456K), 0.0073478 secs] [Times: user=0.03 sys=0.00, real=0.00 secs]
[GC (Allocation Failure) --[PSYoungGen: 9200K->9200K(9216K)] 13233K->19432K(19456K), 0.0121535 secs] [Times: user=0.05 sys=0.00, real=0.01 secs]
[Full GC (Ergonomics) [PSYoungGen: 9200K->0K(9216K)] [ParOldGen: 10232K->9753K(10240K)] 19432K->9753K(19456K), [Metaspace: 3091K->3091K(1056768K)], 0.1368537 secs] [Times: user=0.40 sys=0.01, real=0.14 secs]
[Full GC (Ergonomics) [PSYoungGen: 7859K->8108K(9216K)] [ParOldGen: 9753K->7643K(10240K)] 17613K->15751K(19456K), [Metaspace: 3091K->3091K(1056768K)], 0.1691025 secs] [Times: user=0.47 sys=0.01, real=0.17 secs]
[Full GC (Allocation Failure) [PSYoungGen: 8108K->8090K(9216K)] [ParOldGen: 7643K->7643K(10240K)] 15751K->15733K(19456K), [Metaspace: 3091K->3091K(1056768K)], 0.0967636 secs] [Times: user=0.40 sys=0.00, real=0.09 secs]
java.lang.OutOfMemoryError: Java heap space
Dumping heap to /Users/gaopeng/java_pid6999.hprof ...
Heap dump file created [27817336 bytes in 0.098 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:3210)
at java.util.Arrays.copyOf(Arrays.java:3181)
at java.util.ArrayList.grow(ArrayList.java:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at com.gaop.netty.bio.HeapOOM.main(HeapOOM.java:15)
[Full GC (Ergonomics) [PSYoungGen: 8192K->0K(9216K)] [ParOldGen: 7643K->396K(10240K)] 15835K->396K(19456K), [Metaspace: 3116K->3116K(1056768K)], 0.0042065 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]
Heap
PSYoungGen total 9216K, used 82K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000)
eden space 8192K, 1% used [0x00000007bf600000,0x00000007bf614938,0x00000007bfe00000)
from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000)
to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000)
ParOldGen total 10240K, used 396K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000)
object space 10240K, 3% used [0x00000007bec00000,0x00000007bec63330,0x00000007bf600000)
Metaspace used 3122K, capacity 4500K, committed 4864K, reserved 1056768K
class space used 342K, capacity 388K, committed 512K, reserved 1048576K
Process finished with exit code 1
抓住 gc 日志報錯的關(guān)鍵詞:堆內(nèi)存溢出。作為對象分配的主要區(qū)域握童,產(chǎn)生堆內(nèi)存溢出的問題姆怪,就需要知道在報錯的時候,堆空間的使用情況:到底是什么對象的分配占用了堆空間澡绩。這個時候就可以查看 JVM 生成的 .hprof 文件稽揭,即 dump 日志。
查看工具是 visualVM肥卡。
匯總視圖中的關(guān)注點
- class by number of instance 創(chuàng)建實例數(shù)量
- class by size of instance 創(chuàng)建實例占用空間
可以看到溪掀,在限制堆空間 20M 的范圍內(nèi),測試實例 OOMObject 的創(chuàng)建數(shù)量為 810326步鉴,占用空間為 12965216 byte揪胃,約 12MB。占據(jù)了老年代的大部分空間氛琢,而且因為仍存在 GCRoots 的關(guān)聯(lián)喊递,無法被回收,持續(xù)不斷的生成最終導(dǎo)致內(nèi)存溢出阳似。
因此骚勘,dump 文件,可以幫我們分析和定位問題關(guān)鍵點撮奏。
三 基礎(chǔ)配置項總結(jié)
堆大小默認配置
按運行程序的類型俏讹,配置堆的大小比例,取決于實際運行機器的物理內(nèi)存挽荡。
類似于 ES 這樣的數(shù)據(jù)檢索服務(wù)節(jié)點
- 通常把堆的初始化大小藐石,設(shè)置成物理內(nèi)存的一半。這是因為 ES 是存儲類型的服務(wù)定拟,我們需要預(yù)留一半的內(nèi)存給文件緩存
另一種線上計算型節(jié)點于微,比如處理請求的 web 服務(wù)
- 通常會把堆內(nèi)存設(shè)置為物理內(nèi)存的 2/3,剩下的 1/3 就是給堆外內(nèi)存使用的青自。
ps. 注:這部分只能是一個大概值株依,并且不同場景的適用配置也不同。
配置項
打印 gc 日志
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-XX:+PrintGCDateStamps
-Xloggc:/Users/gaopeng/gc.log
在不同的時機 dump 內(nèi)存快照
-XX:+HeapDumpOnOutOfMemoryError
-XX:+HeapDumpAfterFullGC
-XX:+HeapDumpBeforeFullGC
打印對象年齡分布
PrintTenuringDistribution
打印這個信息是作為調(diào)整 MaxTenuringThreshold 選項的參考延窜。調(diào)節(jié)年輕代對象晉升老年代需要經(jīng)歷的 GC 次數(shù)恋腕。是一個需要關(guān)注的參數(shù)信息。
打印 STW 時間
PrintGCApplicationStoppedTime
超過一定大小的對象逆瑞,將直接在老年代分配
PretenureSizeThreshold