Java堆分析
大纲:
n 内存溢出(OOM)的原因
n MAT使用基础
– 浅堆(Shallow Heap)与深堆(Retained Heap)
– 显示入引用(incoming)和出引用(outgoing)
– 支配树
n 使用Visual VM分析堆
n Tomcat OOM分析案例
内存溢出(OOM)的原因
在JVM中,有哪些内存区间?
直接内存:
不是Java虚拟机规范中定义的内存区域,但是这一部分仍然会出现OutOfMemoryError内存溢出异常。
直接内存区域是全局共享的内存区域。
直接内存区域可以进行自动内存管理(GC),但机制并不完善。
本机的 Native 堆(直接内存) 不受 JVM 堆内存大小限制。
直接分配在物理内存中,并不占用堆空间,其可申请的最大内存受操作系统限制。可以通过参数-XX:MaxDirectMemorySize=1M来设置可分配的直接内存的大小。
堆溢出
代码:
public static void main(String args[]){ ArrayList<byte[]> list=new ArrayList<byte[]>(); for(int i=0;i<1024;i++){ list.add(new byte[1024*1024]); } } |
原因:
创建大量的对象,占用大量堆空间,直接溢出
输出:
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space at geym.jvm.ch8.oom.SimpleHeapOOM.main(SimpleHeapOOM.java:14) |
解决:
解决方法:增大堆空间,或者及时释放内存
永久区溢出
永久区是非堆的,不属于堆内存,但是它也是属于JVM进程内部的一块存储区间。
代码:
//生成大量的类 public static void main(String[] args) { for(int i=0;i<100000;i++){ CglibBean bean = new CglibBean("geym.jvm.ch3.perm.bean"+i,new HashMap()); } } |
输出:
Caused by: java.lang.OutOfMemoryError: PermGen space [Full GC[Tenured: 2523K->2523K(10944K), 0.0125610 secs] 2523K->2523K(15936K), [Perm : 4095K->4095K(4096K)], 0.0125868 secs] [Times: user=0.02 sys=0.00, real=0.01 secs] Heap def new generation total 4992K, used 89K [0x28280000, 0x287e0000, 0x2d7d0000) eden space 4480K, 2% used [0x28280000, 0x282966d0, 0x286e0000) from space 512K, 0% used [0x286e0000, 0x286e0000, 0x28760000) to space 512K, 0% used [0x28760000, 0x28760000, 0x287e0000) tenured generation total 10944K, used 2523K [0x2d7d0000, 0x2e280000, 0x38280000) the space 10944K, 23% used [0x2d7d0000, 0x2da46cf0, 0x2da46e00, 0x2e280000) compacting perm gen total 4096K, used 4095K [0x38280000, 0x38680000, 0x38680000) the space 4096K, 99% used [0x38280000, 0x3867fff0, 0x38680000, 0x38680000) ro space 10240K, 44% used [0x38680000, 0x38af73f0, 0x38af7400, 0x39080000) rw space 12288K, 52% used [0x39080000, 0x396cdd28, 0x396cde00, 0x39c80000) |
解决:
解决方法:
增大Perm区,或者允许Class回收
Java栈溢出
– 这里的栈溢出指,在创建线程的时候,需要为线程分配栈空间,这个栈空间是向操作系统请求的,如果操作系统无法给出足够的空间,就会抛出OOM
– 线程栈是操作系统分配给JVM的一块内存区域,它不在堆里面。
代码:
public static class SleepThread implements Runnable{ public void run(){ try { Thread.sleep(10000000); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String args[]){ for(int i=0;i<1000;i++){ new Thread(new SleepThread(),"Thread"+i).start(); System.out.println("Thread"+i+" created"); } } |
参数设置:
-Xmx1g -Xss1m (最大堆为1G,每个线程栈为1M)
输出:
Exception in thread "main" java.lang.OutOfMemoryError: unable to create new native thread(无法创建新的本地线程) |
解决:
解决方法:
减少堆内存,或者减少线程栈大小
直接内存溢出
– ByteBuffer.allocateDirect()直接分配方法,无法从操作系统获得足够的空间
– 直接内存的分配不会引起GC,只有堆内存和永久区内存的分配才可能会触发GC。直接内存是可以被JVM所回收的,但是却不能引起GC的触发,因此,我们需要编码手动的触发GC来回收这些直接内存。
– 堆,永久区,线程栈,直接内存,加起来不能超过操作系统能够分配给JVM的内存空间的。一般来说,在32位的计算机当中,不能超过2个G。总体不能超过计算机的物理内存。
代码:
for(int i=0;i<1024;i++){ ByteBuffer.allocateDirect(1024*1024); System.out.println(i); System.gc(); } |
参数设置:
-Xmx1g -XX:+PrintGCDetails (最大堆为1G,打印GC详细信息)
输出:
堆信息:
解决:
解决方法:
减少堆内存,或者有意触发GC。
直接内存需要GC回收,但是直接内存无法引起GC。直接内存使用满时,无法触发GC。
如果堆空间很富余,无法触发GC,直接内存可能就会溢出。如果堆空间触发GC,直接内存可以回收。
MAT(内存分析器)使用基础
简介和下载:
n Memory Analyzer(MAT)
MAT(Memory Analyzer Tool) 是基于heap dumps来进行分析的,它的分析速度比jhat快,分析结果是图形界面显示,比java内置jhat的可读性更高
n 基于Eclipse的软件
柱状图显示
柱状图显示,显示每个类的使用情况,比如类的数量,所占空间等
显示支配树
什么是支配树?
在对象引用图中,所有指向对象x的路径都经过对象y,则认为对象x支配对象y
如果对象x是离对象y最近的一个支配对象,则认为对象x为对象y的直接支配者
注意:支配者被回收,被支配对象也被回收。
显示线程信息
只能显示部分线程,一些系统线程无法显示
显示堆总体信息
显示堆总体信息,比如消耗最大的一些对象等
可以马上找到系统中什么类,占用空间最多
显示一个对象引用的对象,显示引用这个对象的对象
查看浅堆和深堆
什么是浅堆和深堆?
浅堆
– 一个对象结构所占用的内存大小(即本对象中成员变量占用内存的大小)
JDK7后,String结构发生变化,但是向8字节对齐后,浅堆还是24字节
– 3个int类型以及一个引用类型合计占用内存3*4+4=16个字节。再加上对象头的8个字节,因此String对象占用的空间,即浅堆的大小是16+8=24字节
– 对象大小按照8字节对齐,一定是8的倍数。
– 浅堆大小和对象的内容无关,只和对象的结构有关
深堆
– 一个对象被GC回收后,可以真实释放的内存大小
– 只能通过对象访问到的(直接或者间接)所有对象的浅堆之和 (支配树){即:该对象和所有它支配的对象的浅堆之和}
示例:
代码:
分析:
MAT查看浅堆和深堆:
可以看到,所有的Point实例浅堆大小都是16字节。而dLine对象,浅堆为16字节,深堆也是16字节,这是因为dLine对象内的两个点f和g没有被设置为null,因此,即使dLine被回收,f和g也不会被释放。对象cLine内的引用对象d和e由于仅在cLine内还存在引用,因此只要cLine被释放,d和e必然也作为垃圾被回收,即d和e在cLine的保留集内,因此cLine的深堆为16*2+16=48字节。
对于aLine和bLine对象,由于两者均持有对方的一个点,因此,当aLine被回收时,公共点a在bLine中依然有引用存在,故不会被回收,点a不在aLine对象的保留集中,因此aLine的深堆大小为16+16=32字节。对象bLine与aLine完全一致。
使用Visual VM分析堆
n VisualVM 是Netbeans的profile子项目,已在JDK6.0 update 7 中自带(java启动时不需要特定参数,监控工具在bin/jvisualvm.exe)。
n java自带的多功能分析工具,可以用来分析堆Dump
类的柱状图
n 类的柱状图 ,显示对象数量,总大小等
从类试图切换到实例试图,显示所有的实例
使用OQL查询
返回引用了(0,0)这个点的所有对象
Tomcat OOM分析案例
Tomcat OOM
Tomcat 在接收大量请求时发生OOM,获取堆Dump文件,进行分析。
使用MAT打开堆
分析目的:
n 找出OOM的原因
n 推测系统OOM时的状态
n 给出解决这个OOM的方法
分析步骤截图
找到消耗内存最大的对象
查看这个对象引用的对象
查看引用的对象中深堆最大的对象
推断出sessions 对象导致了内存溢出
用OQL查询,确认是否有大量session
9941/((1403324677648-1403324645728)/1000)= 320次/秒
结论:
结论,tomcat在32M内存下,承受每秒320次请求,持续31秒,合计9941次请求,导致OOM
解决方法:
n 解决方法:
– 1. OOM由于保存session过多引起,可以考虑增加堆大小
– 2. 如果应用允许,缩短session的过期时间,使得session可以及时过期,并回收