4.3 JDK的可视化工具

目录

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

1 启动JConsole

2 内存监控

3 线程监控

4.3.2 VisualVM:多合一故障处理工具

1. VisualVM兼容范围与插件安装

2. 生成和浏览堆转储快照

3. 分析程序性能

4. BTrace动态日志跟踪


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

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

1 启动JConsole

 通过JDK/bin目录下的“jconsole.exe”启动JConsole后,将自动搜索出本机运行的所有虚拟机进程,不需要用户自己再使用jps来查询了,如下图所示。双击选择其中一个进程即可开始监听,也可以使用下面的“远程进程”功能来连接远程服务器,对远程虚拟机进行监控。

  从上图可以看出,该机器现在运行了Eclipse、JConsole和MonitoringTest三个本地虚拟机进程,其中MonitoringTest就是准备的“反面教材”代码之一。双击他进入JConsole主界面,可以看到主界面里共包括“概述”、“内存”、“线程”、“类”、“VM摘要”、“MBean”6个页签,如下图所示。

2 内存监控

“内存”页面相当于可视化的jstat命令,用于监视受收集器管理的虚拟机内存(Java堆和永久代)的变化趋势。我们通过运行下面代码来体验一下他的监视功能。运行时设置的虚拟机参数为:-Xms100m-XX :  +UseSerialGC,这段代码的作用是以64KB/50毫秒的速度往Java堆中填充数据,一共填充1000次,使用JConsole的“内存”页签进行监视,观察曲线和柱状图指示图的变化。

/**
 *代码清单4-6 JConsole监视代码
 * 内存占位符对象,一个OOMObject大约占64KB
 * VM Args:-verbose:gc -Xms100M -Xmx100M -XX:+UseSerialGC
 */
package cn.chapter4;

import java.util.ArrayList;
import java.util.List;

public class JConsole1 {
	
	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);
	}

}

运行结果(这里实际不关系这个)

[GC 27309K->26568K(99008K), 0.0332868 secs]
[GC 53890K->53592K(99008K), 0.0368004 secs]
[Full GC 64470K->64414K(99008K), 0.0329244 secs]

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

  • 虚拟机启动参数只限制了Java堆为100MB,没有指定-Xmn参数,能否从监控图中估计出新生代有多大?

下图显示Eden空间为27 328KB,因为没有设置-XX : SurvivorRadio参数,所以Eden与Survivor空间比例为默认值8:1,整个新生代空间大约为27 328KB*125%=34 160KB。

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

执行完System.gc()之后,空间未能回收是因为List<OOMObject> list对象仍然存活,fillHeap()方法仍然没有退出,因此list对象在System.gc()执行时仍然处于作用域之内。如果把System.gc()移动到fillHeap()方法外调用就可以回收掉全部内存。

 为啥我的监控结果和书上不一样啊,监控同样的程序啊????待解决。。。。

3 线程监控

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

代码4-7

/**
 * 代码线程4-7  线程等待演示代码
 */
package cn.chapter4;

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

public class JConsole2 {
	
	//线程死循环演示
	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);

	}

}

 没有运行结果,直接取jdk安装路径中找到bin目录下的JConsole.exe观察如下: 

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

   接着监控testBusyThread线程,如下图所示,testBusyThread线程一直在执行空循环,从堆栈追踪中看到一直在MonitoringTest.java代码的41行停留,41行为:while(true)。这时候线程为Runnable状态,而且没有归还线程执行令牌的动作,会在空循环上用尽全部执行时间直到线程切换,这种等待会消耗较多的CPU资源。我自己运行程序并监控时,没找到这个线程,原因未知,待解决。。。。。

  下图显示testLockThread线程在等待着lock对象的notify或notifyAll方法的出现,线程这时候处于WAITING状态,在被唤醒前不会被分配执行时间。我自己运行程序并监控时,没找到这个线程,原因未知,待解决。。。。。

   testLockThread线程正在处于正常的活锁等待,只要lock对象的notify()或notifyAll()方法被调用,这个线程便能激活以继续执行。下面代码演示了一个无法再被激活的死锁等待。

 这段代码开了200个线程去分别计算1+2以及2+1的值,其中for循环是可省略的,两个线程也可能会导致死锁,不过那样概率太小,需要尝试运行很多次才能看到效果。一般的话,带for循环的版本最多运行2~3次就会遇到线程死锁,程序无法结束。造成死锁的原因是Integer.valueOf()方法基于减少对象创建次数和节省内存的考虑,[-128, 127]之间的数字会被缓存,当valueOf()方法传入参数在这个范围之内,将直接返回缓存中的对象。也就是说,代码中调用了200次Integer.value()方法一共就只返回了两个不同的对象。假如在某个线程的两个synchronized块之间发生了一次线程切换,那就会出现线程A等着被线程B持有的Integer.valueOf(1),线程B又等着被线程A持有的Integer.valueOf(2),结果出现大家都跑不下去的情景。

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

 上图中很清晰的显示了线程Thread-43在等待一个被线程Thread-12持有Integer对象,而点击线程Thread-12则显示他也在等待一个Integer对象,被线程Thread-43持有,这样两个线程就互相卡住,都不存在等到锁释放的希望了。

4.3.2 VisualVM:多合一故障处理工具

 VisualVM(All-in-One Java Troubleshooting Tool)是到目前为止随JDK发布的功能最强大的运行监视和故障处理程序,并且可以遇见在未来一段时间内都是官方主力发展的虚拟机故障处理工具。官方在VisualVM的软件说明中写上了“All-in-One”的描述字样,预示着他除了运行监视、故障处理外,还提供了很多其他方面的功能。如性能分析(Profiling),VisualVM的性能分析功能甚至比起JProfiler、YourKit等专业且收费的Profiling工具都不会逊色多少,而且VisualVM还有一个很大的优点:不需要被监视的程序基于特殊Agent运行,因此他对应用程序的实际性能的影响很小,使得他可以直接应用在生产环境中。这个优点是JProfiler、YourKit等工具无法与之媲美的。

1. VisualVM兼容范围与插件安装

VisualVM基于NetBeans平台开发,因此他一开始就具备了插件扩展功能的特性,通过插件扩展支持,VisualVM可以做到:

  • 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)。
  • 监视应用程序的CPU、GC、堆、方法区以及线程的信息(jstat、jstack)。
  • dump以及分析堆转储快照(jmap、jhat)。
  • 方法级的程序运行性能分析,找到被调用最多、运行时间最长的方法。
  • 离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立一个快照,可以将快照发送开发者处进行Bug反馈。
  • 其他plugins的无限的可能性......

        VisualVM在JDK 1.6 update 7中才首次出现,但并不意味着他只能监控运行于JDK 1.6上的程序,他具备很强的向下兼容能力,甚至能向下兼容至近10年前发布的JDK 1.4.2平台,这对无数已经处于实施、维护的项目很有意义。当然,并非所有功能都能完美的向下兼容。

        首次启动VisualVM后,不必着急找应用程序进行检测,因为现在VisualVM还没有加载任何插件,虽然基本的监视、线程面板的功能都以默认插件的形式提供了,但是不给VisualVM装任何扩展插件,就相当于放弃了他最精华的功能,和没有安装任何应用软件操作系统差不多。

        插件可以进行手工安装,在相关网站上下载*.nbm包后,点击“工具”→“插件”→“已下载”菜单,然后在弹出的对话框中指定nbm包路径便可进行安装,插件按照后存放在JDK_HOME/lib/visumalvm/visualvm中。不过手工安装并不常用,使用VisualVM的自动安装功能已经可以找到大多数所需的插件,在有网络连接的环境下,点击“工具”→“插件菜单”,弹出下图所示的插件页面,在页签的“可用插件”中列举了当前版本VisualVM可以使用的插件,选中插件后在右边窗口将显示这个插件的基本信息,如开发者、版本、功能描述等。

VisualVM安装:

我的是win10系统,jdk1.8,可以用visualvm_139;若是jdk是更高版本,需要visualvm对应版本。

visualvm官网:https://visualvm.github.io

链接:https://pan.baidu.com/s/18XzrTiZ7d3c3s5NSh6N1xw 
提取码:4613 
 

超级坑爹,visualvm_139我成功安装了,但是在这个软件上自动检测安装插件时却报错:

Wranning:Unable to connect to the VisualVM Plugins Center because of Received fatal alert: protocol_version。

Check your proxy settings or try again later. The server may be unavailable at the moment. You may also want to make sure that your firewall is not blocking network traffic.

没找到解决办法,重新下载另一个版本visualvm_138

还是不行

Warnning:

Unable to connect to the VisualVM Plugins Center because of Server returned HTTP response code: 503 for URL: http://www.oracle.com/splash/java.net/maintenance/index.html

Check your proxy settings or try again later. The server may be unavailable at the moment. You may also want to make sure that your firewall is not blocking network traffic.

待解决。。。。插件没有安装成功

2. 生成和浏览堆转储快照

 在VisualVM中生成dump文件有两种方式,可以执行下列任一操作:

  • 在“应用程序”窗口中右键点击应用程序节点,然后选择“堆Dump”。
  • 在“应用程序”窗口中双击应用程序节点以打开应用程序标签,然后在“监视”标签中单机“堆Dump”。

        生成了dump文件之后,应用程序页签将在该堆的应用程序下增加一个以[heapdump]开头的子节点,并且在主页签中打开了该转储快照,如下图所示。如果需要把dump文件保存或发送出去,要在heapdump节点上右键选择“另存为”菜单,否则当VisualVM关闭时,生成的dump文件会被当作临时文件删除掉。要打开一个已经存在的dump文件,通过文件菜单中的“装入”功能,选择硬盘的dump文件即可。

 从堆页签中的“摘要”面板可以看到应用程序dump时的运行时参数、System.getProperties()的内容、线程堆栈等信息,“类”面板则是以类为统计口径统计类的实例数量、容量信息,“实例”面板不能直接使用,因为不能确定用户想查看哪个类的实例,所以需要通过“类”面板进入,在“类”中选择一个关心的类后双击鼠标,即可在“实例”里面看见此类中500个实例的具体属性信息。“OQL控制台”面板中就是运行OQL查询语句的。

 

3. 分析程序性能

 在Profiler页签中,VisualVM提供了程序运行期间方法级的CPU执行时间分析以及内存分析,做Profiling分析肯定会对程序运行性能有比较大的影响,所以一般不在生产环境中使用这项功能。

        要开始分析,先选择“CPU”和“内存”按钮中的一个,然后切换到应用程序中对程序进行操作,VisualVM会记录到这段时间中应用程序执行过的方法。如果是CPU分析,将会统计每个方法的执行次数、执行耗时;如果是内存分析,则会统计每个方法关联的对象数以及这些对象所占的空间。分析结束后,点击“停止”按钮结束监控过程,如下图所示。

注意,在JDK 1.5之后,在Client模式下的虚拟机加入并且自动开启了类共享——这是一个在多虚拟机进程中共享rt.jar中类数据以提高加载速度和节省内存的优化,而根据相关Bug报告的反映,VisualVM的Profiler功能可能会因为类共享而导致被监视的应用程序崩溃,所以进行Profiling前,最好在被监视程序中使用-Xshare : off参数来关闭类共享优化。

        上图是对Eclipse IDE一段操作的录制和分析结果,分析自己的应用程序时,可以根据实际业务的复杂与方法的时间、调用次数做比较,找到最有优化价值的方法。

4. BTrace动态日志跟踪

插件为安装成功,无法继续,卡了好久,先跳过。。。。。

猜你喜欢

转载自blog.csdn.net/Quantum_Dog/article/details/87608755
4.3
今日推荐