oom java_[JVM]OOM实例分析

本文内容总结自《深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)》中的第2章。

通过设置虚拟机的启动参数来模拟内存区域溢出的情况

通过异常信息判断出是哪个内存溢出了,知道什么样的代码可能会导致内存溢出

8eabaf631d15

Java内存区域

设置虚拟机的启动参数

在使用代码进行验证之前,我们需要先了解如何设置虚拟机的启动参数,设置方法有两种。

使用命令行执行程序时

使用命令行运行程序时,直接在java后面跟上参数和程序名称

D:\>javac HeapOOM.java

D:\>java HeapOOM

D:\>java -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError HeapOOM

java.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid9512.hprof ...

Heap dump file created [27898328 bytes in 0.121 secs]

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at java.util.Arrays.copyOf(Arrays.java:2245)

at java.util.Arrays.copyOf(Arrays.java:2219)

at java.util.ArrayList.grow(ArrayList.java:242)

at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:216)

at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:208)

at java.util.ArrayList.add(ArrayList.java:440)

at HeapOOM.main(HeapOOM.java:16)

使用IDE执行程序时

直接在VM Arguments中设置

8eabaf631d15

使用IDE执行程序

Java堆溢出

VM arguments

设置Java堆的大小20MB,将堆的最小值-Xms参数和最大值-Xmx参数设置为一样,避免堆自动扩展。通过设置参数-XX:+HeapDumpOnOutOfMemoryError,在发生内存异常时可以生成堆转存储的dump文件,通过Eclipse Memory Analyzer (MAT)工具可以分析该文件。

VM Args:

-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

Option

Description

-Xms20M -Xmx20M

to specify that a maximum heap size of 20 Mbytes

-XX:+HeapDumpOnOutOfMemoryError

Dump heap to file when java.lang.OutOfMemoryError is thrown

Code

根据Java内存区域可知,通过new指令创建的对象是放到Java堆中的。下面这段代码的功能就是一直在创建OOMObject对象,当OOMObject对象占用大小超出设置的20M时,就会报出OutOfMemoryError异常,并提示发生异常的区域为"Java heap space"

import java.util.ArrayList;

import java.util.List;

/**

* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError

*/

public class HeapOOM {

static class OOMObject {

}

public static void main(String[] agrs) {

List list = new ArrayList();

while (true) {

list.add(new OOMObject());

}

}

}

Output

发生异常后生产的dump文件的默认名称为"java_pid进程id.hprof",默认生成路径为工程的根目录

java.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid9512.hprof ...

Heap dump file created [15782234 bytes in 0.074 secs]

Exception in thread "main" java.lang.OutOfMemoryError: Java heap space

at java.util.Arrays.copyOf(Arrays.java:2760)

at java.util.Arrays.copyOf(Arrays.java:2734)

at java.util.ArrayList.ensureCapacity(ArrayList.java:167)

at java.util.ArrayList.add(ArrayList.java:351)

at HeapOOM.main(HeapOOM.java:16)

使用MAT打开 java_pid9512.hprof,可以生成一个分析报告

8eabaf631d15

使用MAT打开 java_pid9512.hprof

点击进入Dominator Tree界面,可以将对象占用Heap大小进行排序,由下图可以看出main thread里面生成了很多HeapOOM$OOMObject对象,导致暂用了97.49%的内存。

8eabaf631d15

Dominator Tree

虚拟机栈溢出

VM arguments

使用-Xss参数减少栈内存的容量

VM Args:

-Xss128k

Code

stackLeak()方法不断地自己调用自己,导致栈帧的深度一直在增加,当超出栈内存容量时,将会抛出java.lang.StackOverflowError异常

/**

* VM Args:-Xss128k

*/

public class JavaVMStackSOF {

private int stackLength = 1;

public void stackLeak() {

stackLength++;

stackLeak();

}

/**

* @param args

* @throws Throwable

*/

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

// TODO Auto-generated method stub

JavaVMStackSOF oom = new JavaVMStackSOF();

try {

oom.stackLeak();

} catch (Throwable e) {

// TODO: handle exception

System.out.println("stack length : " + oom.stackLength);

throw e;

}

}

}

Output

stack length : 11432

Exception in thread "main" java.lang.StackOverflowError

at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:8)

at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)

at JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:9)

... ...

方法区和运行时常量池异常

运行时常量池是方法区的一部分,而方法区属于"永久代"(PermGen space)

VM arguments

通过-XX:PermSize和 -XX:MaxPermSize限制永久代的大小

VM Args:

-XX:PermSize=10M -XX:MaxPermSize=10M

Code

String.intern()的作用:如果字符串常量池中已经包含一个等于此String对象的字符串,则返回常量池中这个字符串的String对象;否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。

下面这段代码一直在往常量池中添加字符串,直到超出常量池的容量。

import java.util.ArrayList;

import java.util.List;

/**

* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M

*/

public class RuntimeConstantPoolOOM {

/**

* @param args

*/

public static void main(String[] args) {

// TODO Auto-generated method stub

List list = new ArrayList();

int i = 0;

while (true) {

list.add(String.valueOf(i++).intern());

}

}

}

Output

JDK 1.6及之前的版本中,常量池分配在永久代内,可以通过-XX:PermSize和 -XX:MaxPermSize限制永久代的大小,当常量池超出容量时,会抛出异常"Exception in thread "main" java.lang.OutOfMemoryError: PermGen space"

由于JDK 1.7的HotSpot中,已经把原本放在永久代中的字符串常量池移除,因此使用JDK 1.7运行这段代码时不会抛出异常,while循环将一直进行下去

8eabaf631d15

JDK 1.6和JDK 1.7运行结果不同

本机直接内存溢出

VM arguments

DirectMemory容量可以通过MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值一样(-Xmx指定)

VM Args:

-Xmx20M -XX:MaxDirectMemorySize=10M

Code

通过Unsafe对象的allocateMemory方法申请直接内存,当超出DirectMemory容量时,抛出异常java.lang.OutOfMemoryError

import java.lang.reflect.Field;

import sun.misc.Unsafe;

/*

* VM Args: -Xmx20M -XX:MaxDirectMemorySize=10M

*/

public class DirectMemoryOOM {

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) throws IllegalArgumentException,

IllegalAccessException {

Field unsafeField = Unsafe.class.getDeclaredFields()[0];

unsafeField.setAccessible(true);

Unsafe unsafe = (Unsafe) unsafeField.get(null);

while (true) {

unsafe.allocateMemory(_1MB);

}

}

}

Output

Exception in thread "main" java.lang.OutOfMemoryError

at sun.misc.Unsafe.allocateMemory(Native Method)

at DirectMemoryOOM.main(DirectMemoryOOM.java:16)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值