虚拟机性能监控与故障处理(1)

本文详细介绍JConsole工具的使用,涵盖启动方法、主要功能模块及内存、线程监控实践。通过具体示例展示如何监控Java应用的内存使用趋势、线程状态及死锁情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面的话

在很多大公司,对系统监控有较高的要求,也会自建很多监控平台。

在以前处理生产问题时,会用到jvm的一些工具,比如jstack命令,但是没有很系统,很多时候都是现用现学。所以打算抽时间整体梳理一下这块。在此声明一下,本文章参考周志明老师的《深入理解Java虚拟机》,如果感兴趣,可以购买一本,链接就不放了,以免有广告之嫌

传送门

JDK的可视化工具

JConsole

JConsole(Java Monitoring and Management Console)是jdk1.5就已经提供的虚拟机监控工具,是一种基于JMX的可视化监视、管理工具

启动JConsole

找到jdk的安装目录,打开jconsole工具,Windows是jconsole.exe(Linux可以执行/usr/libexec/java_home -V找到安装目录)

启动之后会自动搜索本机运行的所有虚拟机进程,不需要在用jps查询,选择其中一个进程即可开始监控。

远程的链接的下次再介绍,需要进行jvm配置

从上面可以看到2个进程,分别是JConsole、Idea。写一个testNg测试用例链接看一下界面

可以看到主界面包括"概览"、"内存"、"线程"、"类"、"VM概要"、"MBean"等6个页签。

其中"概述"的页签显示整个虚拟机的主要运行数据概览

内存监控

内存页签相当于jstat命令,用于监视受收集器管理的虚拟机内存(Java堆和永久代)的变化趋势。

通过一个例子来感受一下它的监视功能。运行之前先对虚拟机参数设置一

-Xms100m -Xmx100m -XX:+UseSerialGC

实例代码:以64KB/50毫秒的速度往Java堆中填充数据,一共填充1000次

public class Test1 {

    /**
     * 内存占用对象,一个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<>();
        for (int i = 0; i < num; i++) {
            Thread.sleep(100);
            list.add(new OOMObject());
        }
        System.gc();
    }

    public static void main(String[] args) throws InterruptedException {
        fillHeap(2000);
    }
}

程序运行后,在"内存"页签中可以看到内存池Eden区的运行趋势呈折现状,扩大到整个堆后,曲线是一条向上的平滑曲线,直到OOM.

新生代内存

堆上内存

从Eden Space上面可以看到大小为27328KB,那整个新生代大小是多少呢?虚拟机启动参数没有设置-Xmn,默认Eden与Survivor为8:1,计算一下:27328/4*5=34160KB

线程监控

上面的"内存"页签相当于jstat的话,那么线程"线程页签"就相当于jstack,遇到线程停顿时可以使用这个页签进行监控分析。

线程长时间停顿的原因可能有:

  • 等待外部资源:数据库连接、网络资源、设备资源等
  • 死循环
  • 锁等待:活锁、死锁

看一下代码例子

/**
 * Alipay.com Inc. Copyright (c) 2004-2020 All Rights Reserved.
 */
package com.alipay.gmcore.enhancetest.testcase.fliggy.callback;

import java.io.BufferedReader;
import java.io.InputStreamReader;

/**
 * @author laoke.tw
 * @version $Id: Test2.java, v 0.1 2020-03-18 11:29 PM laoke.tw Exp $$
 */
public class Test2 {

    /**
     * 线程死循环演示
     */
    public static void createBusyThread() {
        Thread thread = new Thread(() -> {
            while (true) {

            }
        }, "testBusyThread");
        thread.start();
    }

    /**
     * 线程锁等待演示
     * @param lock
     */
    public static void createLockThread(final Object lock) {
        Thread thread = new Thread(() -> {
            synchronized (lock) {
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "testLockThread");
        thread.start();
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        br.readLine();

        createBusyThread();

        Object obj = new Object();
        createLockThread(obj);
    }
}

先监控main方法,这时线程为RUNNABLE状态,堆栈追踪显示BufferedInputStream在readBytes方法中等待System.in输入。

这种等待会消耗很小的CPU资源,因为虽然RUNNABLE状态的线程会被分配运行时间,但是readBytes方法在检测到没有流输入时,会立刻归还执行令牌。

接着在控制台输入一个值,然后监控:testBusyThread,发现也是RUNNABLE状态

堆栈追踪到代码20行,为while(true),这个时候会一直进行空循环直到线程切换,会消耗较多的CPU资源。

最后在看一下testLockThread线程,显示是WAITING状态,在等待lock对象通过notify或notifyAll唤醒。在这之前都不会分配时间片

注意到下面有一个"检测死锁"的按你,点击一下试试

发现未检测到死锁,表明这是一种活锁的等待状态,只要对象的notify或notifyAll方法被调用,这个线程就可以继续执行。

死锁等待

public class Test3 {

    static class syncAddRunnable implements Runnable {

        int a, b;

        public syncAddRunnable(int a, int b) {
            this.a = a;
            this.b = b;
        }

        /**
         * When an object implementing interface <code>Runnable</code> is used to create a thread, starting the thread causes the object's
         * <code>run</code> method to be called in that separately executing
         * thread.
         * <p>
         * The general contract of the method <code>run</code> is that it may take any action whatsoever.
         *
         * @see Thread#run()
         */
        @Override
        public void run() {
            synchronized (Integer.valueOf(a)) {
                synchronized (Integer.valueOf(b)) {
                    System.out.println(a + b);
                }
            }
        }
    }

    public static void main(String[] args) throws Exception {
        for (int i = 0, j = 1000; i < j; i++) {
            new Thread(new syncAddRunnable(1, 2)).start();
            new Thread(new syncAddRunnable(2, 1)).start();
        }
    }
}

执行一下上面这串代码,如无意外会出现下面这个死锁现象(看机器性能,如果for循环过少,也有可能看不到)

发现,Thread-590合Thread-591相互等待。

造成死锁的原因是Integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,会将[-128,127]之间的数字缓存,当valueOf传入的参数在这个范围内,将之间返回缓存中的对象。也就是说代码调用了2000次Integer.valueOf()方法一共就返回了2个对象。

假如在某个线程的两个synchronized块之间发生了一次线程切换,那就会出现线程A等待线程B持有的Integer.valueOf(1),而线程B又等待线程A持有的Integer.valuefo(2),出现互相等待,导致大家都跑不出下去进而死锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值