Skip to content
Branch: master
Find file History
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
..
Failed to load latest commit information.
src/main 开始1.0.9开发 Dec 19, 2018
README.md 开始1.0.9开发 Dec 19, 2018
README_EN.md #67 live option implemented to collect live objects' stat Sep 9, 2018
pom.xml 开始1.0.9开发 Dec 19, 2018

README.md

1. 概述

分代版的jmap(新生代,存活区,老生代),是排查内存缓慢泄露,老生代增长过快原因的利器。因为jmap -histo PID 打印的是整个Heap的对象统计信息,而为了定位上面的问题,我们需要专门查看OldGen对象,和Survivor区大龄对象的工具。

vjmap的原始思路来源于R大的TBJMap ,翻新后支持JDK8,支持Survivor区大龄对象过滤,以及大天秤对输出结果不要看歪脖子的执着。

这里有一篇实战:【唯实践】JVM老生代增长过快问题排查,最后定位到是Jedis的锅。

注意:因为vjmap的原理,只支持CMS和ParallelGC,不支持G1。

2.使用说明

Download vjmap-1.0.8.zip (from Maven Central)

2.1 注意事项

注意:vjmap在执行过程中,会完全停止应用一段时间,必须摘流量执行!!!!

  1. JAVA_HOME定义

vjmap使用的java为JAVA_HOME/bin/java, 需要至少JDK7,且与目标应用的JVM使用相同的JDK大版本。

vjmap需要依赖JAVA_HOME/lib/sa-jdi.jar

JAVA_HOME的定位,通过读取环境变量JAVA_HOME,如果没有定义,则尝试通过"which java"定位java从而获得相对路径。

  1. 权限说明

需要root权限 (sudo -E vjmap.sh ...,),权限与jmap -heap pid相同.

如果无法联通进程时,可尝试执行jstack -F pid, jmap -heap pid 自行比对。

如果在容器中运行,需要打开ptrace权限。

2.2 常用指令

针对活着的进程,PID为进程号

// 打印整个堆中对象的统计信息,按对象的total size排序:
./vjmap.sh -all PID > /tmp/histo.log

// 推荐,打印老年代的对象统计信息,按对象的oldgen size排序,比-all快很多,暂时只支持CMS:
./vjmap.sh -old PID > /tmp/histo-old.log

// 推荐,打印Survivor区的对象统计信息,默认age>=3
./vjmap.sh -sur PID > /tmp/histo-sur.log

// 推荐,打印Survivor区的对象统计信息,查看age>=4的对象
./vjmap.sh -sur:minage=4 PID > /tmp/histo-sur.log

// 推荐,打印Survivor区的对象统计信息,单独查看age=4的对象:
./vjmap.sh -sur:age=4 PID > /tmp/histo-sur.log

针对CoreDump文件

./vjmap.sh -old ${path_to_java} ${path_to_coredump}

2.3 仅输出存活的对象

原理为正式统计前先执行一次full gc

./vjmap.sh -old:live PID > /tmp/histo-old-live.log

2.4 过滤对象大小,不显示过小的对象:

// 按对象的oldgen size进行过滤,只打印OldGen占用超过1K的数据
./vjmap.sh -old:minsize=1024 PID > /tmp/histo-old.log

2.5 按class name排序,配合大小过滤, 生成用于两次结果比较的报表:

./vjmap.sh -all:minsize=1024,byname PID > /tmp/histo.log

2.6 其他注意事项

  1. 意外停止

vjmap的运行需要一段时间,如果中途需要停止执行,请使用ctrl+c,或者kill vjmap的PID,让vjmap从目标进程退出。

如果错用了kill -9 ,目标java进程会保持在阻塞状态不再工作,此时必须执行两次 kill -SIGCONT $目标进程PID,重新唤醒目标java进程。

  1. OldGen碎片

如果很久没都有进行过CMS GC or Full GC,OldGen将有非常非常多的Live Regions,执行 -all 和 -old 时将非常缓慢,比如 -all的第一步Get Live Regions就会非常缓慢,如非要故意观察死对象的场景,此时可尝试先触发一次full gc, 如使用vjmap -all:live, 或 jmap -histo:live 或 jcmd GC.run 等。

3.输出示例

3.1 Survivor区年龄大于N的对象统计

Survivor Object Histogram:

 #num  #count     #bytes #Class description
-----------------------------------------------------------------------------------
   1:      37         1k io.netty.buffer.PoolThreadCache$MemoryRegionCache$Entry
   2:       2         64 java.util.concurrent.locks.AbstractQueuedSynchronizer$Node
Total: 39/    1k over age 2

Heap traversal took 1.3 seconds.

4. 使用Eclipse MAT进一步分析

如果只依靠对象统计信息,不足以定位问题,需要使用完整HeapDump,计算对象关联关系来进一步分析时,可以在MAT中使用OQL过滤出老生代的对象。

假设,OldGen地址范围是"0xfbd4c000" ~ "0xfce94050"

SELECT * FROM INSTANCEOF java.lang.Object t WHERE toHex(t.@objectAddress) <= "0xfce94050" AND toHex(t.@objectAddress) >= "0xfbd4c000"

注意,MAT要在偏好设置中 勾选 "Keep unreachable object"

用如下方式可获得老生代地址:

第一种方式是在启动参数增加 -XX:+PrintHeapAtGC,每次GC都打印地址

第二种方式是使用vjmap的命令,在-old, -sur, -address 中,都会打印出该区间的地址

第三种方式,使用vjmap的address命令,快速打印各代地址,不会造成过长时间停顿

./vjmap.sh -address PID

输出如下:

  eden [0x0000000119000000,0x0000000119c4a258,0x0000000121880000) space capacity = 143130624, 9.003395387977907 used
  from [0x0000000121880000,0x0000000121880000,0x0000000122990000) space capacity = 17891328, 0.0 used
  to   [0x0000000122990000,0x0000000122990000,0x0000000123aa0000) space capacity = 17891328, 0.0 used
concurrent mark-sweep generation
free-list-space[ 0x0000000123aa0000 , 0x0000000139000000 ) space capacity = 357957632 used(4%)= 17024696 free= 340932936

上例中的 0x123aa0000 即为OldGen的下界。 注意OQL中使用时要把数值前的那串0去掉。

5. 打印加载的Class列表

./vjmap.sh -class PID

为了兼容JDK8,不再打印Class所在的Jar包

6. 与TBJMap的对比

  • 兼容JDK8
  • 新功能:Survivor区 age大于N的对象统计
  • 新功能:打印各分代的地址区间,用于MAT进一步分析
  • 性能提升:直接访问Survivor或OldGen区,而不是以Heap Visitor回调的方式访问整个Heap
  • 新配置项:按对象的占用内存进行过滤,不显示过小的对象
  • 新配置项:按对象的名称进行排序,可用于两次统计结果的比对
  • 输出改进:报表数字的单位化(k,m,g)与对齐,OldGen报表默认按对象在OldGen的大小排序
You can’t perform that action at this time.