1.堆中的OutOfMemory异常
设置最大堆和Xmx最小堆Xms参数,通过不断的创建对象,当没有足够的空间创建新的对象时产生内存溢出异常
-verbose:gc
-Xms20M
-Xmx20M
-Xmn10M
-XX:+PrintGCDetails
-XX:SurvivorRatio=8
Xms:最小堆
Xmx:最大堆
Xmn:新生代的大小
PrintGCDetails:打印GC日志
SurvivorRatio:Eden区和Survivor比例
import java.util.ArrayList; import java.util.List; /** * 演示OutOfMemoryError * 需要指定VM参数 * -verbose:gc * -Xms20M * -Xmx20M * -Xmn10M * -XX:+PrintGCDetails * -XX:SurvivorRatio=8 */ public class HeapOOM { //静态内部类 static class OOMObject{ } public static void main(String[] args) { List<OOMObject> list=new ArrayList<OOMObject>(); //通过不断的创建对象达到OOM while (true){ list.add(new OOMObject()); } } }
输出,从出错信息java heap space中也可以看出异常是在堆中发生的:
[GC (Allocation Failure) [PSYoungGen: 8192K->1008K(9216K)] 8192K->4857K(19456K), 0.0058096 secs] [Times: user=0.02 sys=0.00, real=0.00 secs] [GC (Allocation Failure) --[PSYoungGen: 9200K->9200K(9216K)] 13049K->19432K(19456K), 0.0165642 secs] [Times: user=0.07 sys=0.01, real=0.02 secs] [Full GC (Ergonomics) [PSYoungGen: 9200K->0K(9216K)] [ParOldGen: 10232K->10044K(10240K)] 19432K->10044K(19456K), [Metaspace: 3299K->3299K(1056768K)], 0.1272741 secs] [Times: user=0.30 sys=0.00, real=0.13 secs] [Full GC (Ergonomics) [PSYoungGen: 8019K->7829K(9216K)] [ParOldGen: 10044K->8415K(10240K)] 18063K->16244K(19456K), [Metaspace: 3312K->3312K(1056768K)], 0.1143881 secs] [Times: user=0.39 sys=0.01, real=0.11 secs] [Full GC (Allocation Failure) [PSYoungGen: 7829K->7828K(9216K)] [ParOldGen: 8415K->8396K(10240K)] 16244K->16225K(19456K), [Metaspace: 3312K->3312K(1056768K)], 0.0771517 secs] [Times: user=0.36 sys=0.01, real=0.08 secs] Exception in thread "main" java.lang.OutOfMemoryError: Java heap space Heap at java.util.Arrays.copyOf(Arrays.java:3210) at java.util.Arrays.copyOf(Arrays.java:3181) PSYoungGen total 9216K, used 8069K [0x00000007bf600000, 0x00000007c0000000, 0x00000007c0000000) at java.util.ArrayList.grow(ArrayList.java:261) eden space 8192K, 98% used [0x00000007bf600000,0x00000007bfde1540,0x00000007bfe00000) at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:235) from space 1024K, 0% used [0x00000007bfe00000,0x00000007bfe00000,0x00000007bff00000) at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:227) to space 1024K, 0% used [0x00000007bff00000,0x00000007bff00000,0x00000007c0000000) at java.util.ArrayList.add(ArrayList.java:458) ParOldGen total 10240K, used 8396K [0x00000007bec00000, 0x00000007bf600000, 0x00000007bf600000) at chapter2.HeapOOM.main(HeapOOM.java:20) object space 10240K, 81% used [0x00000007bec00000,0x00000007bf433318,0x00000007bf600000) Metaspace used 3344K, capacity 4496K, committed 4864K, reserved 1056768K class space used 372K, capacity 388K, committed 512K, reserved 1048576K
关于设置VM参数:
Eclipse在Debug页签中设置VM arguments(可以直接参考深入理解JVM书中第2章)
IDEA:点击Edit Configuration..弹出的Run/Debug Conigurations中设置VM options
(1)首先在类文件中右键,选择create 'HeapOOM main()'...,创建Run/Debug Conigurations,在VM options中设置JVM 参数
(2)之后,在这里的下拉列表中即可选择Edit Configuration..
(3)设置VM Options
2.虚拟机栈中的*异常
通过递归的方式,不断的调用stackLeak方法,来增加栈的深度,当栈的深度超过虚拟机允许的最大深度时将抛出SOF异常。
当线程执行一个方法时,会在Java虚拟机栈区创建一个栈帧,并将栈帧压栈,因此不断的执行stackLeak方法,可以增加栈的深度。
JVM参数设置:
-Xss160k
Xss设置栈容量,书中给出的是128k,实际运行时提示Xss最少要160k,因此设置为了160k
** * 演示*(栈深度达到虚拟机允许的最大深度时抛出的SOF异常) * 需要指定VM参数 * -Xss160k */ public class JavaVMStackSOF { private int stackLength = 1; /** * 递归方法,不断的调用自己可以增加栈的深度 */ public void stackLeak() { stackLength++; stackLeak(); } public static void main(String[] args) throws Throwable { JavaVMStackSOF javaVMStackSOF = new JavaVMStackSOF(); try { //调用stackLeak方法,当栈深度达到虚拟机允许的最大深度时,抛出抛出*异常 javaVMStackSOF.stackLeak(); } catch (Throwable e) { System.out.println("stack length:" + javaVMStackSOF.stackLength); throw e; } } }
输出结果,当调用了771次stackLeak方法时,抛出了SOF异常
stack length:771 Exception in thread "main" java.lang.*Error at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:14) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) at chapter2.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:15) ......
3.虚拟机栈中的OutOfMemory异常
操作系统分配给每个进程的内存是有限制的,操作系统内存限制减去Xmx和MaxPermSize的容量,程序计数器消耗内存很小,如果不计算虚拟机进程本身所耗内存,剩下的内存由虚拟机栈和本地方法栈瓜分,因此每个线程分配的栈容量越大,可以建立的线程数就越少,建立线程时就容易耗尽剩下的内存。
内存溢出产生方式:通过不断的创建线程去执行方法中的任务,线程越多,可用的内存就越少,直到内存耗尽,抛出OutOfMemroy异常。
VM参数,设置栈容量为2M:
-Xss2M
代码有风险,运行需谨慎!!!
代码容易导致系统假死,运行前请保存当前的工作。
/** * java虚拟机栈抛出OutOfMemory异常 * 代码有风险,运行需谨慎!!! * 方法:通过不断的建立线程的方式导致内存耗尽产生OutOfMemory异常 * VM参数: * -Xss2M * 来自深入理解Java虚拟机 */ 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(); } }
4.运行时常量池OutOfMemory
由于JDK 1.7中将常量池移到了堆中,不再放在方法区中,因此VM参数适用于JDK 1.7以下的版本。
String.intern()方法作用:如果常量池中已经包含了一个等于此String对象的字符串,返回代表池中的这个字符串的String对象,否则将此String对象包含的字符串添加到常量池中,并返回对象的引用。
产生内存溢出方式:通过不断的在常量池中添加对象,达到最大容量时,抛出内存溢出异常。
VM参数:
-XX:PermSize=10M
-XX:MaxPermSize=10M
PermSize:永久代大小
MaxPermSize:永久代最大容量
import java.util.ArrayList; import java.util.List; /** * 运行时常量池OutOfMemory异常 * 方法:常量池在方法区中(JDK 1.7之前),通过String的intern方法不断的向常量池中添加对象,当没有足够的空间创建新的对象时抛出OOM异常 * 虚拟机参数: * -XX:PermSize=10M * -XX:MaxPermSize=10M * 注意:1.7中常量池被移到了堆中,需要使用1.7之下的JDK * 来自深入理解Java虚拟机 */ public class RuntimeConstantPoolOOM { public static void main(String[] args) { //使用List保持常量池的引用,使对象避免Full GC List<String> list = new ArrayList<String>(); int i = 0; while (true) { //通过intern方法不断的向常量池添加对象 list.add(String.valueOf(i++).intern()); } } }
JDK1.7以上VM参数设置如下:
-Xms20m
-Xmx20m
异常信息:
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded at java.lang.Integer.toString(Integer.java:403) at java.lang.String.valueOf(String.java:3099) at chapter2.RuntimeConstantPoolOOM.main(RuntimeConstantPoolOOM.java:22)
详细可以参考:
Java方法区和运行时常量池溢出问题分析
https://www.cnblogs.com/luoxn28/p/5425425.html
5.方法区OutOfMemoryError
方式:通过CGLib产生大量的动态类,由于方法区需要存放Class的相关信息,因此方法区的容量将会越来越少,直到抛出内存溢出异常。
VM参数:
-XX:PermSize=10M
-XX:MaxPermSize=10M
/** * 方法区OutOfMemory * 方法:通过CGLib,动态生成大量的类 * VM参数: * -XX:PermSize=10M * -XX:MaxPermSize=10M * 来自深入理解Java虚拟机 */ 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 o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { return methodProxy.invokeSuper(o, objects); } }); enhancer.create(); } } }
JDK1.8中移除了永久代,取而代之的是元空间,因此可以通过设置元空间的大小,参数如下:
-XX:MetaspaceSize=10M
-XX:MaxMetaspaceSize=10M
异常信息:
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 chapter2.JavaMethodAreaOOM.main(JavaMethodAreaOOM.java:35)
参考:
Java 8: 从永久代(PermGen)到元空间(Metaspace)