基于jdk1.8的简单性能监控分析


jdk的目录下有很多附属的工具,供开发人员跟踪调试使用,对于系统性能监控提供了极大的便利性。虽然也可以通过很多jdk可视化的监控工具进行性能分析,但生产环境比较难以建立起这样的条件,因此了解这些简单的监控命令还是很有必要的。


下面以一个小程序来整理一下堆栈分析的简单步骤。

List list = new ArrayList();
			
while (true) {
	list.add(new MemoryDemo());
}
  • 运行Java程序:
    java -jar -Xmx32m -Xms32m mebool-demo.jar

  • 运行jps -l(查询Java进程)
    运行 jstat -gcutil 7177 250 20(7177是上面一条命令查询出的进程号,每250毫秒执行一次查询,共查询20次)
    基于jdk1.8的简单性能监控分析_第1张图片

     这里主要关注的参数有E(Eden区占用比例)、O(Old老年代占用比例)、M(方法区占用比例)。再后面的参数可以参考来推测垃圾回收的繁忙程度:YGC(Young GC次数)、YGCT(Young GC总时间)、FGC(Full GC次数)、FGCT(Full GC总时间)、GCT(GC总时间)。
     
     在这里说明一下jdk1.7和jdk1.8的内存模型变化。我们知道在Hotspot虚拟机上,通过永久代来实现方法区,jdk1.8去永久代之后,使用元空间实现方法区。在jdk1.7的环境下执行jstat时,使用P来代表永久代,即方法区的内存占用率,而jdk1.8显示的是M,代表方法区。
    
     最后发生内存溢出时的内存监控:
    

这里写图片描述

    最后发现老年代接近100%,而发生内存溢出了。jdk1.8默认使用的垃圾收集器Parallel使用的是复制算法,内存处理的机制是优先使用Eden区分配新产生的对象,其次在Survivor1分配对象,为了避免内存出现大量的不连续空间,内存回收时,将Eden区和Survivor1复制到Survivor0的空间中,如果Survivor0的空间不足时,会复制到老年代中,最终进行一次Full GC。因此虽然最后的Eden区虽然有空闲,但繁忙的老年代的空间不足以承受工作压力,最终发生内存溢出。

    另外,并不是说对象创建的多、空间有限就会导致内存溢出,而是大多数对象到GCRoots的引用一直存在,无法进行垃圾回收导致空间不足而溢出。例如下面的代码基本不会造成内存溢出:


```
while (true) {
	new MemoryDemo();
}
		
```
  • 查看堆转储快照
    jmap命令用于生成堆转储快照;jhat命令用于启动堆转储快照的web server,以便于通过界面查看。

     运行命令(13528是jps查询到的进程号)
     jmap -dump:format=b,file=mebool.bin 13528
     则当前目录下会生成一个mebool.bin文件,即堆转储快照文件。
     
     运行命令
     jhat mebool.bin
     浏览器访问 http://localhost:7000,展示的是堆转储快照信息界面:
    

    基于jdk1.8的简单性能监控分析_第2张图片

     第一项package是项目中的包路径及实例列表
     第二项是实例的统计信息
     比较实用的是Show heap histogram
    
  • show heap histogram.

    基于jdk1.8的简单性能监控分析_第3张图片

      进入这个列表默认是按照占用空间排序,毋庸置疑的Object类占用的空间最大,再点击instance count,按照生成实例数量统计:
    

基于jdk1.8的简单性能监控分析_第4张图片

    此时发现我们自己创建的MemoryDemo数量最多,而Total Size却为0。因为它只是实例的一个外壳,真正占用空间的是该实例包含的所有引用对象。而分析内存溢出时,这种数量巨大的实例一般就是罪魁祸首,因此统计数和占用空间要结合起来看。
  • 备注
    线上环境一般都有用户权限控制,执行jstack等命令时出现“xxx: well-known file is not secure”,这种情况说明被访问控制了。需要切换到这个进程对应的用户,再操作就可以了。查看进程对应的用户可以用top命令。

你可能感兴趣的:(java)