上一节介绍了针对JVM的监控工具,包括JPS能够查看当前全部的java进程,jstack查看线程栈能够帮助分析是否有死锁等状况,jmap能够导出java堆文件在MAT工具上进行分析等等。这些工具都很是有用,但要用好他们须要不断的进行实践分析。本文将介绍使用MAT工具进行java堆分析的案例。java
常见的OOM(OutOfMemoryError)发生的缘由不仅是堆内存溢出,堆内存溢出只是OOM其中一种状况,OOM还可能发生在元空间、线程栈、直接内存。数组
下面演示在各个区发生OOM的状况:dom
public static void main(String[] args) { List<Byte[]> list=new ArrayList<Byte[]>(); for(int i=0;i<100;i++){ //构造1M大小的byte数值 Byte[] bytes=new Byte[1024*1024]; //将byte数组添加到list列表中,由于存在引用关系因此bytes数组不会被GC回收 list.add(bytes); } }
以上程序设置最大堆内存50M,执行:工具
显然程序经过循环将占用100M的堆空间,超过了设置的50M,因此发生了堆内存的OOM。spa
针对这种OOM,解决办法是增长堆内存空间,在实际开发中必要的时候去掉引用关系,使垃圾回收器尽快对无用对象进行回收。.net
public static void main(String[] args) throws Exception { for(int i=0;i<1000;i++){ //动态建立类 Map<Object,Object> propertyMap = new HashMap<Object, Object>(); propertyMap.put("id", Class.forName("java.lang.Integer")); CglibBean bean=new CglibBean(propertyMap); //给 Bean 设置值 bean.setValue("id", new Random().nextInt(100)); //打印 Bean的属性id System.out.println("id=" + bean.getValue("id")); } }
以上代码经过Cglib动态建立class,设置元数据区大小为4M:线程
因为代码循环建立class,大量的class元数据,存放在元数据区超过了设置的4M空间,所以报元数据区OOM:3d
解决该OOM的办法是增大MaxMetaspaceSize参数值,或者干脆不设置该参数,在默认状况元空间可以使用的内存会受到本地内存的限制。code
当建立新的线程时JVM会给每一个线程分配栈内存,当建立线程过多,占用的内存也就越多,这种状况下有可能发生OOM:对象
public static void main(String[] args) throws Exception { //循环建立线程 for (int i = 0; i < 1000000; i++) { new Thread(new Runnable() { public void run() { try { //线程sleep时间足够长,保证线程不销毁 Thread.sleep(200000000); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); System.out.println("created " + i + "threads"); } }
很明显解决此OOM的办法是减少线程数。
public static void main(String[] args) throws Exception { for (int i = 0; i < 1000000; i++) { //申请堆外内存,这个内存是本地的直接内存,并不是java堆内存 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1024*1024*1024); System.out.println("created "+i+" byteBuffer"); } }
ByteBuffer的allocateDirect方法能够申请直接内存,当申请的内存超过的本地可用内存时,会报OOM:
解决该OOM的办法是适当使用堆外内存,若有必要可显式执行垃圾回收。(即在代码中执行System.gc();)
当java应用出现故障时,可能须要使用MAT分析问题,找出问题出现的缘由,下面经过一个案例介绍MAT的使用方法:
准备:
事先从程序运行环境上使用jmap工具或者jvisualvm导出一个堆快照文件出来。
使用MAT工具打开:
发现占用内存最大的对象是AppClassLoader,咱们知道AppClassLoader是用来加载应用的类,所以咱们进一步查看它引用的对象。
下图显示了AppClassLoader引用的对象空间使用状况,“Shallow Heap”表示浅堆的大小,浅堆就是类自身所占用的空间大小,也就是类自己元数据的大小。“Retained Heap”表示深堆的大小,深堆表示该类以及它引用的其余类所占用空间的总和,也表示该类被垃圾回收后,所可以释放的空间大小。(若是该类被回收了,他引用的对象会变成不可达对象所以也会被回收)
随藤摸瓜,继续查看深堆占用最大的对象。
从上图能够看出形成深堆比较大的缘由是程序当中包含了一个ArrayList,他里面包含有大量的String对象,而且每一个String对象有80216字节大小。
所以针对这个堆的分析基本清楚了,由于程序中包括大量的String对象,而他们又在ArrayList当中,引用关系一直存在,所以没法被垃圾回收,形成OOM。
除了上述使用到的MAT功能外,还有一些功能也是常常用到的。
Histogram:显示每一个类使用状况以及占用空间大小。
上图能够看到char[]类,有1026个对象,占用5967480字节的空间,经过上面的分析得出结论是String对象占用了大部分的空间,而Stirng对象内部存放字符使用char[]来存放的,因此这里显示char[]的浅堆大小为5967480字节也是能够理解的。
Thread_overview:显示线程相关的信息。
OQL:经过相似SQL语句的表达式查询对象信息。
上图经过OQL语句查询字符串中匹配123的String对象。
本文首先介绍了java程序中出现OOM的几种状况,而后经过简单的案例介绍了MAT的基本用法。