分析Java堆:内存溢出的原因

1、堆溢出

  一般来说,绝大部分Java的内存溢出都属于堆溢出。原因是因为大量对象占据了堆空间,这些对象都持有强引用导致无法回收,当对象大小之和大于Xmx参数指定的堆空间时就会发生堆溢出;

解决办法

  • 使用Xmx参数指定一个更大的堆空间;
  • 由于堆空间不可能无限增长,分析找到大量占用对空间的对象,在应用程序上做出优化;

2、直接内存溢出

在Java的NIO中,支持直接内存的使用,通过Java代码获得一块堆外的内存空间。直接内存的申请速度一般比堆内存慢,但是访问速度快于堆内存。

对于那些可复用的,并且会被经常访问的空间使用直接内存可以提高系统性能。直接内存没有被JVM完全托管若使用不当容易触发直接内存溢出导致宕机;

public class DirectBufferOOM {
    public static void main(String[] args) {
        for (int i = 0; i <1024 ; i++) {
            ByteBuffer.allocateDirect(1024*1024);
            System.out.println(i);
//            System.gc();
        }
    }
}

直接内存不一定能够触发GC(除非直接内存使用量达到了-XX:MaxDirectMemorySize),所以保证直接内存不溢出的方法是:

  • 合理进行显式GC;
  • 设定一个系统实际可达的-XX:MaxDirectMemorySize,默认情况下等于-Xmx的设置;

如果堆内存少有GC发生,但是直接内存申请频繁会容易导致直接内存溢出;

3、过多线程导致OOM

每一个现成的开启都要占用系统内存,当线程数量太多时,有可能导致OOM,由于线程的栈空间也是在堆外分配的,因此和直接内存非常相似。如果想让系统支持更多的线程应该使用一个较小的堆空间;

  • 减少堆空间,这样操作系统就可以预留更多内存用于线程创建;
  • 减少每一个线程占用的内存空间,使用-Xss参数指定线程的栈空间(栈溢出的风险相应上升);

4、永久区溢出

永久区是存放类元数据的区域,如果一个系统定义了太多的类型,会导致永久区溢出;

  • 增加MaxPermSize的值;
  • 减小系统需要的类的数量;
  • 使用ClassLoader合理地装载各个类,并定期进行回收;

5、GC效率低下导致的OOM

GC是内存回收的关键,如果GC效率低下,系统性能会受到严重影响;如果系统堆空间太小,GC所占用的事件会较多,回收释放的内存较少。

根据GC占有的系统时间、释放内存大小,虚拟机会评估GC效率,一旦认为效率过低就会直接抛出OOM异常;一般情况虚拟机会检查:

  • 花在GC上的时间是否超过了98%;
  • 老年代释放的内存是否小于2%;
  • eden释放的内存是否小于2%;
  • 是否连续最近5次都出现了上述几种情况;

你可能感兴趣的:(JVM)