前言

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>