内存溢出异常即OutOfMemoryError, 在Java虚拟机规范中规定, 除程序计数器以外, 其他内存区域都有可能出现OutOfMemoryError
下面就针对不同区域出现的异常进行分析.
虚拟机参数设置
为了能够简单模拟内存溢出异常, 需要给JVM添加堆栈大小, 打印GC堆等参数.下面是一些常用参数
设置
- -Xms 设置java程序启动时初始堆大小
- -Xmx 设置java程序能获得的最大堆大小
- -Xss 设置每个线程的堆栈大小,JDK5.0以后每个线程堆栈默认大小为1M,以前每个线程堆栈默认大小为256K.
- -Xmn 设置年轻代大小( eden + 2 survivor space )
- -XX:SurvivorRatio=n Eden区与Survivor区的大小比值, 例如值为8表示Eden : Survivor from : Survivor to = 8 : 1 : 1
- -XX:NewRatio=n 设置年老代和年轻代的比值, 例如值为3则表示年老代和年轻代的比值为 3 : 1
打印信息
- -XX:+PrintGC
- -XX:+PrintGCDetails
- -XX:+PrintHeapAtGC 打印GC时堆的信息
- -XX:+HeapDumpOnOutOfMemoryError 内存溢出异常时dump出当前内存堆快照
Java堆溢出
堆用来存放对象实例, 是GC的主要区域, 如果堆中的对象不能被有效释放, 并且不断地创建对象, 就会导致OOM异常.
/**
* VM Args: -Xms20m -Xmx20m -xx:+HeapDumpOnOutOfMemoryError
* @author: shuo
* @date: 2019/08/05
*/
public class HeapOOM {
static class OOMObject{}
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true)
{
list.add(new OOMObject());
}
}
}
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid11300.hprof ...
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:265)
at java.util.ArrayList.ensureExplicitCapacity(ArrayList.java:239)
at java.util.ArrayList.ensureCapacityInternal(ArrayList.java:231)
at java.util.ArrayList.add(ArrayList.java:462)
at JavaTest.jvm.HeapOOM.main(HeapOOM.java:19)
Heap dump file created [28340442 bytes in 0.071 secs]
利用内存映像分析工具分析dump下来的快照可以分析内存溢出的原因.
虚拟机栈和本地方法栈溢出
Java虚拟机规范规定了栈会出现两种溢出异常
- StackOverFlowError 堆溢出, 线程中栈内存不够用时抛出
/**
* VM Args: -Xss128k
* @author: shuo
* @date: 2019/08/05
*/
public class StackOF {
private int stackLength = 1;
public void stackLeak()
{
stackLength++;
stackLeak();
}
public static void main(String[] args) {
StackOF stackOf = new StackOF();
try {
stackOf.stackLeak();
}catch (Throwable e)
{
System.out.println(stackOf.stackLength);
e.printStackTrace();
}
}
}
28260
java.lang.StackOverflowError
at JavaTest.jvm.StackOF.stackLeak(StackOF.java:16)
at JavaTest.jvm.StackOF.stackLeak(StackOF.java:16)
at JavaTest.jvm.StackOF.stackLeak(StackOF.java:16)
..........
- OutOfMemoryError 内存溢出, 线程要申请栈空间但内存不够时抛出
/**
* -Xss2M
* @author: shuo
* @date: 2019/08/05
*/
public class StackOOM {
private void dontStop()
{
while (true)
{
}
}
public void stackLeak()
{
while (true)
{
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dontStop();
}
});
thread.start();
}
}
public static void main(String[] args) {
StackOOM stackOOM = new StackOOM();
stackOOM.stackLeak();
}
}
运行时常量池溢出
/**
* VM Args:-XX:PermSize=1M -XX:MaxPermSize=1M
* @author: shuo
* @date: 2019/08/05
*/
public class RuntimeConstantPoolOOM {
public static void main(String[] args) {
List<String> list = new ArrayList();
for (int i = 0;; i++) {
list.add(String.valueOf(i).intern());
}
}
}
在JDK1.7以前
Exception in thread "main" java.lang.OutOfMemoryError: PermGen space
PermGen space 说明运行时常量池在永久代里
在JDK1.7以后while会一直运行下去。
因为JDK1.7的运行时常量池在堆中
需要重新设置虚拟机参数
-Xmx20m -Xms20m -XX:-UseGCOverheadLimit
运行后报错
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
Java heap space 运行时常量池在堆里
jdk1.6常量池放在方法区,jdk1.7常量池放在堆内存,jdk1.8放在元空间里面,和堆相独立。所以string的intern方法在不同版本会有不同表现。
方法区溢出
方法区用于存放Class的相关信息, 例如类名, 访问修饰符, 常量池, 字段描述符, 方法描述符等。
可以产生大量的类填满方法区,直到溢出。
利用CGLib直接操作字节码产生大量的动态类。
/**
* VM Args: -XX:PerSize=10M -XX:MaxPermSize=10M
* @author: shuo
* @date: 2019/08/05
*/
public class JavaMethodAreaOOM {
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(objects, args);
}
});
enhancer.create();
}
}
static class OOMObject{
}
}
Caused by: java.lang.OutOfMemoryError: PermGen space
参考: 《深入理解Java虚拟机》