前言
JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。
1.启动JConsole
通过JDK/bin目录下的“jconsole.exe”启动JConsole后,将自动搜索出本机运行的所有虚拟机进程,不需要用户自己再使用jps来查询了,如图双击选择其中一个进程即可开始监控了,也可以使用下面的“远程进程”功能来连接远程服务器,对远程虚拟机进行监控。
从图中可以看出机器运行了几个本地虚拟机进程,其中OOMTest是我准备的“反面教材”代码双击它进入JConsole主界面,包括“概述”、“内存”、“线程”、“类”、“VM概要”、“MBean”六个页签。如图所示
“概述”页签显示的是整个虚拟机主要运行数据的概览,其中包括“堆内存使用情况”、“类”、“CPU使用情况”四种信息的曲线图,这些曲线图是后面“内存”、“线程”、“类”页签的信息汇总。
2.内存监控
“内存”页签相当于可视化的jsat命令,用于监视收集器管理的虚拟机内存(Java堆和永久代)的变化趋势。我们通过运行代码“OOMTest”来体验一下监视功能,运行时设置的虚拟机参数为:-Xms100m -Xmx100m -XX:+UseSerialGC,这段代码的作用是以64KB/50毫秒的速度往Java堆中填充数据,一共填充1000次,使用JConsole的“内存”页签进行监视,观察曲线和柱状图指示图的变化。
import java.util.ArrayList; import java.util.List; /** * @Author:luomo * @CreateTime:2019/11/9 * @Description:OOMTest */ public class OOMTest { /** * 内存占位符对象,一个OOMObject大约占64KB */ static class OOMobject{ public byte[] placeholder=new byte[64*1024]; } public static void fillHeap(int num) throws InterruptedException{ List<OOMobject> list =new ArrayList<OOMobject>(); for(int i=0;i<num;i++) { //稍作延时,令监视曲线的变化更加明显 Thread.sleep(50); list.add(new OOMobject()); } System.gc(); } public static void main(String[] args)throws Exception{ fillHeap(1000); } }
程序运行后,在“内存”页签中可以看到内存池Eden区的运行趋势呈现折线状,如图。
监视范围扩大至整个堆后,会发现曲线是一条向上增长的平滑曲线。
并且从柱状图可以看出,在1000次循环执行结束,运行了System.gc()后,虽然整个新生代Eden和Survivor区都基本被清空了,但是代表老年代的柱状图仍然保持峰值状态,说明被填充进堆中的数据在System.gc()方法执行之后仍然存活。
这里有两个小问题供读者思考:
1.虚拟机启动参数只限制了Java堆为100MB,没有指定-Xmn参数,能否从监控图中估计出新生代有多大?
2.为何执行了System.gc()之后,图中代表老年代的柱状图仍然显示峰值状态,代码需要如何调整才能让System.gc()回收掉填充到堆中的对象?
问题一:我们可以从图中知道Eden空间大小,因为没有设置-XX:SurvivorRadio参数,所以Eden与Survivor空间比例默认值为8:1,整个新生代空间这样我们可以用Eden空间大小/占新生代空间的比例,得出新生代空间的大小。
问题二:执行完System.gc()之后,空间未能回收是因为List<oomobject>list对象仍然存活,fillHeap()方法仍然没有退出,因此list对象在System.gc()执行时仍然处于作用域之内。如果把System.gc移动到fillHeap()方法外调用就可以回收掉全部内存。 </oomobject>