Java内存溢出的几种情况

目录

  1. Java堆溢出
    1.1 模拟场景
    1.2 用内存影响分析工具分析堆快照
    2.虚拟机栈和本地方法栈溢出
    2.1 StackOverflowError异常
    2.2 OutOfMemoryError异常
    3.方法区和运行时常量池溢出
    3.1 运行时常量区溢出
    3.2 String创建对象和对应内存状态
    3.3 运行时方法区溢出
    4.本机直接内存溢出
  2. JVM相关wiki和工具

正文

本文通过几段代码模拟实际的内存溢出异常。

文中代码都是基于Oracle公司的HotSpot虚拟机运行的。

回到顶部
1. Java堆溢出
1.1 模拟场景
Java堆用于存储对象,只要不断的创建对象,并保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,

那么在对象数量到达最大堆的容量限制后就会产生内存溢出异常。

复制代码
package com.lindaxuan.outofmemory;

import java.util.ArrayList;
import java.util.List;

/**
* VM Args:-Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
* 将堆的最小值-Xms参数与最大值-Xmx参数设置为一样即可避免堆自动扩展。
* @author linxuan
*/
public class HeapOOM {

static class OOMObject {
}

public static void main(String[] args) {
    List<OOMObject> list = new ArrayList<OOMObject>();

    while (true) {
        list.add(new OOMObject());
    }
}

}
/*
result:
java.lang.OutOfMemoryError: Java heap space
Dumping heap to java_pid9220.hprof …
Heap dump file created [27717826 bytes in 0.160 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 com.lindaxuan.outofmemory.HeapOOM.main(HeapOOM.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:606)
at com.intellij.rt.execution.application.AppMain.main(AppMain.java:144)
*/
复制代码
1.2 用内存影响分析工具分析堆快照
回到顶部

回到顶部
2.虚拟机栈和本地方法栈溢出
HotSpot虚拟机中不区分虚拟机栈和本地方法栈。栈容量用-Xss参数设定。Java虚拟机规范中描述了两种异常:

如果线程请求的栈深度大于虚拟机锁允许的最大深度,将抛出StackOverflowError异常。
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常。
2.1 StackOverflowError异常
复制代码
package com.lindaxuan.outofmemory;

/**
* VM Args:-Xss128k
* Error: Could not create the Java Virtual Machine.

Error: A fatal exception has occurred. Program will exit.
The stack size specified is too small, Specify at least 160k

VM Args:-Xss256k
* @author linxuan
*/
public class JavaVMStackSOF {

private int stackLength = 1;

public void stackLeak() {
    stackLength++;
    stackLeak();
}

public static void main(String[] args) throws Throwable {
    JavaVMStackSOF oom = new JavaVMStackSOF();
    try {
        oom.stackLeak();
    } catch (Throwable e) {
        System.out.println("stack length:" + oom.stackLength);
        throw e;
    }
}

}

/*
result:
stack length:1868
Exception in thread “main” java.lang.StackOverflowError
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:18)
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)
at com.lindaxuan.outofmemory.JavaVMStackSOF.stackLeak(JavaVMStackSOF.java:19)

*/
复制代码
当单个线程下,无论是由于栈帧太大还是虚拟机栈容量太小,当内存无法分配的时候,虚拟机抛出的都是StackOverflowError异常。

2.2 OutOfMemoryError异常
复制代码
package com.lindaxuan.outofmemory;

/**
* VM Args:-Xss2M (这时候不妨设大些)
* @author linxuan
*/
public class JavaVMStackOOM {

private void dontStop() {
    while (true) {
    }
}

public void stackLeakByThread() {
    while (true) {
        Thread thread = new Thread(new Runnable() {
            public void run() {
                dontStop();
            }
        });
        thread.start();
    }
}

public static void main(String[] args) throws Throwable {
    JavaVMStackOOM oom = new JavaVMStackOOM();
    oom.stackLeakByThread();
}

}
/*
my result:
run too long
*/
复制代码
回到顶部
3.方法区和运行时常量池溢出
3.1 运行时常量区溢出
下面这段代码需要jdk1.6模拟。

复制代码
package com.lindaxuan.outofmemory;

import java.util.ArrayList;
import java.util.List;

/**
* VM Args:-XX:PermSize=10M -XX:MaxPermSize=10M
* @author zzm
* could not download jdk1.6 for macos
*/
public class RuntimeConstantPoolOOM {

public static void main(String[] args) {
    // 使用List保持着常量池引用,避免Full GC回收常量池行为
    List<String> list = new ArrayList<String>();
    // 10MB的PermSize在integer范围内足够产生OOM了
    int i = 0;
    while (true) {
        list.add(String.valueOf(i++).intern());
    }
}

}
/*
result:
run too long
*/
复制代码
String.intern()返回引用的测试

复制代码
package com.lindaxuan.outofmemory;

public class RuntimeConstantPoolOOM2 {

public static void main(String[] args) {
    String str1 = new StringBuilder("中国").append("钓鱼岛").toString();
    System.out.println(str1.intern() == str1);

    String str2 = new StringBuilder("ja").append("va").toString();
    System.out.println(str2.intern() == str2);
}

}
/*
result:
true
false
*/
复制代码
对于jdk1.6,intern()方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中这个字符串实例的引用。

而StringBuilder创建的字符串实例在Java堆,所以必然不是同一个引用,将返回false。

而jdk1.7中的intern()实现不会复制实例,只是在常量池中首次出现的实例引用,因此intern()返回的引用和由StringBuild创建的那个字符串实例是同一个。

3.2 String创建对象和对应内存状态
再看另一段代码

复制代码
public class StringConstantPool {
public static void main(String[] args) {

    String str1 = new StringBuilder("中国").append("钓鱼岛").toString();
    System.out.println(str1.intern() == str1);

    String str2 = new String("倚天不屠龙");
    System.out.println(str2.intern() == str2);
}

}
/*
true
false
*/
复制代码
“中国钓鱼岛”和“倚天不屠龙”都在常量区中不存在,那么为什么输出结果一个是true,另一个是false呢?

这就涉及到创建String对象的原理。下面我们将代码和内存对应起来看一下。

String str1 = new StringBuilder(“中国”).append(“钓鱼岛”).toString(); String创建对象时,会把参数”中国”和“钓鱼岛”放到常量池中
内存状态1

System.out.println(str1.intern() == str1); // str1.intern()将str1的引用复制到常量池中
内存状态2

String str2 = new String(“倚天不屠龙”); //String创建对象时,会把参数”倚天不屠龙”放到常量池中
内存状态3

System.out.println(str2.intern() == str2); // str2.intern()先去常量池中看有没有”倚天不屠龙”,已经有了。
内存状态4 (和内存状态3一致)

3.3 运行时方法区溢出
下面一段代码借助CGLib使方法区出现内存溢出异常。

复制代码
package com.lindaxuan.outofmemory;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

/**
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
* @author linxuan
*/
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() {
            public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
                return proxy.invokeSuper(obj, args);
            }
        });
        enhancer.create();
    }
}

static class OOMObject {

}

}
/*
* result:
Exception in thread “main”
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread “main”
*/
复制代码
回到顶部
4.本机直接内存溢出
DirectMemory容量可通过-XX: MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值 (-Xmx指定)一样。

复制代码
package com.lindaxuan.outofmemory;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

/**
* VM Args:-Xmx20M -XX:MaxDirectMemorySize=10M
* @author linxuan
*/
public class DirectMemoryOOM {

private static final int _1MB = 1024 * 1024;

public static void main(String[] args) throws Exception {
    Field unsafeField = Unsafe.class.getDeclaredFields()[0];
    unsafeField.setAccessible(true);
    Unsafe unsafe = (Unsafe) unsafeField.get(null);
    while (true) {
        unsafe.allocateMemory(_1MB);
    }
}

}
复制代码
以上是模拟各种类型的内存溢出异常。

注:

本文的代码基于于深入理解Java虚拟机 2.4 实战:OutOfMemoryError异常,有轻微调整。

虚拟机的参数根据机器性能不同可以灵活调整。

回到顶部
5. JVM相关wiki和工具
内存分析工具,memory analyzer下载

借HSDB来探索HotSpot VM的运行时数据

java反编译工具javap

java基础和工具

JAVA虚拟机体系结构

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值