IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 深入理解java虚拟机(十)JConsole:Java监视与管理控制台 -> 正文阅读

[Java知识库]深入理解java虚拟机(十)JConsole:Java监视与管理控制台


一、JConsole介绍

JConsole( Java Monitoring and Management Console)是一款基于JMX( Java Manage-ment Extensions)的可视化监视、管理工具。它的主要功能是通过JMX的MBean( Managed Bean)对系统进行信息收集和参数动态调整。 JMX是一种开放性的技术,不仅可以用在虚拟机本身的管理上,还可以运行于虚拟机之上的软件中,典型的如中间件大多也基于JMX来实现管理与监控。虚拟机对JMXMBean的访问也是完全开放的,可以使用代码调用API、支持JMX协议的管理控制台,或者其他符合JMX规范的软件进行访问。
在这里插入图片描述


二、启动JConsole

通过JDK/bin目录下的jconsole.exe启动JCon-sole后,会自动搜索出本机运行的所有虚拟机进程,而不需要用户自己使用jps来查询,如上图所示。双击选择其中一个进程便可进入主界面开始监控。

JMX支持跨服务器的管理,也可以使用下面的“远程进程”功能来连接远程服务器,对远程虚拟机进行监控。

上图中可以看到笔者的机器现在运行了Eclipse、 JConsole、 MonitoringTest三个本地虚拟机进程,这里MonitoringTest是笔者准备的“反面教材”代码之一。双击它进入JConsole主界面,可以看到主界面里共包括“概述”“内存”“线程”“类”“VM摘要”“MBean”六个页签,如下图所示。
在这里插入图片描述
“概述”页签里显示的是整个虚拟机主要运行数据的概览信息,包括“堆内存使用情况”“线程”“类”“CPU使用情况”四项信息的曲线图,这些曲线图是后面“内存”“线程”“类”页签的信息汇总。


三、内存监控

“内存”页签的作用相当于可视化的jstat命令,用于监视被收集器管理的虚拟机内存(被收集器直接管理的Java堆和被间接管理的方法区)的变化趋势。我们通过运行代码清单中的代码来体验一下它的监视功能。运行时设置的虚拟机参数为:

-Xms100m -Xmx100m -XX:+UseSerialGC

代码清单 JConsole监视代码:

/**
* 内存占位符对象,一个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);
}

这段代码的作用是以64KB/50ms的速度向Java堆中填充数据,一共填充1000次,使用JConsole的“内存”页签进行监视,观察曲线和柱状指示图的变化。

程序运行后,在“内存”页签中可以看到内存池Eden区的运行趋势呈现折线状,如下图所示。监视范围扩大至整个堆后,会发现曲线是一直平滑向上增长的。从柱状图可以看到,在1000次循环执行结束,运行了System.gc()后,虽然整个新生代Eden和Survivor区都基本被清空了,但是代表老年代的柱状图仍然保持峰值状态,说明被填充进堆中的数据在System.gc()方法执行之后仍然存活。
在这里插入图片描述
提两个小问题供读者思考一下:

  • 1)虚拟机启动参数只限制了Java堆为100MB,但没有明确使用-Xmn参数指定新生代大小,读者能否从监控图中估算出新生代的容量?

  • 2)为何执行了System.gc()之后,图4-12中代表老年代的柱状图仍然显示峰值状态,代码需要如何调整才能让System.gc()回收掉填充到堆中的对象?

答案如下:

  • 问题1答案:上图显示Eden空间为27328KB,因为没有设置-XX: SurvivorRadio参数,所以Eden与Survivor空间比例的默认值为8∶ 1,因此整个新生代空间大约为27328KB×125%=34160KB。

  • 问题2答案:执行System.gc()之后,空间未能回收是因为Listlist对象仍然存活,fillHeap()方法仍然没有退出,因此list对象在System.gc()执行时仍然处于作用域之内[1]。如果把System.gc()移动到fillHeap()方法外调用就可以回收掉全部内存。


四、线程监控

如果说JConsole的“内存”页签相当于可视化的jstat命令的话,那“线程”页签的功能就相当于可视化的jstack命令了,遇到线程停顿的时候可以使用这个页签的功能进行分析。前面讲解jstack命令时提到线程长时间停顿的主要原因有等待外部资源(数据库连接、网络资源、设备资源等)、死循环、锁等待等,下述代码清单将分别演示这几种情况。

/**
* 线程死循环演示
*/
public static void createBusyThread() {
	Thread thread = new Thread(new Runnable() {
	@Override
	public void run() {
		while (true) // 第41行
			;
		}
	}, "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) throws Exception {
	BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
	br.readLine();
	createBusyThread();
	br.readLine();
	Object obj = new Object();
	createLockThread(obj);
}

程序运行后,首先在“线程”页签中选择main线程,如下图所示。堆栈追踪显示BufferedReader的readBytes()方法正在等待System.in的键盘输入,这时候线程为Runnable状态, Runnable状态的线程仍会被分配运行时间,但readBytes()方法检查到流没有更新就会立刻归还执行令牌给操作系统,这种等待只消耗很小的处理器资源。
在这里插入图片描述
接着监控testBusyThread线程,如下图所示。 testBusyThread线程一直在执行空循环,从堆栈追踪中看到一直在MonitoringTest.java代码的41行停留, 41行的代码为while(true)。这时候线程为Runnable状态,而且没有归还线程执行令牌的动作,所以会在空循环耗尽操作系统分配给它的执行时间,直到线程切换为止,这种等待会消耗大量的处理器资源。

在这里插入图片描述
下图显示testLockThread线程在等待lock对象的notify()或notifyAll()方法的出现,线程这时候处于WAITING状态,在重新唤醒前不会被分配执行时间。
在这里插入图片描述
testLockThread线程正处于正常的活锁等待中,只要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循环都是可省略的,两个线程也可能会导致死锁,不过那样概率太小,需要尝试运行很多次才能看到死锁的效果。如果运气不是特别差的话,上面带for循环的版本最多运行两三次就会遇到线程死锁,程序无法结束。造成死锁的根本原因是Integer.valueOf()方法出于减少对象创建次数和节省内存的考虑,会对数值为-128~127之间的Integer对象进行缓存,如果valueOf()方法传入的参数在这个范围之内,就直接返回缓存中的对象。也就是说代码中尽管调用了200次Integer.valueOf()方法,但一共只返回了两个不同的Integer对象。假如某个线程的两个synchronized块之间发生了一次线程切换,那就会出现线程A在等待被线程B持有的Integer.valueOf(1),线程B又在等待被线程A持有的Integer.valueOf(2),结果大家都跑不下去的情况。

出现线程死锁之后,点击JConsole线程面板的“检测到死锁”按钮,将出现一个新的“死锁”页签,如图所示。
在这里插入图片描述
图中很清晰地显示,线程Thread-43在等待一个被线程Thread-12持有的Integer对象,而点击线程Thread-12则显示它也在等待一个被线程Thread-43持有的Integer对象,这样两个线程就互相卡住,除非牺牲其中一个,否则死锁无法释放。


结尾

  • 感谢大家的耐心阅读,如有建议请私信或评论留言。
  • 如有收获,劳烦支持,关注、点赞、评论、收藏均可,博主会经常更新,与大家共同进步
  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-27 11:44:09  更:2021-08-27 11:44:46 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 10:18:31-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码