JVM 监控以及内存分析

Java 语言,开发者不能直接控制程序运行内存,对象的创建都是由类加载器一步步解析,执行与生成与内存区域中的;并且jvm有自己的垃圾回收器对内存区域管理、回收;但是我们已经可以通过一些工具来在程序运行时查看对应的jvm内存使用情况,帮助更好的分析与优化我们的代码。

查看系统里java进程信息

1
2
3
4
// 查看当前机器上所有运行的java进程名称与pid(进程编号)
jps -l
// 显示指定的jvm进程所有的属性设置和配置参数
jinfo pid

jmap -histo

查看类的内存占用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ pid=\`jps | awk '{if ($2 == "Bootstrap") print $1}'`
$ jmap -histo $pid >>1.jmap

num #instances #bytes class name
----------------------------------------------
1: 16226 862336904 [Ljava.util.HashMap$Node;
2: 230727 14940520 [B
3: 5105 14082960 [I
4: 205826 12756064 [C
5: 150472 4815104 java.util.HashMap$Node
6: 186884 4485216 java.lang.String
7: 277734 4443744 java.lang.Integer
8: 108699 4347960 com.jiemo.school.model.impl.SchoolModel
9: 128405 4108960 java.util.concurrent.ConcurrentHashMap$Node
10: 114 1512448 [Ljava.util.concurrent.ConcurrentHashMap$Node;
11: 34496 1511024 [Ljava.lang.Object;
12: 25702 1233696 java.nio.HeapByteBuffer
13: 34277 1096864 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
14: 7665 856392 java.lang.Class
15: 18725 749000 java.util.HashMap$KeyIterator
16: 14489 695472 java.nio.HeapCharBuffer
17: 28122 674928 java.util.ArrayList
18: 25272 642776 [Ljava.lang.String;
19: 15380 615200 java.net.DatagramPacket

class name 解读
B代表byte
C代表char
D代表double
F代表float
I代表int
J代表long
Z代表boolean
前边有[代表数组,[I 就相当于int[]
对象用[L+类名表示

如果某个类的个数特别多, 就得检查是否内存溢出了。

jmap -heap

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
$ jmap -heap 22792
Attaching to process ID 19395, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.45-b02

using thread-local object allocation.
Parallel GC with 8 thread(s)

Heap Configuration:
MinHeapFreeRatio = 0 # 对应jvm启动参数 -XX:MinHeapFreeRatio 设置JVM堆最小空闲比率 (默认40)
MaxHeapFreeRatio = 100 # 对应jvm启动参数 -XX:MaxHeapFreeRatio 设置JVM堆最大空闲比率 (默认70)
MaxHeapSize = 8388608000 (8000.0MB) # 对应jvm启动参数 -XX:MaxHeapSize 设置JVM堆的最大大小
NewSize = 44564480 (42.5MB) # 对应jvm启动参数 -XX:NewSize 设置JVM堆的年轻代的默认大小
MaxNewSize = 2796027904 (2666.5MB) # 对应jvm启动参数 -XX:MaxNewSize 设置JVM堆的年轻带的最大大小
OldSize = 89653248 (85.5MB) # 对应jvm启动参数 -XX:OldSize 设置JVM堆的老年代的大小
NewRatio = 2 # 对应jvm启动参数 -XX:NewRatio 老年代与年轻代的大小比率
SurvivorRatio = 8 # 对应jvm启动参数 -XX:SurvivorRatio 设置年轻代中Eden区与Survivor区的大小比值
MetaspaceSize = 21807104 (20.796875MB) # -XX:MetaspaceSize
CompressedClassSpaceSize = 1073741824 (1024.0MB) # -XX:CompressedClassSpaceSize, 只有当-XX:+UseCompressedClassPointers开启了才有效
MaxMetaspaceSize = 536870912 (512.0MB) # -XX:MaxMetaspaceSize
G1HeapRegionSize = 0 (0.0MB)

Heap Usage:
PS Young Generation
Eden Space: # Eden区内存分布 总量 已使用 空闲 使用比率
capacity = 404750336 (386.0MB)
used = 376643272 (359.1950149536133MB)
free = 28107064 (26.80498504638672MB)
93.05570335585837% used
From Space: # 其中一个Survivor区内存分布 总量 已使用 空闲 使用比率
capacity = 458227712 (437.0MB)
used = 383131152 (365.38233947753906MB)
free = 75096560 (71.61766052246094MB)
83.61151933124464% used
To Space: # 另一个Survivor区内存分布 总量 已使用 空闲 使用比率
capacity = 492830720 (470.0MB)
used = 0 (0.0MB)
free = 492830720 (470.0MB)
0.0% used
PS Old Generation # 当前老年代内存分布 总量 已使用 空闲 使用比率
capacity = 918552576 (876.0MB)
used = 463923192 (442.43163299560547MB)
free = 454629384 (433.56836700439453MB)
50.50589417758053% used


16117 interned Strings occupying 1692808 bytes.

MaxHeapFreeRatio: GC后如果发现空闲堆内存占到整个预估堆内存的N%(百分比),则收缩堆内存的预估最大值, 预估堆内存是堆大小动态调控的重要选项之一. 堆内存预估最大值一定小于或等于固定最大值(-Xmx指定的数值). 前者会根据使用情况动态调大或缩小, 以提高GC回收的效率
MinHeapFreeRatio: GC后如果发现空闲堆内存占到整个预估堆内存的N%(百分比), 则放大堆内存的预估最大值

MaxHeapSize: 即-Xmx, 堆内存大小的上限
InitialHeapSize: 即-Xms, 堆内存大小的初始值

NewSize: 新生代预估堆内存占用的默认值
MaxNewSize: 新生代占整个堆内存的最大值

OldSize: 老年代的默认大小, default size of the tenured generation
NewRatio: 老年代对比新生代的空间大小, 比如2代表老年代空间是新生代的两倍大小. The ratio of old generation to young generation.

SurvivorRatio: Eden/Survivor的值. 这个值的说明, 很多网上转载的都是错的. 8表示Survivor:Eden=1:8, 因为survivor区有2个, 所以Eden的占比为8/10. Ratio of eden/survivor space size. -XX:SurvivorRatio=6 sets the ratio between each survivor space and eden to be 1:6, each survivor space will be one eighth of the young generation.

MetaspaceSize: 分配给类元数据空间的初始大小(Oracle逻辑存储上的初始高水位,the initial high-water-mark ). 此值为估计值. MetaspaceSize设置得过大会延长垃圾回收时间. 垃圾回收过后, 引起下一次垃圾回收的类元数据空间的大小可能会变大
MaxMetaspaceSize: 是分配给类元数据空间的最大值, 超过此值就会触发Full GC. 此值仅受限于系统内存的大小, JVM会动态地改变此值

CompressedClassSpaceSize: 类指针压缩空间大小, 默认为1G

G1HeapRegionSize: G1区块的大小, 取值为1M至32M. 其取值是要根据最小Heap大小划分出2048个区块. With G1 the Java heap is subdivided into uniformly sized regions. This sets the size of the individual sub-divisions. The default value of this parameter is determined ergonomically based upon heap size. The minimum value is 1Mb and the maximum value is 32Mb. Sets the size of a G1 region. The value will be a power of two and can range from 1MB to 32MB. The goal is to have around 2048 regions based on the minimum Java heap size.

指针压缩

  1. 64位平台上默认打开
    1)使用-XX:+UseCompressedOops压缩对象指针
    “oops”指的是普通对象指针(“ordinary” object pointers)。
    Java堆中对象指针会被压缩成32位。
    使用堆基地址(如果堆在低26G内存中的话,基地址为0)
    2)使用-XX:+UseCompressedClassPointers选项来压缩类指针
    对象中指向类元数据的指针会被压缩成32位
    类指针压缩空间会有一个基地址
  2. 元空间和类指针压缩空间的区别
    1)类指针压缩空间只包含类的元数据,比如InstanceKlass, ArrayKlass
    仅当打开了UseCompressedClassPointers选项才生效
    为了提高性能,Java中的虚方法表也存放到这里
    这里到底存放哪些元数据的类型,目前仍在减少
    2)元空间包含类的其它比较大的元数据,比如方法,字节码,常量池等。

jstat -gcutil [pid] [internal]

1
2
 S0     S1     E      O      M     CCS     YGC     YGCT     FGC    FGCT     GCT
83.61 0.00 93.25 50.51 97.92 93.98 32 45.703 10 16.852 62.555

S0: Survivor 0区的空间使用率 Survivor space 0 utilization as a percentage of the space’s current capacity.
S1: Survivor 1区的空间使用率 Survivor space 1 utilization as a percentage of the space’s current capacity.
E: Eden区的空间使用率 Eden space utilization as a percentage of the space’s current capacity.
O: 老年代的空间使用率 Old space utilization as a percentage of the space’s current capacity.
M: 元数据的空间使用率 Metaspace utilization as a percentage of the space’s current capacity.
CCS: 类指针压缩空间使用率 Compressed class space utilization as a percentage.
YGC: 新生代GC次数 Number of young generation GC events.
YGCT: 新生代GC总时长 单位秒 Young generation garbage collection time.
FGC: Full GC次数 Number of full GC events.
FGCT: Full GC总时长 单位秒 Full garbage collection time.
GCT: 总共的GC时长 单位秒 Total garbage collection time.

查看JVM参数及值的命令行工具

  1. -XX:+PrintFlagsInitial参数
    显示所有可设置参数及默认值,可结合-XX:+PrintFlagsInitial与-XX:+PrintFlagsFinal对比设置前、设置后的差异,方便知道对那些参数做了调整。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    $ java -XX:+PrintFlagsInitial
    [Global flags]
    uintx AdaptiveSizeDecrementScaleFactor = 4 {product}
    uintx AdaptiveSizeMajorGCDecayTimeScale = 10 {product}
    uintx AdaptiveSizePausePolicy = 0 {product}
    uintx AdaptiveSizePolicyCollectionCostMargin = 50 {product}
    uintx AdaptiveSizePolicyInitializingSteps = 20 {product}
    uintx AdaptiveSizePolicyOutputInterval = 0 {product}
    uintx AdaptiveSizePolicyWeight = 10 {product}
    uintx AdaptiveSizeThroughPutPolicy = 0 {product}
    uintx AdaptiveTimeWeight = 25 {product}
    bool AdjustConcurrency = false {product}
    bool AggressiveOpts = false {product}
    intx AliasLevel = 3 {C2 product}
    bool AlignVector = true {C2 product}
    intx AllocateInstancePrefetchLines = 1 {product}
    intx AllocatePrefetchDistance = -1 {product}
    intx AllocatePrefetchInstr = 0 {product}
    intx AllocatePrefetchLines = 3 {product}
    intx AllocatePrefetchStepSize = 16 {product}
  2. -XX:+PrintFlagsFinal参数
    可以获取到所有可设置参数及值(手动设置之后的值),这个参数只能使用在Jdk6 update 21以上版本(包括该版本)。-XX:+PrintFlagsFinal参数的使用 与上面-XX:+PrintFlagsInitial 参数使用相同

    1
    Java -XX:+PrintFlagsFinal
  3. 使用 jinfo 命令 查看或设置某个参数的值,jinfo命令格式:

    1
    jinfo [option] <pid>

2017-07-18 更新

jcmd

在JDK 1.7之后,新增了一个命令行工具jcmd。它是一个多功能工具,可以用来导出堆,查看Java进程,导出线程信息,执行GC等。

  • 列出当前运行的所有虚拟机

    1
    2
    3
    4
    5
    6
    7
    [root@apple ~]# jcmd -l
    30356 com.aliyun.tianji.cloudmonitor.Application
    8757 org.jruby.Main /opt/logstash/lib/bootstrap/environment.rb logstash/runner.rb -f /etc/logstash/conf.d/event-operate-post-shipper.yaml
    8713 org.jruby.Main /opt/logstash/lib/bootstrap/environment.rb logstash/runner.rb -f /etc/logstash/conf.d/statistics-shipper.yaml
    11615 sun.tools.jcmd.JCmd -l
    21167 org.apache.catalina.startup.Bootstrap start
    [root@apple ~]#

    参数-l表示列出所有java虚拟机,针对每一个虚拟机,可以使用help命令列出该虚拟机支持的所有命令,如下图所示,以21167这个进程为例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    [root@apple ~]# jcmd 21167 help
    21167:
    The following commands are available:
    JFR.stop
    JFR.start
    JFR.dump
    JFR.check
    VM.native_memory
    VM.check_commercial_features
    VM.unlock_commercial_features
    ManagementAgent.stop
    ManagementAgent.start_local
    ManagementAgent.start
    GC.rotate_log
    Thread.print
    GC.class_stats
    GC.class_histogram
    GC.heap_dump
    GC.run_finalization
    GC.run
    VM.uptime
    VM.flags
    VM.system_properties
    VM.command_line
    VM.version
    help

    For more information about a specific command use 'help <command>'.
  • 查看虚拟机启动时间VM.uptime

    1
    2
    3
    [root@apple ~]# jcmd 21167 VM.uptime
    21167:
    527760.414 s
  • 打印线程栈信息Thread.print

  • 查看系统中类统计信息GC.class_histogram
  • 导出堆信息GC.heap_dump(这个命令可以导出当前堆栈信息,功能和jmap -dump功能一样)
  • 获取系统Properties内容VM.system_properties
  • 获取启动参数VM.flags
  • 获取所有性能相关数据PerfCounter.print

总结:jcmd拥有jmap的大部分功能,并且Oracle官方也建议使用jcmd代替jmap。