机构档案
  • 机构级别:普通会员
  • 信用等级:

在线交谈:点击这里给我发消息

咨询热线:029-62258374

学校评价(我要提问/点评)

  • 学校被点评:0
  • 好评(0%)
  • 中评(0%)
  • 差评(0%)

资料认证

    未通过身份证认证 未通过身份证认证

    未通过办学许可认证 未通过办学许可认证

  • 学校浏览人次:
  • 加盟时间:2017年03月10日
新闻动态

西安尚学堂java:Java常见内存溢出异常与代码实现

发布者:西安尚学堂 发布时间:2017-04-17 来源:西安尚学堂

Java堆是用来存储对象实例的,因此如果我们不断地创建对象,并且保证GC Root和创建的对象之间有可达路径以免对象被垃圾回收,那么当创建的对象过多时,会导致heap内存不足,进而引发OutOfMemoryError异常.本文西安尚学堂java培训专家为大家整理了Java常见内存溢出异常与代码实现:

/**

* @author xiongyongshun

* VM Args: java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError

*/

public class OutOfMemoryErrorTest {

public static void main(String[] args) {

List list = new ArrayList<>();

int i = 0;

while (true) {

list.add(i++);

}

}

}

上面是一个引发OutOfMemoryError异常的代码,我们可以看到,它就是通过不断地创建对象,并将对象保存在list中防止其被垃圾回收,因此当对象过多时,就会使堆内存溢出.

通过java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError我们设置了堆内存为10兆,并且使用参数-XX:+HeapDumpOnOutOfMemoryError让JVM在发生OutOfMemoryError异常时打印出当前的内存快照以便于后续分析.

编译运行上述代码后,会有如下输出:

>>> java -Xms10m -Xmx10m -XX:+HeapDumpOnOutOfMemoryError com.test.OutOfMemoryErrorTest16-10-02 23:35

java.lang.OutOfMemoryError: Java heap space

Dumping heap to java_pid1810.hprof ...

Heap dump file created [14212861 bytes in 0.125 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.test.OutOfMemoryErrorTest.main(OutOfMemoryErrorTest.java:15)

Java栈StackOverflowError

我们知道, JVM的运行时数据区中有一个叫做虚拟机栈的内存区域,此区域的作用是:每个方法在执行时都会创建一个栈帧,用于存储局部变量表,操作数栈,方法出口等信息.

因此我们可以创建一个无限递归的递归调用,当递归深度过大时,就会耗尽栈空间,进而导致了StackOverflowError异常.

下面是具体的代码:

/**

* @author xiongyongshun

* VM Args: java -Xss64k

*/

public class OutOfMemoryErrorTest {

public static void main(String[] args) {

stackOutOfMemoryError(1);

}public static void stackOutOfMemoryError(int depth) {

depth++;

stackOutOfMemoryError(depth);

}

}

当编译运行上述的代码后,会输出如下异常信息:

Exception in thread "main" java.lang.StackOverflowError

at com.test.OutOfMemoryErrorTest.stackOutOfMemoryError(OutOfMemoryErrorTest.java:27)

方法区内存溢出

注意,因为JDK8已经移除了永久代,取而代之的是metaspace,因此在JDK8中,下面两个例子都不会导致java.lang.OutOfMemoryError: PermGen space异常.

运行时常量池溢出

在Java 1.6以及之前的HotSpot JVM版本时,有永久代的概念,即GC的分代收集机制是扩展至方法区的.在方法区中,有一部分内存是用于存储常量池,因此如果代码中常量过多时,就会耗尽常量池内存,进而导致内存溢出.

那么如何添加大量的常量到常量池呢?这时就需要依靠String.intern()方法了. String.intern()方法的作用是:若此String的值在常量池中已存在,则这个方法返回常量池中对应字符串的引用;反之将此String所包含的值添加到常量池中,并返回此String对象的引用.在JDK 1.6以及之前的版本中,常量池分配在永久代中,因此我们可以通过设置参数"-XX:PermSize"和"-XX:MaxPermSize"来间接限制常量池的大小.

注意,上面所说的String.intern()方法和常量池的内存分布仅仅针对于JDK 1.6及之前的版本,在JDK 1.7或以上的版本中,由于去除了永久代的概念,因此内存布局稍有不同.

下面是实现常量池内存溢出的代码例子:

/**

* @author xiongyongshun

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

*/

public class RuntimeConstantPoolOOMTest {

public static void main(String[] args) {

List list = new ArrayList();

int i = 0;

while (true) {

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

}

}

}

我们看到,这个例子中,正是使用了String.intern()方法,向常量池中添加了大量的字符串常量,因而导致了常量池的内存溢出.

我们通过JDK1.6编译并运行上面的代码,会有如下输出:

Exception in thread "main" java.lang.OutOfMemoryError: PermGen space

at java.lang.String.intern(Native Method)

at com.test.RuntimeConstantPoolOOMTest.main(RuntimeConstantPoolOOMTest.java:16)

需要注意的是,如果通过JDK1.8来编译运行上面代码的话,会有如下警告,并且不会产生任何的异常:

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option PermSize=10M; support was removed in 8.0

Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=10M; support was removed in 8.0

方法区的内存溢出

方法区作用是存放Class的相关信息,例如类名,类访问修饰符,字段描述,方法描述等.因此如果方法区过小,而加载的类过多,就会造成方法区的内存溢出.

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

public class MethodAreaOOMTest {

public static void main(String[] args) {

while (true) {

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(MethodAreaOOMTest.class);

enhancer.setUseCache(false);

enhancer.setCallback(new MethodInterceptor() {

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

return methodProxy.invokeSuper(o, objects);

}

});enhancer.create();

}

}

}

上面的代码中,我们借助CGlib来动态地生成大量的类,在JDK6下,运行上面的代码会产生OutOfMemoryError: PermGen space异常:

/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home/bin/java -jar -XX:PermSize=10M -XX:MaxPermSize=10M target/Test-1.0-SNAPSHOT.jar

输出结果如下:

Caused by: java.lang.OutOfMemoryError: PermGen space

at java.lang.ClassLoader.defineClass1(Native Method)

at java.lang.ClassLoader.defineClassCond(ClassLoader.java:637)

at java.lang.ClassLoader.defineClass(ClassLoader.java:621)

... 11 more

MetaSpace内存溢出

在方法区的内存溢出内存溢出一节中,我们提到, JDK8没有了永久代的概念,因此那两个例子在JDK8下没有实现预期的效果.那么在JDK8下,是否有类似方法区内存溢出之类的错误呢?当然有的.在JDK8中,使用了MetaSpace的区域来存放Class的相关信息,因此当MetaSpace内存空间不足时,会抛出java.lang.OutOfMemoryError: Metaspace异常.

我们还是以上面提到的例子为例:

//VM Args: -XX:MaxMetaspaceSize=10M

public class MethodAreaOOMTest {

public static void main(String[] args) {

while (true) {

Enhancer enhancer = new Enhancer();

enhancer.setSuperclass(MethodAreaOOMTest.class);

enhancer.setUseCache(false);

enhancer.setCallback(new MethodInterceptor() {

public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {

return methodProxy.invokeSuper(o, objects);

}

});enhancer.create();

}

}

}

此例子的代码部分没有改动,唯一的区别是我们需要使用JDK来运行这段代码,并且设着参数-XX:MaxMetaspaceSize=10M,这个参数告诉JVM Metaspace的最大大小是10M.

接着我们使用JDK8来编译运行这个例子,输出如下异常:

>>> java -jar -XX:MaxMetaspaceSize=10M target/Test-1.0-SNAPSHOT.jar

Exception in thread "main" java.lang.OutOfMemoryError: Metaspace

at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345)

at net.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492)

at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114)

at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291)

at net.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480)

at net.sf.cglib.proxy.Enhancer.create(Enhancer.java:305)

at com.test.MethodAreaOOMTest.main(MethodAreaOOMTest.java:22)