前言
在故障定位(尤其是Out Of Memory)和性能分析的时候,我们经常会用一些文件辅助我们排查代码问题,这些文件记录了JVM运行期间的 内存占用、线程执行 等情况,这就是我们常说的dump文件。常用的有 heap dump 和 thread dump,我们可以理解为heap dump是记录内存信息的,thread dump是记录CPU信息的。
概念
(1)heap dump:此文件是一个二进制文件,它保存了某一时刻 JVM 堆中对象使用情况,它是指定时刻的JAVA堆栈的快照,是一种镜像文件。
(2)jmap:是一个可以输出内存中对象的工具,它位于JDK目录里的bin文件夹下。
(3)thread dump:它保存了某一时刻线程活动的快照,及JVM中所有java线程的堆栈跟踪信息,堆栈信息一般包含完整的类名及所执行的方法,还可能有源代码的行数。
(4)jstack:是一个抓取 thread dump 文件的有效的命令行工具,它位于JDK目录里的bin文件夹下。
Heap Dump 包含的信息
(1)所有的对象信息:对象的类信息、字段信息、原生值(int, long等)及引用值
(2)所有的类信息:类加载器、类名、超类及静态字段
(3)垃圾回收的根对象:根对象是指那些可以直接被虚拟机触及的对象
(4)线程栈及局部变量:包含了转储时刻的线程调用栈信息和栈帧中的局部变量信息
一. 进程分析(thread dump)
1. 在 windows 下查看占用内存最多的java线程的 tid
(1)使用Process Explorer 工具:http://technet.microsoft.com/en-us/sysinternals/bb896653.aspx
(2)找到 IEDA 运行的 java 程序,点击properties 显示进程详情,可以看到此进程的 pid 为 4016(如上图3所示),然后可以查看占用内存和cpu最多的线程为17072(如上图4所示)
2. 进入jdk安装目录 C:\Program Files\Java\jdk1.8.0_211\bin
3. 使用命令:jstack -l 4016 > c:/dumptest/4016.stack 导出进程4016的 thread dump 快照
4. 占用内存最多的线程17072的十六进制为42b0,打开上一步导出的4016.stack快照,搜索 42b0,可知占用CPU最高的线程为垃圾回收线程
5. 到这一步,就要分析为何频繁GC,一般是由某个对象占用了大量内存无法回收和代码中有死循环导致,接下来就要用到内存分析了,使用内存分析查看哪些对象占用了大量内存
二. 内存分析(heap dump)
1. Heap Dump 文件获取
(1)使用jmap命令生成dump文件
a. 在windows主机上通过 tasklist 查看java进程id
b. 进入jdk安装目录 C:\Program Files\Java\jdk1.8.0_211\bin
c. 使用命令 jmap -dump:live,format=b,file=c:\dumptest\heap-dump.hprof
(2)使用jcmd命令生成dump文件
jcmd
(3)使用JVM参数获取dump文件
a. JVM配置中添加-XX:+HeapDumpOnOutOfMemoryError,当OutOfMemoryError发时自动生成heap dump文件
b. -XX:+HeapDumpBeforeFullGC,当 JVM 执行 FullGC 前执行 dump
c. -XX:+HeapDumpAfterFullGC,当 JVM 执行 FullGC 后执行 dump
d. -XX:+HeapDumpOnCtrlBreak,交互式获取dump。在控制台按下快捷键Ctrl + Break时,JVM就会转存一下堆快照
e. -XX:HeapDumpPath=c:\test.hprof,指定 dump 文件存储路径
注意:JVM 生成 Heap Dump 的时候,虚拟机是暂停一切服务的。如果是线上系统执行 Heap Dump 时需要注意。
2. Heap Dump 文件分析
(1)分析工具:JProfiler
(2)下载地址:https://www.jb51.net/softs/608640.html
(3)打开软件,点击左上角 Start Center——Open Snapshots——Open Single Snapshot,导入hprof格式的heap dump文件
(4)查看 biggest objects 分析对象的内存占比
三. 性能分析
1. 常用方法:
(1)使用 jstat -gcutil
(2)如果两次FGC后老年代使用比例任然居高不下,说明有大对象常驻内存;
(3)然后使用 jmap -histo:live 72947 | more 查看该对象大小;
2. 参数解析:
(1)java堆分为新生代和老年代,新生代为 Eden + From Survivor + To Survivor,如上图中的 E + S0 + S1,Eden 和 Survivor 的内存比为8:1,每次只使用一个Eden 和一个 Survivor 区域,另一个 Survivor 用于复制收集算法回收内存;
(2)O(Old)老年代,一般大对象(长字符串和大数组)直接分配在老年代中,同时“年龄”长的的对象会从新生代自动晋升到老年代中;
(3)P(Permanent)永久代,方法区称为永久代;
(4)YGC 和 YGCT 分别代表年轻代回收次数和回收耗时;
(5)FGC 和 FGCT 分别代表老年代回收次数和回收耗时;
(6)GCT 代表总的垃圾回收耗时;
(7)当 Eden 区分配不足时,会自动发生一次 Minor GC,当发生 Minor GC 时,虚拟机会自动检测(比较)新生代晋升到老年代的对象内存大小和老年代剩余内存大小:
a. 如果 晋升 > 剩余,则发生一次Full GC;
b. 如果 晋升 < 剩余,则去检测老年代的内存担保 HandlePromotionFailure 是否允许担保失败,如果不允许担保失败,则发生一次 Full GC,如果允许失败,则进行一次Minor GC。