内存溢出(OOM)的原因

在JVM中,有哪些内存区间?

堆,永久区,线程栈,直接内存

堆溢出

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)

解决方法:增大堆空间,及时释放内存

永久区

生成大量的类
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

QQ截图20170507233515.png

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

Exception in thread "main" java.lang.OutOfMemoryError: 
unable to create new native thread

解决方法:减少堆内存 减少线程栈大小

直接内存溢出

ByteBuffer.allocateDirect()无法从操作系统获得足够的空间

QQ截图20170507234537.png

for(int i=0;i<1024;i++){
    ByteBuffer.allocateDirect(1024*1024);
    System.out.println(i);
      System.gc();
}

-Xmx1g -XX:+PrintGCDetails

QQ截图20170507234654.png

解决方法:减少堆内存 有意触发GC

遇到内存溢出后,应该如何思考和处理问题?

MAT使用基础

  1. Memory Analyzer(MAT)

  2. 基于Eclipse的软件

  3. http://www.eclipse.org/mat/

图片1.png

柱状图显示,显示每个类的使用情况,比如类的数量,所占空间等

图片2.png

显示支配树

QQ截图20170507235008.png

在对象引用图中,所有指向对象B的路径都经过对象A,则认为对象A支配对象B.如果对象A是离对象B最近的一个支配对象,则认为对象A为对象B的直接支配者

支配者被回收被支配者也会被回收,可以了解一个对象被释放后可以回收多少空间

图片3.png

显示线程信息

图片4.png

显示堆总体信息,比如消耗最大的一些对象等

图片5.png

显示一个对象引用的对象

显示引用这个对象的对象

图片6.png

浅堆 深堆

浅堆

  • 一个对象结构所占用的内存大小

    图片7.png

    图片8.png

JDK7后,String结构发生变化,但是向8字节对齐后,浅堆还是24字节

  • 3个int类型以及一个引用类型合计占用内存3*4+4=16个字节。再加上对象头的8个字节,因此String对象占用的空间,即浅堆的大小是16+8=24字节

  • 对象大小按照8字节对齐

  • 浅堆大小和对象的内容无关,只和对象的结构有关

深堆

  • 一个对象被GC回收后,可以真实释放的内存大小

  • 只能通过对象访问到的(直接或者间接)所有对象的浅堆之和 (支配树)

图片9.png

图片10.png

图片11.png

图片12.png

图片13.png

可以看到,所有的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字节。

使用Visual VM分析堆

java自带的多功能分析工具,可以用来分析堆Dump

图片14.png

类的柱状图 ,显示对象数量,总大小等

图片15.png

从类视图切换到实例试图,显示所有的实例

图片17.png

使用OQL查询

图片18.png

返回引用了(0,0)这个点的所有对象

Tomcat OOM分析案例

Tomcat OOM

  • Tomcat 在接收大量请求时发生OOM,获取堆Dump文件,进行分析。

使用MAT打开堆

分析目的:

  • 找出OOM的原因

  • 推测系统OOM时的状态

  • 给出解决这个OOM的方法

图片19.png

图片20.png

图片21.png

图片22.png

图片23.png

确认是否有大量session

图片24.png

图片25.png

图片26.png

9941/((1403324677648-1403324645728)/1000)= 320次/秒

图片27.png

结论,tomcat在32M内存下,承受每秒320次请求,持续31秒,合计9941次请求,导致OOM

解决方法:

  1. OOM由于保存session过多引起,可以考虑增加堆大小

  2. 如果应用允许,缩短session的过期时间,使得session可以及时过期,并回收