Java编程常见内存溢出异常与代码示例
Java堆是用来存储对象实例的,因此如果我们不断地创建对象,并且保证GCRoot和创建的对象之间有可达路径以免对象被垃圾回收,那么当创建的对象过多时,会导致heap内存不足,进而引发OutOfMemoryError异常.
/** *@authorxiongyongshun *VMArgs:java-Xms10m-Xmx10m-XX:+HeapDumpOnOutOfMemoryError */ publicclassOutOfMemoryErrorTest{ publicstaticvoidmain(String[]args){ Listlist=newArrayList<>(); inti=0; while(true){ list.add(i++); } } }
上面是一个引发OutOfMemoryError异常的代码,我们可以看到,它就是通过不断地创建对象,并将对象保存在list中防止其被垃圾回收,因此当对象过多时,就会使堆内存溢出。
通过java-Xms10m-Xmx10m-XX:+HeapDumpOnOutOfMemoryError我们设置了堆内存为10兆,并且使用参数-XX:+HeapDumpOnOutOfMemoryError让JVM在发生OutOfMemoryError异常时打印出当前的内存快照以便于后续分析.
编译运行上述代码后,会有如下输出:
>>>java-Xms10m-Xmx10m-XX:+HeapDumpOnOutOfMemoryErrorcom.test.OutOfMemoryErrorTest16-10-0223:35 java.lang.OutOfMemoryError:Javaheapspace Dumpingheaptojava_pid1810.hprof... Heapdumpfilecreated[14212861bytesin0.125secs] Exceptioninthread"main"java.lang.OutOfMemoryError:Javaheapspace atjava.util.Arrays.copyOf(Arrays.java:3210) atjava.util.Arrays.copyOf(Arrays.java:3181) atjava.util.ArrayList.grow(ArrayList.java:261) atjava.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) atjava.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) atjava.util.ArrayList.add(ArrayList.java:458) atcom.test.OutOfMemoryErrorTest.main(OutOfMemoryErrorTest.java:15)
Java栈StackOverflowError
我们知道,JVM的运行时数据区中有一个叫做虚拟机栈的内存区域,此区域的作用是:每个方法在执行时都会创建一个栈帧,用于存储局部变量表,操作数栈,方法出口等信息.
因此我们可以创建一个无限递归的递归调用,当递归深度过大时,就会耗尽栈空间,进而导致了StackOverflowError异常.
下面是具体的代码:
/** *@authorxiongyongshun *VMArgs:java-Xss64k */ publicclassOutOfMemoryErrorTest{ publicstaticvoidmain(String[]args){ stackOutOfMemoryError(1); } publicstaticvoidstackOutOfMemoryError(intdepth){ depth++; stackOutOfMemoryError(depth); } }
当编译运行上述的代码后,会输出如下异常信息:
Exceptioninthread"main"java.lang.StackOverflowError atcom.test.OutOfMemoryErrorTest.stackOutOfMemoryError(OutOfMemoryErrorTest.java:27)
方法区内存溢出
注意,因为JDK8已经移除了永久代,取而代之的是metaspace,因此在JDK8中,下面两个例子都不会导致java.lang.OutOfMemoryError:PermGenspace异常.
运行时常量池溢出
在Java1.6以及之前的HotSpotJVM版本时,有永久代的概念,即GC的分代收集机制是扩展至方法区的.在方法区中,有一部分内存是用于存储常量池,因此如果代码中常量过多时,就会耗尽常量池内存,进而导致内存溢出.那么如何添加大量的常量到常量池呢?这时就需要依靠String.intern()方法了.String.intern()方法的作用是:若此String的值在常量池中已存在,则这个方法返回常量池中对应字符串的引用;反之将此String所包含的值添加到常量池中,并返回此String对象的引用.在JDK1.6以及之前的版本中,常量池分配在永久代中,因此我们可以通过设置参数“-XX:PermSize”和“-XX:MaxPermSize”来间接限制常量池的大小.
注意,上面所说的String.intern()方法和常量池的内存分布仅仅针对于JDK1.6及之前的版本,在JDK1.7或以上的版本中,由于去除了永久代的概念,因此内存布局稍有不同.
下面是实现常量池内存溢出的代码例子:
/** *@authorxiongyongshun *VMArgs:-XX:PermSize=10M-XX:MaxPermSize=10M */ publicclassRuntimeConstantPoolOOMTest{ publicstaticvoidmain(String[]args){ Listlist=newArrayList (); inti=0; while(true){ list.add(String.valueOf(i++).intern()); } } }
我们看到,这个例子中,正是使用了String.intern()方法,向常量池中添加了大量的字符串常量,因而导致了常量池的内存溢出.
我们通过JDK1.6编译并运行上面的代码,会有如下输出:
Exceptioninthread"main"java.lang.OutOfMemoryError:PermGenspace atjava.lang.String.intern(NativeMethod) atcom.test.RuntimeConstantPoolOOMTest.main(RuntimeConstantPoolOOMTest.java:16)
需要注意的是,如果通过JDK1.8来编译运行上面代码的话,会有如下警告,并且不会产生任何的异常:
>>>java-XX:PermSize=10M-XX:MaxPermSize=10Mcom.test.RuntimeConstantPoolOOMTest16-10-030:23 JavaHotSpot(TM)64-BitServerVMwarning:ignoringoptionPermSize=10M;supportwasremovedin8.0 JavaHotSpot(TM)64-BitServerVMwarning:ignoringoptionMaxPermSize=10M;supportwasremovedin8.0
方法区的内存溢出
方法区作用是存放Class的相关信息,例如类名,类访问修饰符,字段描述,方法描述等.因此如果方法区过小,而加载的类过多,就会造成方法区的内存溢出.
//VMArgs:-XX:PermSize=10M-XX:MaxPermSize=10M publicclassMethodAreaOOMTest{ publicstaticvoidmain(String[]args){ while(true){ Enhancerenhancer=newEnhancer(); enhancer.setSuperclass(MethodAreaOOMTest.class); enhancer.setUseCache(false); enhancer.setCallback(newMethodInterceptor(){ publicObjectintercept(Objecto,Methodmethod,Object[]objects,MethodProxymethodProxy)throwsThrowable{ returnmethodProxy.invokeSuper(o,objects); } }); enhancer.create(); } } }
上面的代码中,我们借助CGlib来动态地生成大量的类,在JDK6下,运行上面的代码会产生OutOfMemoryError:PermGenspace异常:
/System/Library/Frameworks/JavaVM.framework/Versions/1.6/Home/bin/java-jar-XX:PermSize=10M-XX:MaxPermSize=10Mtarget/Test-1.0-SNAPSHOT.jar
输出结果如下:
Causedby:java.lang.OutOfMemoryError:PermGenspace atjava.lang.ClassLoader.defineClass1(NativeMethod) atjava.lang.ClassLoader.defineClassCond(ClassLoader.java:637) atjava.lang.ClassLoader.defineClass(ClassLoader.java:621) ...11more
MetaSpace内存溢出
在方法区的内存溢出内存溢出一节中,我们提到,JDK8没有了永久代的概念,因此那两个例子在JDK8下没有实现预期的效果.那么在JDK8下,是否有类似方法区内存溢出之类的错误呢?当然有的.在JDK8中,使用了MetaSpace的区域来存放Class的相关信息,因此当MetaSpace内存空间不足时,会抛出java.lang.OutOfMemoryError:Metaspace异常.
我们还是以上面提到的例子为例:
//VMArgs:-XX:MaxMetaspaceSize=10M publicclassMethodAreaOOMTest{ publicstaticvoidmain(String[]args){ while(true){ Enhancerenhancer=newEnhancer(); enhancer.setSuperclass(MethodAreaOOMTest.class); enhancer.setUseCache(false); enhancer.setCallback(newMethodInterceptor(){ publicObjectintercept(Objecto,Methodmethod,Object[]objects,MethodProxymethodProxy)throwsThrowable{ returnmethodProxy.invokeSuper(o,objects); } }); enhancer.create(); } } }
此例子的代码部分没有改动,唯一的区别是我们需要使用JDK8来运行这段代码,并且设着参数-XX:MaxMetaspaceSize=10M,这个参数告诉JVMMetaspace的最大大小是10M.
接着我们使用JDK8来编译运行这个例子,输出如下异常:
>>>java-jar-XX:MaxMetaspaceSize=10Mtarget/Test-1.0-SNAPSHOT.jar Exceptioninthread"main"java.lang.OutOfMemoryError:Metaspace atnet.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:345) atnet.sf.cglib.proxy.Enhancer.generate(Enhancer.java:492) atnet.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:114) atnet.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:291) atnet.sf.cglib.proxy.Enhancer.createHelper(Enhancer.java:480) atnet.sf.cglib.proxy.Enhancer.create(Enhancer.java:305) atcom.test.MethodAreaOOMTest.main(MethodAreaOOMTest.java:22)
总结
以上就是本文关于Java编程常见内存溢出异常与代码示例的全部内容,希望对大家有所帮助。感兴趣的朋友可以继续参阅本站其他相关专题,如有不足之处,欢迎留言指出。感谢朋友们对本站的支持!