Java进程分析工具

2023/06/26

Tags: Java JVM

JVM 内存区域

jvm-area

如果要为新生代分配 256m 的内存(NewSizeMaxNewSize 设为一致),参数应该这样来写:-Xmn256m;

还可以通过 -XX:NewRatio=<int> 来设置老年代新生代内存的比值。比如以下参数就是设置老年代与新生代内存的比值为 1。也就是说老年代和新生代所占比值为 1:1,新生代占整个堆栈的 1/2。

-XX:NewRatio=1

JDK 1.8 ,方法区(HotSpot 的永久代)被彻底移除了,取而代之是元空间 Metaspace,元空间使用的是本地内存。

  1. Metaspace 的初始容量并不是 -XX:MetaspaceSize 设置,无论 -XX:MetaspaceSize 配置什么值,对于 64 位 JVM 来说,Metaspace 的初始容量都是 21807104(约 20.8m)。可以参考 Oracle 官方文档 :

    Specify a higher value for the option MetaspaceSize to avoid early garbage collections induced for class metadata. The amount of class metadata allocated for an application is application-dependent and general guidelines do not exist for the selection of MetaspaceSize. The default size of MetaspaceSize is platform-dependent and ranges from 12 MB to about 20 MB.

  2. Metaspace 由于使用不断扩容到-XX:MetaspaceSize参数指定的量,就会发生 Full GC,且之后每次 Metaspace 扩容都会发生 Full GC。也就是说,MetaspaceSize 表示 Metaspace 使用过程中触发 Full GC 的阈值,只对触发起作用;如果 MaxMetaspaceSize 设置太小,可能会导致频繁 Full GC ,甚至OOM。


jmap

打印heap信息

命令:jmap -heap pid

描述:显示Java堆详细信息

➜  ~ jmap -heap 10439
Attaching to process ID 10439, please wait...
Error attaching to process: sun.jvm.hotspot.runtime.VMVersionMismatchException: 
  Supported versions are 25.312-b07. Target VM is 25.272-b1
sun.jvm.hotspot.debugger.DebuggerException: sun.jvm.hotspot.runtime.VMVersionMismatchException: 
  Supported versions are 25.312-b07. Target VM is 25.272-b1
    at sun.jvm.hotspot.HotSpotAgent.setupVM(HotSpotAgent.java:435)
    at sun.jvm.hotspot.HotSpotAgent.go(HotSpotAgent.java:305)
    at sun.jvm.hotspot.HotSpotAgent.attach(HotSpotAgent.java:140)
    at sun.jvm.hotspot.tools.Tool.start(Tool.java:185)
    at sun.jvm.hotspot.tools.Tool.execute(Tool.java:118)
    at sun.jvm.hotspot.tools.HeapSummary.main(HeapSummary.java:50)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.tools.jmap.JMap.runTool(JMap.java:201)
    at sun.tools.jmap.JMap.main(JMap.java:130)
Caused by: sun.jvm.hotspot.runtime.VMVersionMismatchException: Supported versions are 25.312-b07. 
  Target VM is 25.272-b1
    at sun.jvm.hotspot.runtime.VM.checkVMVersion(VM.java:227)
    at sun.jvm.hotspot.runtime.VM.<init>(VM.java:294)
    at sun.jvm.hotspot.runtime.VM.initialize(VM.java:370)
    at sun.jvm.hotspot.HotSpotAgent.setupVM(HotSpotAgent.java:431)

报错原因是系统有多个jdk,可以指定绝对路径。

➜  ~ echo $JAVA_HOME
/usr/local/TencentKona-8.0.4-272
➜  ~ /usr/local/TencentKona-8.0.4-272/bin/jmap -heap 10439
Attaching to process ID 10439, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.272-b1

using thread-local object allocation.
Garbage-First (G1) GC with 8 thread(s)

Heap Configuration:
   MinHeapFreeRatio         = 40
   MaxHeapFreeRatio         = 70
   MaxHeapSize              = 536870912 (512.0MB)
   NewSize                  = 1363144 (1.2999954223632812MB)
   MaxNewSize               = 321912832 (307.0MB)
   OldSize                  = 5452592 (5.1999969482421875MB)
   NewRatio                 = 2
   SurvivorRatio            = 8
   MetaspaceSize            = 21807104 (20.796875MB)
   CompressedClassSpaceSize = 260046848 (248.0MB)
   MaxMetaspaceSize         = 268435456 (256.0MB)
   G1HeapRegionSize         = 1048576 (1.0MB)

Heap Usage:
G1 Heap:
   regions  = 512
   capacity = 536870912 (512.0MB)
   used     = 226987352 (216.47200775146484MB)
   free     = 309883560 (295.52799224853516MB)
   42.27968901395798% used
G1 Young Generation:
Eden Space:
   regions  = 207
   capacity = 338690048 (323.0MB)
   used     = 217055232 (207.0MB)
   free     = 121634816 (116.0MB)
   64.08668730650155% used
Survivor Space:
   regions  = 0
   capacity = 0 (0.0MB)
   used     = 0 (0.0MB)
   free     = 0 (0.0MB)
   0.0% used
G1 Old Generation:
   regions  = 10
   capacity = 198180864 (189.0MB)
   used     = 9932120 (9.472007751464844MB)
   free     = 188248744 (179.52799224853516MB)
   5.0116443129443615% used

8841 interned Strings occupying 891192 bytes.

统计heap对象

命令:jmap -histo:live pid 描述:显示堆中对象的统计信息

其中包括每个Java类、对象数量、内存大小(单位:字节)、完全限定的类名。打印的虚拟机内部的类名称将会带有一个 * 前缀。如果指定了live子选项,则只计算活动的对象。

➜  ~ /usr/local/TencentKona-8.0.4-272/bin/jmap -histo:live 10439 |head -n 30

 num     #instances         #bytes  class name
----------------------------------------------
   1:         23634        2041848  [C
   2:            43        1409712  [Ljava.util.concurrent.ForkJoinTask;
   3:          1053        1191208  [B
   4:          7197         790496  java.lang.Class
   5:         23631         567144  java.lang.String
   6:          6322         542264  [Ljava.lang.Object;
   7:          8725         279200  java.util.concurrent.ConcurrentHashMap$Node
   8:          4123         263872  java.nio.DirectByteBuffer
   9:          4096         229376  org.apache.flink.core.memory.MemorySegment
  10:          2900         201184  [I
  11:          4117         164680  sun.misc.Cleaner
  12:          4115         131680  java.nio.DirectByteBuffer$Deallocator
  13:          3923         125536  java.util.HashMap$Node
  14:          5009          80144  java.lang.Object
  15:           900          79200  java.lang.reflect.Method
  16:           811          77264  [Ljava.util.HashMap$Node;
  17:          1304          73024  java.lang.invoke.MemberName
  18:           101          66976  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  19:          4160          66560  java.util.concurrent.atomic.AtomicBoolean
  20:            80          53760  org.apache.flink.shaded.netty4.io.netty.util.internal.shaded.
                                    org.jctools.queues.MpscArrayQueue
  21:          1266          50640  java.util.LinkedHashMap$Entry
  22:          1160          46400  java.lang.invoke.MethodType
  23:          1144          45760  java.lang.ref.SoftReference
  24:          1663          41632  [Ljava.lang.Class;
  25:           986          39440  com.typesafe.config.impl.SimpleConfigOrigin
  26:          1177          37664  java.lang.invoke.MethodType$ConcurrentWeakInternSet$WeakEntry
  27:           777          37296  java.util.HashMap

打印等待终结的对象

命令:jmap -finalizerinfo pid

描述:打印等待终结的对象信息

➜  ~ /usr/local/TencentKona-8.0.4-272/bin/jmap -finalizerinfo 10439
Attaching to process ID 10439, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.272-b1
Number of objects pending for finalization: 0

生成dump文件

命令:jmap -dump:format=b,file=heapdump.hprof pid

描述:生成堆转储快照dump文件。

➜  ~ /usr/local/TencentKona-8.0.4-272/bin/jmap -dump:format=b,file=heapdump.hprof 10439
Dumping heap to /root/heapdump.hprof ...
Heap dump file created

以hprof二进制格式转储Java堆到指定filename的文件中。live子选项是可选的,如果指定了live子选项,堆中只有活动的对象会被转储。想要浏览heap dump,你可以使用jhat(Java堆分析工具)读取生成的文件。

jhat

分析dump文件

命令:jhat -J-mx1024m heapdump.hprof,当dump文件很大时,需要适当调高-mx; 当端口有占用时 jhat -port 7080 heapdump.hprof 可以指定端口;

➜  ~ jhat -J-mx1024m heapdump.hprof
Reading from heapdump.phrof...
Dump file created Tue May 24 21:38:43 CST 2022
Snapshot read, resolving...
Resolving 4346760 objects...
Chasing references, expect 869 dots
Eliminating duplicate references.
Snapshot resolved.
Started HTTP server on port 7000
Server is ready.

jhat会启动一个web服务,在web界面上查看dump文件解析的结果。

使用EMA工具,会比jhat更友好的界面;

jinfo

jinfo 获取 JVM 运行参数,包括启动时指定的环境变量;

$ /usr/local/TencentKona-8.0.10-332/bin/jinfo  1
Attaching to process ID 1, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.332-b1
Java System Properties:

zookeeper.sasl.client = true
java.runtime.name = OpenJDK Runtime Environment
java.vm.version = 25.332-b1
sun.boot.library.path = /usr/local/TencentKona-8.0.10-332/jre/lib/amd64
java.vm.vendor = Tencent
path.separator = :
file.encoding.pkg = sun.io
java.vm.name = OpenJDK 64-Bit Server VM
sun.os.patch.level = unknown
sun.java.launcher = SUN_STANDARD
user.dir = /
java.vm.specification.name = Java Virtual Machine Specification
java.runtime.version = 1.8.0_332-b1
java.awt.graphicsenv = sun.awt.X11GraphicsEnvironment
os.arch = amd64
java.endorsed.dirs = /usr/local/TencentKona-8.0.10-332/jre/lib/endorsed
line.separator = 

...
VM Flags:
Non-default VM flags: -XX:ActiveProcessorCount=8 -XX:CICompilerCount=4 
-XX:CompressedClassSpaceSize=260046848 -XX:ErrorFile=null 
-XX:InitialHeapSize=3103784960 -XX:MaxDirectMemorySize=493921243 
-XX:MaxHeapSize=3103784960 -XX:MaxMetaspaceSize=268435456 -XX:MaxNewSize=1034420224 
-XX:MinHeapDeltaBytes=524288 -XX:NewSize=1034420224 -XX:OldSize=2069364736 -XX:+PrintGC 
-XX:+PrintGCDetails -XX:+PrintGCTimeStamps -XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
-XX:+UseParallelGC -XX:+UseUTF8UTF16Intrinsics 

Command line:  -Duser.timezone=GMT+08 -Dlog.level=INFO -Xmx3103113861 -Xms3103113861 
-XX:MaxDirectMemorySize=493921243 -XX:MaxMetaspaceSize=268435456 -verbose:gc -XX:+PrintGCDetails 
-XX:+PrintGCTimeStamps -XX:ActiveProcessorCount=8 -Dlog4j2.formatMsgNoLookups=true

jstat

jstat -gc 监控GC相关信息。

/usr/local/TencentKona-8.0.10-332/bin/jstat -gc 1 1000
S0C     S1C      S0U   S1U     EC       EU       OC          OU        MC      MU      CCSC    CCSU       YGC   YGCT    FGC    FGCT     GCT   
43520.0 43008.0  0.0   27234.4 922112.0 282128.7 2020864.0   35542.2   76928.0 73257.9 10368.0 9767.7      9    0.124   3      0.320    0.444
43520.0 43008.0  0.0   27234.4 922112.0 282128.7 2020864.0   35542.2   76928.0 73257.9 10368.0 9767.7      9    0.124   3      0.320    0.444
43520.0 43008.0  0.0   27234.4 922112.0 282128.7 2020864.0   35542.2   76928.0 73257.9 10368.0 9767.7      9    0.124   3      0.320    0.444
43520.0 43008.0  0.0   27234.4 922112.0 282128.7 2020864.0   35542.2   76928.0 73257.9 10368.0 9767.7      9    0.124   3      0.320    0.444
43520.0 43008.0  0.0   27234.4 922112.0 282128.7 2020864.0   35542.2   76928.0 73257.9 10368.0 9767.7      9    0.124   3      0.320    0.444
43520.0 43008.0  0.0   27234.4 922112.0 285552.9 2020864.0   35542.2   76928.0 73257.9 10368.0 9767.7      9    0.124   3      0.320    0.444
43520.0 43008.0  0.0   27234.4 922112.0 285992.0 2020864.0   35542.2   76928.0 73257.9 10368.0 9767.7      9    0.124   3      0.320    0.444
43520.0 43008.0  0.0   27234.4 922112.0 285992.0 2020864.0   35542.2   76928.0 73257.9 10368.0 9767.7      9    0.124   3      0.320    0.444

jstat -gcutil 监控JVM GC 的信息。

/usr/local/TencentKona-8.0.10-332/bin/jstat -gcutil 1 1000
  S0     S1     E      O      M     CCS    YGC     YGCT    FGC    FGCT     GCT   
  0.00  63.32   9.70   1.76  95.23  94.21      9    0.124     3    0.320    0.444
  0.00  63.32   9.71   1.76  95.23  94.21      9    0.124     3    0.320    0.444
  0.00  63.32   9.71   1.76  95.23  94.21      9    0.124     3    0.320    0.444
  0.00  63.32  10.92   1.76  95.23  94.21      9    0.124     3    0.320    0.444
  0.00  63.32  12.08   1.76  95.23  94.21      9    0.124     3    0.320    0.444
  0.00  63.32  12.08   1.76  95.23  94.21      9    0.124     3    0.320    0.444
  0.00  63.32  13.03   1.76  95.23  94.21      9    0.124     3    0.320    0.444

jstack

jstack -l 获取线程栈信息,可以分析java进程中线程的执行情况,可以协助分析死锁问题;

/usr/local/TencentKona-8.0.10-332/bin/jstack  -l 1 

一般情况下,线程栈会很长,可以输出到文件中分析.

/usr/local/TencentKona-8.0.10-332/bin/jstack  -l 1 > /tmp/1.threaddump

Native Memory Tracking

默认情况下,NMT是处于关闭状态的,我们可以通过设置 JVM 启动参数来开启:-XX:NativeMemoryTracking=[off | summary | detail];(启用NMT会导致5% -10%的性能开销)

除了在虚拟机运行时获取 NMT 数据,我们还可以通过两个参数:-XX:+UnlockDiagnosticVMOptions-XX:+PrintNMTStatistics ,来获取虚拟机退出时内存使用情况的数据(输出数据的详细程度取决于你设定的跟踪级别,如 summary/detail 等)。

开启 NMT

jcmd <pid> VM.native_memory [summary | detail | baseline | summary.diff | detail.diff | shutdown] 
  [scale= KB | MB | GB]

jcmd 33561 VM.native_memory summary scale=MB
33561:
Native Memory Tracking:

Total: reserved=5593MB, committed=477MB
-                 Java Heap (reserved=4096MB, committed=256MB)
                            (mmap: reserved=4096MB, committed=256MB) 
 
-                     Class (reserved=1049MB, committed=25MB)
                            (classes #2428)
                            (malloc=11MB #2029) 
                            (mmap: reserved=1038MB, committed=14MB) 
 
-                    Thread (reserved=25MB, committed=25MB)
                            (thread #26)
                            (stack: reserved=25MB, committed=25MB)
 
-                      Code (reserved=244MB, committed=5MB)
                            (malloc=1MB #1623) 
                            (mmap: reserved=244MB, committed=4MB) 
 
-                        GC (reserved=162MB, committed=150MB)
                            (malloc=12MB #148) 
                            (mmap: reserved=150MB, committed=137MB) 
 
-                  Internal (reserved=12MB, committed=12MB)
                            (malloc=12MB #4010) 
 
-                    Symbol (reserved=4MB, committed=4MB)
                            (malloc=3MB #13444) 
                            (arena=1MB #1)

各区域说明

主要关注 committed 部分,这部分是已使用的内存;

Java Heap

Java 堆所使用的内存大小;可以使用 -Xms/-Xmx-XX:InitialHeapSize/-XX:MaxHeapSize 等参数来控制初始/最大的大小

Class

Class 主要是类元数据(meta data)所使用的内存空间,即虚拟机规范中规定的方法区。具体到 HotSpot 的实现中,JDK7 之前是实现在 PermGen 永久代中,JDK8 之后则是移除了 PermGen 变成了 MetaSpace 元空间。 基于此,那在启动 JVM 进程的时候设置的 -XX:MaxMetaspaceSize=256M 参数应该可以限制 Class 所使用的内存大小; 但实际情况并非这样,这里 Class 需要设置 -XX:CompressedClassSpaceSize=256M 来控制。

Thread

线程所使用的内存;可以使用参数 -Xss/-XX:ThreadStackSize 设置

Code

JVM 自身会生成一些 native code 并将其存储在称为 codecache 的内存区域中。JVM 生成 native code 的原因有很多,包括动态生成的解释器循环、 JNI、即时编译器(JIT)编译 Java 方法生成的本机代码 。其中 JIT 生成的 native code 占据了 codecache 绝大部分的空间。

codecache reserve 的最大内存是由 -XX:ReservedCodeCacheSize 参数决定的; codecache commit 的内存是由 -XX:InitialCodeCacheSize 参数决定的。

GC

GC 所使用的内存,就是垃圾收集器使用的数据所占据的内存,例如卡表 card tables、记忆集 remembered sets、标记栈 marking stack、标记位图 marking bitmaps 等等。其实都是一种借助额外的空间,来记录不同内存区域之间引用关系的结构(都是基于空间换时间的思想,否则寻找引用关系就需要诸如遍历这种浪费时间的方式)。GC 这块内存是必须的,也是我们在使用过程中无法压缩的。

Internal

Internal 包含命令行解析器使用的内存、JVMTI、PerfData 以及 Unsafe 分配的内存等等。 直接内存可以由 -XX:MaxDirectMemorySize=128M 参数决定,最后会在 Internal 中体现:

ByteBuffer.allocateDirect(256 * _1M);
// 如果设置 -XX:MaxDirectMemorySize=128M,这里分配 DirectMemory 会失败

Symbol

Symbol 为 JVM 中的符号表所使用的内存,HotSpot中符号表主要有两种:SymbolTable 与 StringTable。

Java 的类在编译之后会生成 Constant pool 常量池,常量池中会有很多的字符串常量,HotSpot 出于节省内存的考虑,往往会将这些字符串常量作为一个 Symbol 对象存入一个 HashTable 的表结构中即 SymbolTable,如果该字符串可以在 SymbolTable 中 lookup(SymbolTable::lookup)到,那么就会重用该字符串,如果找不到才会创建新的 Symbol(SymbolTable::new_symbol)。

除了 SymbolTable,还有 StringTable(StringTable 结构与 SymbolTable 基本是一致的,都是 HashTable 的结构),即我们常说的字符串常量池。HotSpot 也是基于节省内存的考虑为我们提供了 StringTable,我们可以通过 String.intern 的方式将字符串放入 StringTable 中来重用字符串。

-XX:StringTableSize 参数设置 HashTable 的长度; -XX:SymbolTableSize 参数设置 SymbolTable 的长度。

如果该值设置的过小的话会导致hash 冲突,即使 HashTable 进行 rehash,hash 冲突也会十分频繁;

基线分析

jcmd 33561 VM.native_memory baseline
33561:
Baseline succeeded

jcmd 33561 VM.native_memory  summary.diff scale=MB
33561:
Native Memory Tracking:
Total: reserved=5593MB, committed=479MB
-                 Java Heap (reserved=4096MB, committed=256MB)
                            (mmap: reserved=4096MB, committed=256MB)

-                     Class (reserved=1049MB, committed=26MB)
                            (classes #2428)
                            (malloc=11MB #2270 +99)
                            (mmap: reserved=1038MB, committed=14MB)
 
-                    Thread (reserved=25MB, committed=25MB)
                            (thread #26)
                            (stack: reserved=25MB, committed=25MB)
 
-                      Code (reserved=245MB, committed=6MB)
                            (malloc=1MB #1996 +106)
                            (mmap: reserved=244MB, committed=5MB)
 
-                        GC (reserved=162MB, committed=150MB)
                            (malloc=12MB #148)
                            (mmap: reserved=150MB, committed=137MB)
 
-                  Internal (reserved=12MB, committed=12MB)
                            (malloc=12MB #4029 +3)
 
-                    Symbol (reserved=4MB, committed=4MB)
                            (malloc=3MB #13444)
                            (arena=1MB #1)
 
-    Native Memory Tracking (reserved=1MB, committed=1MB)
                            (tracking overhead=0MB)

基线分析会输出从 baseline 开始,到现在各区域的大小变化,以及线程数量变化;