7、虚拟机性能监控与故障处理工具(2)(JVM笔记)

二、JDK的可视化工具

2.1 JConsole:Java监视与管理控制台

JConsole(Java Monitoring and Management Console)是一种基于JMX的可视化监视、管理工具。它管理部分的功能是针对JMX MBean进行管理,由于MBean可以使用代码、中间件服务器的管理控制台或者所有符合JMX规范的软件进行访问。

2.1.1 启动JConsole

JDKbin目录中可以直接双击运行此工具。

1

启动后我们可以对其中一个进程进行监控(这里我们只是启动了JConsole进程),得到的监控界面如下:
2

说明:“概述”页面显示的是整个虚拟机主要运行数据的概览,其中包括“对内存使用情况”、“线程”、“类”、“CPU使用情况”四种信息的曲线图。

2.1.2 内存监控

“内存”页相当于可视化的jstat命令,用于监视受收集器管理的虚拟机内存(Java堆和永久代)的变化趋势。这里通过例子说明(这里亲自试验了书中的例子,但是拿到的曲线和书中有点不一样,这里还是以书中为准):

代码如下:

//使用java -Xms100m -Xmx100m -XX:+UseSerialGC运行
public static void main(String[] args) throws Exception{
    fileHeap(1000);
}

static class OOMObject{
    public byte[] placeholder = new byte[64 * 1024];

}

public static void fileHeap(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();
}

3

说明:这段代码的作用是以64KB/50ms的速度往Java堆中填充数据,一共填充1000次,使用JConsole的“内存”页进行监视,曲线变化如上图。这里可以看到,内存池Eden区的运行趋势呈现折线状。监视范围扩大至整个堆后,会发现曲线是一条向上增长的平滑曲线。之所以呈现折线是因为当Eden区被填满时进行一次GC。从柱状图中可以看到,在1000次循环执行结束,运行了Sytem.gc()后,虽然整个新生代EdenSurvivor区都基本被清空了,但是代表老年代的柱状图仍然保持峰值状态,说明被填充进堆中的数据在System.gc()方法执行后仍然存活。

  • 问题一:虚拟机启动参数只限制了Java堆为100MB,没有指定-Xmn参数(-Xms初始堆大小,-Xmx最大堆大小),能否从监控图中估计出新生代有多大?
    从图中可以看到Eden空间为27328KB,因为没有设置-XX:SurvivorRadio参数,所以EdenSurvivor空间比例默认为8:1,整个新生代空间大约为27328KB*125%=34160KB

  • 问题二、为何执行了System.gc()之后,图中代表的老年代的柱状图仍然显示峰值状态,代码需要如何改动才能让System.gc()回收掉填充到堆中的对象?
    执行完System.gc()后,空间未能回收是因为在List<OOMObject> list对象仍然存活,fileHeap()方法仍然没有退出,因此list对象在System.gc()执行时仍然处于作用域之内。如果把System.gc()移动到fileHeap()方法外调用就可以回收掉全部内存。

2.1.3 线程监控

如果上面的“内存”页签相当于可视化的jstat命令的话,“线程”页签的功能相当于可视化的jstack命令,遇到线程停顿时可以使用这个页签进行监控分析。之前说过线程长时间停顿的主要原因主要有:等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待(活锁和死锁)。下面通过代码演示:

public static void createBusyThread(){
    Thread thread = new Thread(new Runnable(){
        @Override
        public void run(){
            while(true)
                ;
        }
    }, "testBusyThread");
    
    thread.start();
}

//线程锁等待演示

public static void createLockThread(final Object lock){
    Thread thread = new Thread(new Runnable(){
        @Override
        public void run(){
            synchronized(lock){
                try{
                    lock.wait();
                } catch(InterruptedException e){
                    e.printStackTrace();
                }
            }
        }
    }, "testLockThread");
    thread.start();
}

public static void main(String[] args){
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    br.readLine();
    createBusyThread();
    br.readLine();
    Object obj = new Object();
    createLockThread(obj);
}

说明:程序运行后,首先在“线程”页签中选择main线程,如图所示。堆栈追踪显示BufferedReaderreadBytes方法中等待System.in的键盘输入,这时线程为Runnable状态,Runnable状态的线程会被分配运行时间,但readBytes方法检查到流没有更新时会立刻归还执行令牌,这种等待只消耗很小的CPU资源。

4

接着监控testBusyThread线程,如图所示。此时处于一个死循环中,不会归还线程执行令牌,会消耗很多CPU资源。

5

在执行testLockThread线程时,在等待着lock对象的nofifynotifyAll方法的出现,此时线程处于WAITTING状态,在被唤醒之前是不会被分配执行时间的。

6

这个线程只要lock对象的notify()notifyAll()方法被调用就会被激活,继续执行。下面的代码演示了无法再被激活的死锁等待。

//线程死锁等待演示
static class SynAddRunalbe implements Runnable{
    int a , b;
    public SynAddRunalbe(int a, int b){
        this.a = a;
        this.b = b;
    }
    
    @Override
    public void run(){
        synchronized(Integer.valueOf(a)){
            synchronized(Integer.valueOf(b)){
                System.out.println(a + b);
            }
        }
    }
}

public static void main(String[] args){
    for(int i = 0; i < 100; i++){
        new Thread(new SynAddRunalbe(1, 2)).start();
        new Thread(new SynAddRunalbe(2, 1)).start();
    }
}

说明:这段代码开了200个线程去分别计算1+2以及2+1的值,一般的话,for循环只需要运行2~3次就会遇到线程死锁,程序无法结束。造成死锁的原因是Integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,[-128, 127]之间的数字会被缓存,当valueOf()方法传入参数在这个范围之内,将直接返回缓存中的对象。即代码中调用了200valueOf()方法一共就只返回了两个不同的对象。加入在某个线程的两个synchronized块之间发生了一次线程切换,那就会出现线程A等着被线程B持有的Integer.valueOf(1),线程B又等着被线程A持有的Integer.valueOf(2),结果出现大家都抛不下去的情景。

出现死锁之后,点击JConsole线程面板的“监测到死锁”的按钮,将出现一个新的“死锁”页签,如图所示。

7

从图中可以看到,线程Thread-43在等待一个被线程Thread-12持有的Integer对象,而点击线程Thread-12则显示它也在等待一个Integer对象,被线程Thread-43持有,这样就发生了死锁。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容