Java 内存管理机制:02 OOM异常

OOM 异常 (OutOfMemoryError)

Java 堆溢出

出现标志:java.lang.OutOfMemoryError: Java heap space

解决方法:

  • 先通过内存对象分析工具分析Dump出来的堆转存储快照,确认内存中的对象是否是必要的,级分清楚是出现了内存泄漏还是内存溢出;
  • 如果是内存泄漏,通过攻击查看泄漏对象到GC Root的引用链,定位出泄漏的位置;
  • 如果不存在泄漏,检查虚拟机对参数(-Xmx和-Xms)是否可以调大,检查代码中是否有哪些对象的生命周期过长,尝试减少程序运行期的内存消耗。

 虚拟机参数:-XX:HeapDumpOnOutOfMemoryError:让虚拟机在出现内存泄漏异常时 Dump 出当前的内存堆转储快照用于事后分析。

例如:

Java堆溢出,Java堆用于存储对象实例,只要不断的创建对象,并且保证GC Root到对象之间有可达路径来避免垃圾回收,那么对象数量到达最大堆的容量限制后就会产生内存溢出异常。

package com.mall.jvm;

import java.util.ArrayList;
import java.util.List;

/**
 * -Xms20m -Xmx20m  -XX:+HeapDumpOnOutOfMemoryError
 */
public class HeapOOM {

    static class OOMObject{

    }
    public static void main(String[] args) {
        List<OOMObject> list = new ArrayList<>();
        while (true){
            list.add(new OOMObject());
        }
    }
}

  运行结果

java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid18004.hprof ...
Heap dump file created [28624826 bytes in 0.092 secs]
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
	at java.util.Arrays.copyOf(Arrays.java:3210)
	at java.util.Arrays.copyOf(Arrays.java:3181)
	at java.util.ArrayList.grow(ArrayList.java:261)
	at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235)
	at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227)
	at java.util.ArrayList.add(ArrayList.java:458)
	at com.mall.jvm.HeapOOM.main(HeapOOM.java:18)

Java 虚拟机栈和本地方法栈溢出

  • 单线程下,栈帧过大,虚拟机容量过小都不会导致OutOfMemoryError,只会导致StackOverflowError(栈会比内存先爆掉),一般多线程才会出现OutOfMemoryError,因为线程本身要占用内存
  • 如果是多线程导致的OutOfMemoryError,在不能减少线程数或更换64位虚拟机的情况,只能通过减少最大堆和减少栈容量来换取更多的线程;(这个调节思路和 Java 堆出现 OOM 正好相反,Java 堆出现 OOM 要调大堆内存的设置值,而栈出现 OOM 反而要调小)

例如:栈(StackOverflowError)

如果线程请求的栈深度大于虚拟机栈所允许的最大深度,将会抛出StackOverflowError异常

package com.mall.jvm;

/**
 * -Xss128k
 */
public class JavaVMStackSOF {

    private int stackLength = 1;

    public void stackLeak(){
        stackLength++;
        stackLeak();
    }

    public static void main(String[] args) {
        JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF();
        try {
            javaVMStackSOF.stackLeak();
        }catch (Exception e){
            System.out.println("stack length :" + javaVMStackSOF.stackLength);
            throw e;

        }
    }
}

  运行结果:

Exception in thread "main" java.lang.StackOverflowError
	at com.mall.jvm.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:11)

  栈(OutOfMemoryError)

如果虚拟机在拓展栈时无法申请到足够的空间则抛出OutOfMemoryError异常

package com.mall.jvm;

/**
 * -Xss2M
 */
public class JavaVMStackOOM {

    private  void dontStop(){
        while(true){
        }
    }

    public void stackLeakByThread(){
        while (true){
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    dontStop();
                }
            });
            thread.start();
        }
    }

    public static void main(String[] args) {
        JavaVMStackOOM javaVMStackOOM = new JavaVMStackOOM();
        javaVMStackOOM.stackLeakByThread();
    }
}

 方法区和运行时常量池溢出

  • 测试思路:产生大量的类去填满方法区,直到溢出;
  • 在经常动态生成大量 Class 的应用中,如 Spring 框架(使用 CGLib 字节码技术),方法区溢出是一种常见的内存溢出,要特别注意类的回收状况。

直接内存溢出

  • 出现特征:Heap Dump 文件中看不见明显异常,程序中直接或间接用了 NIO;
  • 虚拟机参数:-XX:MaxDirectMemorySize,如果不指定,则和 -Xmx 一样。
package com.ecut.exception;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
 *  -Xmx20M -XX:MaxDirectMemorySize = 10M
 */
public class DirectMemoryOOM {
    private  static final int _1MB = 1024*1024;

    public static void main(String[] args) throws IllegalAccessException {
        Field unsafeField = Unsafe.class.getDeclaredFields()[0];
        unsafeField.setAccessible(true);
        Unsafe unsafe = (Unsafe) unsafeField.get(null);
        while (true){
            unsafe.allocateMemory(_1MB);
        }
    }
}

  运行结果如下:

Exception in thread "main" java.lang.OutOfMemoryError
    at sun.misc.Unsafe.allocateMemory(Native Method)
    at com.ecut.exception.DirectMemoryOOM.main(DirectMemoryOOM.java:18)
原文地址:https://www.cnblogs.com/mengY/p/12249295.html