JVM 堆内存溢出异常 - OutOfMemoryException

  1. public class OutOfMemoryException {
  2. public static void main(String[] args) {
  3. List<OutOfMemoryException> bufList = new ArrayList<>();
  4. for (; ; ) {
  5. bufList.add(new OutOfMemoryException());
  6. }
  7. }
  8. }

需要注意的是,由于现在计算机配置都非常高,因此直接让计算机内存溢出是一件非常困难的操作,因此这里我们将虚拟机的配置更改下,需要在启动参数中添加 `-Xms1m -Xmx1m -XX:+HeapDumpOnOutOfMemoryError` 标识初始化内存1M,最大拓展内存1M,并且dump 异常信息当出现OOM的时候

执行代码,等待一段时间之后,控制台出现以下错误信息并且项目的根目录出现后缀名为: hprof 如:java_pid92363.hprof 的文件

  1. java.lang.OutOfMemoryError: Java heap space
  2. Dumping heap to java_pid92363.hprof ...
  3. Heap dump file created [2571260 bytes in 0.011 secs]
  4. Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
  5. at java.util.Arrays.copyOf(Arrays.java:3210)
  6. at java.util.Arrays.copyOf(Arrays.java:3181)
  7. at java.util.ArrayList.grow(ArrayList.java:265)
  8. at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
  9. at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
  10. at java.util.ArrayList.add(ArrayList.java:462)
  11. at com.zhoutao.memory.OutOfMemoryException.main(OutOfMemoryException.java:17)
  12. Process finished with exit code 1

我们可以在控制台输入命令 jvisualvm 启动 JVisualVM 工具 ,在文件中载入 java_pid92363.hprof 文件,在文件选项卡中载入此hprof文件即可
image.png

可以看到报错信息中显示在main线程中出现OOM,标识OOM的异常信息是在main线程出现的,在类选项卡中,我们可以看到,OutOfMemoryException 类的实例达到4万之多,导致OOM
image.png

栈内存溢出异常 StackOverflowError

/**
 * 使用递归的方式测试 JVM 的栈内存溢出
 *
 * <p>设置栈内存大小: -Xss100k
 */
public class StackOverflowException {

  public static void main(String[] args) {
    StackOverExample example = new StackOverExample();
    example.test();
  }
}

class StackOverExample {

  private int length;

  void test() {
    this.length++;
    try {
      test();
    } catch (Throwable throwable) {
      System.out.println("栈深度:" + this.length);
      throwable.printStackTrace();
    }
  }
}

输出数据:

栈深度:9925
java.lang.StackOverflowError
    at sun.misc.Unsafe.compareAndSwapLong(Native Method)
    at java.util.concurrent.ConcurrentHashMap.addCount(ConcurrentHashMap.java:2259)
    at java.util.concurrent.ConcurrentHashMap.putVal(ConcurrentHashMap.java:1070)
    at java.util.concurrent.ConcurrentHashMap.putIfAbsent(ConcurrentHashMap.java:1535)
    at java.lang.ClassLoader.getClassLoadingLock(ClassLoader.java:463)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:404)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:411)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:349)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
    at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
    at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:26)

可以看到栈深度达到9925 的时候出现了栈内存溢出异常的错误,使用JVisuaVM 获取线程Dump的数据可以看到一下信息, 当前线程状态为 TIMED_WAITING ,处于等待休眠状态。

"main" #1 prio=5 os_prio=31 tid=0x00007fcce4801800 nid=0x1103 sleeping[0x000070000d0c9000]
   java.lang.Thread.State: TIMED_WAITING (sleeping)
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:27)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)
        at com.zhoutao.memory.StackOverExample.test(StackOverflowException.java:28)

死锁异常信息

程序分析:

  • 线程A 已经获取到对象 lockObject1 的锁,然后延时2秒
  • 线程2 已经获取到对象 lockObject2的锁,然后延时1秒
  • 由于延时的时间差异,线程B首先执行完成,这时候B尝试获取 lockObject1的锁,但是A线程持有lockObject1的锁,所以B线程在等待获取lockObject1的锁
  • 在B线程等待的期间,A线程执行完成,尝试获取对象lockObject2的锁,此时就会出现死锁问题
public class DeadLockException {

  public static void main(String[] args) throws Exception {

    final Object lockObject1 = new String();
    final Object lockObject2 = new StringBuffer();

    new Thread(
            () -> {
              System.out.println("DeadLockException.run");
              synchronized (lockObject1) {
                System.out.println("1 get 1's lock");
                try {
                  TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                  e.printStackTrace();
                }

                synchronized (lockObject2) {
                  System.out.println("1 get 2's lock");
                }
              }
            })
        .start();

    new Thread(
            () -> {
              System.out.println("DeadLockException.run");
              synchronized (lockObject2) {
                System.out.println("2 get 2's lock");
                try {
                  TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                  e.printStackTrace();
                }
                synchronized (lockObject1) {
                  System.out.println("2 get 1's lock");
                }
              }
            })
        .start();
  }
}

程序执行结果:

DeadLockException.run
1 get 1's lock
DeadLockException.run
2 get 2's lock

使用JvisualVM 工具分析可见到 图4.3.1 ,JvisualVM 已经发现了死锁,这里dump线程,可以再次看到:

image.png
图 4.3.1

image.png

图 4.3.2 线程Dump 分析

image.png

图 4.3.3 使用JProfile 查看是否存在死锁

Metaspace 元空间内存溢出

从Java1.8开始,永久代的概念被移除,取而代之的是在本地内存中的元空间(Metaspace) ,默认大小20.75M,可以通过 -XX:MaxMatespaceSize=1000m,来修改元空间大小,可以通过使用Cglib 不同的创建类来实现元空间的方法溢出。通过需要修改VM 参数 -XX:MaxMetaspaceSize=2M

public class JavaMethodAreaOOM {

    static class OOMObject{
    }

    public static void main(String[] args) {
        while (true) {
            Enhancer enhancer = new Enhancer();
            enhancer.setSuperclass(OOMObject.class);
            enhancer.setUseCache(false);
            enhancer.setCallback(new MethodInterceptor() {
                @Override
                public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                    return proxy.invoke(obj, args);
                }
            });
            enhancer.create();
        }
    }
}

输出结果: Exception in thread “main” java.lang.OutOfMemoryError: Metaspace at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:237) at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:377) at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:285)

at com.seeyon.oom.JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:42)

其他监控工具

Jconsole

image.png

Jprofile(商业软件)

image.png