java 堆使用情况_深入理解JVM(八)——java堆分析

上一节介绍了针对JVM的监控工具,包括JPS可以查看当前所有的java进程,jstack查看线程栈可以帮助你分析是否有死锁等情况,jmap可以导出java堆文件在MAT工具上进行分析等等。这些工具都非常有用,但要用好他们需要不断的进行实践分析。本文将介绍使用MAT工具进行java堆分析的案例。

内存溢出(OOM)的原因

我们常见的OOM(OutOfMemoryError)发生的原因不只是堆内存溢出,堆内存溢出只是OOM其中一种情况,OOM还可能发生在元空间、线程栈、直接内存。

下面演示在各个区发生OOM的情况:

堆OOM

public static voidmain(String[] args)

{

List list=new ArrayList();for(int i=0;i<100;i++){//构造1M大小的byte数值

Byte[] bytes=new Byte[1024*1024];//将byte数组添加到list列表中,因为存在引用关系所以bytes数组不会被GC回收

list.add(bytes);

}

}

以上程序设置最大堆内存50M,执行:

b4ddfd1c64f308d0e8f1664d4b8657d6.png

6ba751ce2206a9d9fa6f80bbabdf4bc6.png

显然程序通过循环将占用100M的堆空间,超过了设置的50M,所以发生了堆内存的OOM。

针对这种OOM,解决办法是增加堆内存空间,在实际开发中必要的时候去掉引用关系,使垃圾回收器尽快对无用对象进行回收。

元空间OOM

public static void main(String[] args) throwsException

{for(int i=0;i<1000;i++){//动态创建类

Map propertyMap = new HashMap();

propertyMap.put("id", Class.forName("java.lang.Integer"));

CglibBean bean=newCglibBean(propertyMap);//给 Bean 设置值

bean.setValue("id", new Random().nextInt(100));//打印 Bean的属性id

System.out.println("id=" + bean.getValue("id"));

}

}

以上代码通过Cglib动态创建class,设置元数据区大小为4M:

ab13c98f24f8881aecd053504fde02ea.png

由于代码循环创建class,大量的class元数据,存放在元数据区超过了设置的4M空间,因此报元数据区OOM:

9547627fcdb4840b592bd97cf400d2ab.png

解决该OOM的办法是增大MaxMetaspaceSize参数值,或者干脆不设置该参数,在默认情况元空间可使用的内存会受到本地内存的限制。

栈OOM

当创建新的线程时JVM会给每个线程分配栈内存,当创建线程过多,占用的内存也就越多,这种情况下有可能发生OOM:

public static void main(String[] args) throwsException {//循环创建线程

for (int i = 0; i < 1000000; i++) {new Thread(newRunnable() {public voidrun() {try{//线程sleep时间足够长,保证线程不销毁

Thread.sleep(200000000);

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}).start();

System.out.println("created " + i + "threads");

}

}

3620ffb6e8b0fa4e3276b2f4b0d220c4.png

很明显解决此OOM的办法是减小线程数。

直接内存OOM

public static void main(String[] args) throwsException {for (int i = 0; i < 1000000; i++) {//申请堆外内存,这个内存是本地的直接内存,并非java堆内存

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*1024*1024);

System.out.println("created "+i+" byteBuffer");

}

}

ByteBuffer的allocateDirect方法可以申请直接内存,当申请的内存超过的本地可用内存时,会报OOM:

ce3815ee465e2114b560279b2e24aff1.png

解决该OOM的办法是适当使用堆外内存,如有必要可显式执行垃圾回收。(即在代码中执行System.gc();)

MAT工具使用

当java应用出现故障时,我们可能需要使用MAT分析问题,找出问题出现的原因,下面通过一个案例介绍MAT的使用方法:

准备:

我们事先从程序运行环境上使用jmap工具或者jvisualvm导出一个堆快照文件出来。

使用MAT工具打开:

b20305f493f17551fef5004744cbf084.png

我们发现占用内存最大的对象是AppClassLoader,我们知道AppClassLoader是用来加载应用的类,因此我们进一步查看它引用的对象。

00a1c805405eb6bb8dba42c528beaa51.png

下图显示了AppClassLoader引用的对象空间使用情况,“Shallow Heap”表示浅堆的大小,浅堆就是类自身所占用的空间大小,也就是类本身元数据的大小。“Retained Heap”表示深堆的大小,深堆表示该类以及它引用的其他类所占用空间的总和,也表示该类被垃圾回收后,所能够释放的空间大小。(如果该类被回收了,他引用的对象会变成不可达对象因此也会被回收)

536f6c1d1e04aac8a9e18d81520e54d9.png

我们随藤摸瓜,继续查看深堆占用最大的对象。

486d6a1388c5b95ca95c5cfb644ca764.png

从上图可以看出造成深堆比较大的原因是程序当中包含了一个ArrayList,他里面包含有大量的String对象,并且每个String对象有80216字节大小。

因此针对这个堆的分析基本清楚了,因为程序中包括大量的String对象,而他们又在ArrayList当中,引用关系一直存在,因此无法被垃圾回收,造成OOM。

MAT其他功能说明

除了上述我们使用到的MAT功能外,还有一些功能也是经常用到的。

Histogram:显示每个类使用情况以及占用空间大小。

390ab5eae05e03450564f1a1188d43a3.png

上图可以看到char[]类,有1026个对象,占用5967480字节的空间,通过上面的分析得出结论是String对象占用了大部分的空间,而Stirng对象内部存放字符使用char[]来存放的,所以这里显示char[]的浅堆大小为5967480字节也是可以理解的。

Thread_overview:显示线程相关的信息。

70b2af1d7e5dfe9dad8aa5126fb7024b.png

OQL:通过类似SQL语句的表达式查询对象信息。

0f366f184b33fcdcc9e84c42103730e6.png

上图通过OQL语句查询字符串中匹配123的String对象。

结语

本文首先介绍了java程序中出现OOM的几种情况,然后通过简单的案例介绍了MAT的基本用法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值