深入理解JAVA虚拟机读书笔记(9)

    上节主要介绍JDK bin下的一些虚拟机性能监控与故障处理的工具,本节将介绍JDK的可视化工具JConsole。除了JConsole以外还有可视化工具Visual VM,这里就不在介绍了,可以参考书本。

    JConsole(Java监视与管理的工具):直接执行bin下jconsole.exe后会自动搜索本级运行的所有虚拟机进行,不需要jps来查询。界面具体如下所示

                                深入理解JAVA虚拟机读书笔记(9)_第1张图片

    进入之后可以看见概述、内存、线程、类、VM摘要以及MBean这六个标签。

深入理解JAVA虚拟机读书笔记(9)_第2张图片

    这里介绍下其中3个标签内容(另外3个在以后了解其他知识的时候会介绍):

    1.概述:显示的是整个虚拟机主要运行数据的概览,其中包括堆内存使用情况、线程、类、CPU使用情况4中信息的曲线图。

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

        例子:这里执行书上一段例子来体验下这个监视功能。运行时,配置JVM参数-Xms100m -Xmx100m -XX:+UseSerialGC

        运行代码:

    public class JConsoleTest {
	
	/**
	*内存占位符对象,一个OOMObject大约占64KB
	*/
	static class OOMObject{
		public byte[] placeholder = new byte[64*1024];
	}
	
	public static void fillHeap(int num)throws InterruptedException{
		Listlist = new ArrayList();
		for(int i=0;i

    深入理解JAVA虚拟机读书笔记(9)_第3张图片

    结果如图所示,Eden区呈现折线状,而整个堆是一条向上增长的平滑曲线。从右下角下面的图可以看出来,在1000次循环执行结束,运行gc后,整个新生代的Eden区会被清空,但是老年代仍保持峰值,这说明在gc后那些数据依旧存活着。这是为什么呢?首先这里先介绍下怎么计算新生代大小,这里的eden区的空间是27382KB,然后我们没有设置-XX:SurvivorRadio参数去配置Eden区和Survivor区的比例,那就是默认8:1,因此整个新生代的大小是27382/8*10=34227.5。好了,那么接下去,我们去看为什么gc后那些数据依旧存活着。在执行完gc后,list对象依然存活,fillHeap方法没有退出,因此list对象在gc执行时,仍然处于作用域之内,把gc移动到fillHeap之外即可全部回收掉内存。

    3.线程:上面内存相当于jstat命令,这里就相当于jstack命令,遇到线程停顿时,可以在这里进行监控分析。下面通过例子来查看线程监控的变化。

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

    运行这个程序后,打开JConsole,可以看到如下图所示

深入理解JAVA虚拟机读书笔记(9)_第4张图片

    根据堆栈跟踪,可以看到BufferedReader在readBytes方法中等待System.in的键盘输入,这时main线程为Runnable状态,Runnable状态的线程会被分配运行时间,但readBytes方法检查到流没有更新时会立刻归还执行令牌,这种等待只消耗很小的CPU资源。

    接下去,我们键盘输入后,可以看到活动的线程数变了,线程中多了testBusyThread线程,我们监控testBusyThread看看,如下图所示。

深入理解JAVA虚拟机读书笔记(9)_第5张图片

    testBusyThread线程一直在执行空循环,从堆栈追踪中看到一直在JConsoleTestTwo.java代码的16行停留,16行为:while(true)。 这时候线程为Runnable状态,而且没有归还线程执行令牌的动作,会在空循环上用尽全部执行时间直到线程切换,这种等待会消耗较多的CPU资源。

    下面去掉注释,然后把testBusyThread方法进行注释,来看看活锁等待线程监控的效果,如下图所示

深入理解JAVA虚拟机读书笔记(9)_第6张图片

    可以看出testLockThread线程在等待着lock对象的notify或者notifyAll。线程状态处于WAITING状态。这里的testLockThread还是处于活锁状态,当lock对象的notify或者notifyAll方法被执行之后,testLockThread线程就会被激活继续执行了,下面来看下死锁的例子,循环100次为了提高造成死锁的概率,代码具体不介绍了,应该都能看到,代码如下所示:

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

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);
			}
		}
	}
}

        执行这段代码后,在JConsole中点击检查死锁,可以看到如下图所示三个死锁的线程

深入理解JAVA虚拟机读书笔记(9)_第7张图片

    上图很清晰地显示了线程Thread-168在等待一个被线程Thread-163持有Integer对象,而点击线程Thread-163则显示它也在等待一个被线程Thread-168持有Integer对象,这样就存在死锁了,都不存在等到锁释放的希望了。


你可能感兴趣的:(JVM)