【JVM】jvisualvm和jconsole工具(堆,栈,元空间溢出分析)

简介:

jvisualvm是jdk自带的一个强大工具,可以帮助我们更好的分析数据,本文通过使用jvisualvm来分析堆内存溢出和虚拟机栈溢出,并通过线程死锁对jconsole工具加以说明;

jvisualvm:

1.jvisualvm打开方式:

打开在CMD命令行,输入jvisualvm:在这里插入图片描述
在这里插入图片描述

2.堆内存溢出:

采用不断实例化对象的方式使得内存溢出:
在这里提一下new对象的三个步骤,上篇文章已经通过反编译结果加以说明:

  1. new :在堆内存中创建一个对象的实例
  2. dup:复制操作数栈顶的值并压栈
  3. invokespecial :调用构造方法,为实例成员赋初值(上一篇文章详细说明)
  4. astore : 将栈顶的值存入局部变量,也就是返回引用
import java.util.ArrayList;
import java.util.List;

//-Xms5m   -Xmx5m   -XX:+HeapDumpOnOutOfMemoryError
//jvisualvm
public class JText {
		public static void main(String[] args) {
			List<JText> list = new ArrayList<>();
				for( ; ; ) {
					list.add(new JText());
					
				}
		}
}

设置参数,修改堆大小,并在堆内存溢出是生成hprof文件,eclipse在菜单栏中的Run下选择Run Configuration即可

在这里插入图片描述
接下来运行,结果如下:
在这里插入图片描述
因为我们设置的是5M,所以很容易溢出,结果显示对应的文件已经生成了,接下来通过jvisualvm工具分析:
点击jvisualvm左上角的文件,选择装入,找到生成的hprof文件,这里要注意编号一致

在这里插入图片描述
点击打开之后就会显示出概要:
在这里插入图片描述
然后点击类,显示出实例对象的信息:
在这里插入图片描述
在这里我们就可以清晰的看出,因为该类实例太多占据97%,
通过分析每一个实例对象可以看到详细信息,比如该类是由Appclassloader加载的,其父是扩展类加载器,在往上是启动类加载器
在这里插入图片描述
接下来在通过gc来看下jvisualvm的监测能力:

public class JText {
		public static void main(String[] args) {
			List<JText> list = new ArrayList<>();
				for( ; ; ) {
					list.add(new JText());
					System.gc();
				}
		}
}

打开正在执行的程序
在这里插入图片描述
通过监测可以看到详细的图表信息:
在这里插入图片描述
可以看到垃圾回收活动十分活跃
也可以看到当前线程的运行情况,这个功能在底下的死锁详细说明:
在这里插入图片描述

3.虚拟机栈溢出

先引用一段话对Java虚拟机栈做出介绍:

1.Java虚拟机栈也是线程私有的,它的生命周期与线程相同(随线程而生,随线程而灭)

2. 如果线程请求的栈深度大于虚拟机所允许的深度,将抛出StackOverflowError异常;

如果虚拟机栈可以动态扩展,如果扩展时无法申请到足够的内存,就会抛出OutOfMemoryError异常;

(当前大部分JVM都可以动态扩展,只不过JVM规范也允许固定长度的虚拟机栈)

3. Java虚拟机栈描述的是Java方法执行的内存模型:每个方法执行的同时会创建一个栈帧。

对于我们来说,主要关注的stack栈内存,就是虚拟机栈中局部变量表部分。


栈帧(Stack Frame)  
栈帧(Stack Frame)是用于支持虚拟机进行方法调用和方法执行的数据结构。它是虚拟机运行时数据区中的java虚拟机栈的栈元素。

栈帧存储了方法的局部变量表、操作数栈、动态连接和方法返回地址等信息。

每一个方法从调用开始至执行完成的过程,都对应着一个栈帧在虚拟机里面从入栈到出栈的过程。

在编译程序代码的时候,栈帧中需要多大的局部变量表内存,多深的操作数栈都已经完全确定了。

因此一个栈帧需要分配多少内存,不会受到程序运行期变量数据的影响,而仅仅取决于具体的虚拟机实现。


注意:
 在活动线程中,只有位于栈顶的栈帧才是有效的,称为当前栈帧,与这个栈帧相关联的方法称为当前方法。

执行引擎运行的所有字节码指令都只针对当前栈帧进行操作。

通过循环调用方法不断创建栈帧并压栈使得栈溢出

public class Jtext2 {
	private int time ;
	
	public int gettime() {
		return time;
	}
	
	public void goon() {
		this.time++;
		try {
			Thread.sleep(400);   //防止主线程结束太快,不好监测
		} catch (InterruptedException e) {
			e.printStackTrace();
		}	
		goon();
	}
	
	public static void main(String[] args) {
		Jtext2 jtext2 = new Jtext2();
		try {
		jtext2.goon();
		}catch (Throwable e) {
			System.out.println(jtext2.gettime());
			e.printStackTrace();
		}
	}
}

修改参数便于更快产生运行结果:
在这里插入图片描述

运行结果如下,栈溢出已经出显示:
在这里插入图片描述
接下来通过jvisualvm来查看:
监测结果:
在这里插入图片描述
在这里插入图片描述
查看线程,main一直停着
通过线程dump来查看详细信息:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

“main” #1 prio=5 os_prio=0 tid=0x0000000002fad800 nid=0xfa0 waiting on condition [0x00000000011b4000]
“main” #1 prio=5 main线程的优先级,默认为5
os_prio 操作系统的优先级
java.lang.Thread.State: TIMED_WAITING (sleeping)main线程一直在睡
通过这一方法就可以很清晰详细的看出线程运行情况,

4.死锁

jvisualvm也可用来监测死锁:

public class Jtex3 {
	public static void main(String[] args) {
		new Thread(new A(),"Thread-A").start();
		new Thread(new B(),"Thread-B").start();
		
		try {
			Thread.sleep(50000);//不让main线程快速结束,以便进行线程dunmp
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
}
	
	 class A implements Runnable{
		private static final Object lock1 = new Object();
		
		public static  void  amethod () {
			synchronized (lock1) {
				System.out.println("进入A");
				
				try {
					Thread.sleep(5000);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				B.bmethod();
			}
		}

		@Override
		public void run() {
			amethod ();
			
		}
	}

	 class B implements Runnable{
		 private static final Object lock2 = new Object();
			
			public static void  bmethod () {
				synchronized (lock2) {
					System.out.println("进入B");
					

					try {
						Thread.sleep(5000);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					A.amethod();
				}
			}

			@Override
			public void run() {
				bmethod();
				
				
			}
		}

一打开jviisualvm就会很直观的报出监测到死锁:
在这里插入图片描述
打开线程dump来看详细信息:
首先就给发现一个Java级别的死锁
在这里插入图片描述
接下来是对两线程的详细信息:
在这里插入图片描述
“Thread-A” #10 prio=5 os_prio=0 tid=0x00000000189ea000 nid=0xb34 waiting for monitor entry [0x000000001952e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.mec.JVM.B.bmethod(Jtex3.java:47)
- waiting to lock <0x00000000d5eb1268> (a java.lang.Object)
at com.mec.JVM.A.amethod(Jtex3.java:31)
- locked <0x00000000d5eaf5a0> (a java.lang.Object)
可以看出来线程A锁住了 <0x00000000d5eaf5a0> (a java.lang.Object),在等待<0x00000000d5eb1268> (a java.lang.Object)

“Thread-B” #11 prio=5 os_prio=0 tid=0x00000000189eb000 nid=0x2f28 waiting for monitor entry [0x000000001962e000]
java.lang.Thread.State: BLOCKED (on object monitor)
at com.mec.JVM.A.amethod(Jtex3.java:23)
- waiting to lock <0x00000000d5eaf5a0> (a java.lang.Object)
at com.mec.JVM.B.bmethod(Jtex3.java:56)
- locked <0x00000000d5eb1268> (a java.lang.Object)
线程B锁住了<0x00000000d5eb1268> (a java.lang.Object),在等待<0x00000000d5eaf5a0> ,两者刚好相互匹配上了,再次反映出jvisualvm的强大。

jconsole工具

1.jconsole打开方式:

在这里插入图片描述
在这里插入图片描述

2.另一种死锁

这次通过另外一种方式产生死锁:

package com.mec.JVM;

public class Jtext4 {
	public static void main(String[] args) {
		new Thread(() ->  C.cmethod(),"Thread C").start();
		new Thread(() ->  D.dmethod(),"Thread D").start();
		
		
		try {
			Thread.sleep(50000);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
}

 class C {
	public static synchronized void cmethod() {
		System.out.println("C");
		
		try {
			Thread.sleep(5000);
			D.dmethod();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		
	}
	 
}
 
 class D{
		public static synchronized void dmethod() {
			System.out.println("D");
			
			try {
				Thread.sleep(5000);
				C.cmethod();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			
			
		}
		 
	}
 
 

打开Jconsole可以看到当前的线程main处于WATTING状态:
在这里插入图片描述
点击检查死锁,可以清楚的发现这两个死锁
在这里插入图片描述
在这里插入图片描述
我们可以很直观的看出这个死锁和上面的那个死锁大部分相同,俩线程都是锁住一个等待对方持有的另一个,不同点是这次我们用的不是锁域而是给方法上加synchronized(上篇文章从反编译角度讲述两者区别)这次两者的锁都是该类的Class对象。

3.元空间溢出

先引用一段来自Monica Beckwith的分析

在 Java 虚拟机(以下简称JVM)中,类包含其对应的元数据,比如类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池,已确定的符号引用和虚方法表。在过去(当自定义类加载器使用不普遍的时候),类几乎是“静态的”并且很少被卸载和回收,因此类也可以被看成“永久的”。另外由于类作为 JVM 实现的一部分,它们不由程序来创建,因为它们也被认为是“非堆”的内存。在 JDK8 之前的 HotSpot虚拟机中,类的这些“永久的”数据存放在一个叫做永久代的区域。永久代一段连续的内存空间,永久代的垃圾回收和老年代的垃圾回收是绑定的,一旦其中一个区域被占满,这两个区都要进行垃圾回收。


Java8之后,永久代和堆内存已经不连在一起了,元数据被移动到一个在本地内存区域中,该区域称为元空间,J7之前的的hotspot虚拟机中,纳入字符串常量池的字符串被储存在永久代中,因此会导致一系列性能问题和内存溢出问题,而J8中由于改为在本地内存中,所以元空间的内存管理由元空间虚拟机来完成元空间的最大可分配空间就是系统可用内存空间

元空间的内存管理由元空间虚拟机来完成,其采用的形式为组块分配。元空间会被分配给其他类加载器,我们可以这样理解,每个类加载器加载完一个类后,他的命名空间中就多了这个类的信息,对应的元空间虚拟机便会分给该加载器一块区域来保存这个类的元数据,当一个类加载器消亡,他的命名空间不复存在,他对应拥有元空间的组块也全部清除并归还,一旦某个虚拟内存映射区域清空,这部分内存就会返回给操作系统。由于元空间按照组块分配,元空间虚拟机还未支持压缩功能,所以会产生碎片化问题。

接下来通过循环动态生成类,使得新类一直被加载并在元空间中分配区域直到元空间溢出;

public class JText6 {
	
		public static void main(String[] args) {
		for(;;){
			Enhancer enhancer = new Enhancer( ) ;
			enhancer.setSuperclass (JText6.class);
			enhancer.setUseCache(false) ;//是否使用缓存
			enhancer.setCallback((MethodInterceptor) (obj, method, args1, proxy) ->
					proxy. invokeSuper(obj, args1));
			
			System.out.println("RUN");
			enhancer.create(); 
		}
		
		}	

}

设置参数:
在这里插入图片描述
输出结果:
在这里插入图片描述
已经报了Metaspace溢出。

先通过jconsole分析:
在这里插入图片描述
从这里可以清晰的看出来创造的新类在疯狂的被加载,加载总数不断增加。
通过点击详细输出可以看到每个被创建出来类的详细信息。
在这里插入图片描述
jconsole分析结果已经很清楚了,这里再用jvisualvm来尝试下过程监测:
在这里插入图片描述
很清晰的看出右上角的Metaspace一直处于上升状态,左下角的类装入也处于上升状态,十分清晰明了!

通过以上所有分析可以发现jvisualvm和jconsole都是十分强大的工具,实用性非常高,灵活的使用可以帮助我们解决许多问题!

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值