java中内存溢出怎么解决,附几个内存溢出的例子!
在java虚拟机规范中,处理程序计数器之外,其他内存区域都有发生OutOfMemoryError(内存溢出)异常的可能。
一、java堆内存溢出
java堆用来存储对象实例,只要不断创建对象,并且保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,那么在对象数量到达最大堆的容量之后就会产生内存溢出异常:
List<Object> list = new ArrayList<>();
while (true) {
list.add(new Object());
}
结果:Throwing OutOfMemoryError
在java语言中,可以作为 GC Roots 的对象包括下面几种:
1、虚拟机栈(栈帧中本地变量表)中引用的对象
2、方法区中类静态属性引用的对象
3、方法区中常量引用的对象
4、本地方法栈中JNI(即一般所说的Native方法)引用的对象
解决:确认内存中对象存在是否必要,及检查是否有内存泄漏。
如果有内存泄漏,查看泄漏对象到 GC Roots 引用链,定位泄漏对象。
如果没有内存泄漏,检查虚拟机对参数(-Xmx和-Xms)是否可以调大,检查代码中是否有哪些对象的生命周期过长,尝试减少程序运行期的内存消耗
二、虚拟机栈和本地方法栈内存溢出
HotSpot虚拟机直接把本地方法栈和JAVA虚拟机栈和二为一了。对于HotSpot来说,虽然 -Xoss(设定本地方法栈大小)参数存在,但是无效的。栈大小只由 -Xss参数设定。
在Java虚拟机规范中描述了两种异常:
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出StackOverFlowError异常
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出OutOfMemoryError异常
在代码测试后发现:
单线程下,栈帧过大,虚拟机容量过小都不会导致OutOfMemoryError,只会导致StackOverflowError(栈会比内存先爆掉),一般多线程才会出现OutOfMemoryError,因为线程本身要占用内存。
例如:栈(StackOverflowError)
如果线程请求的栈深度大于虚拟机栈所允许的最大深度,将会抛出StackOverflowError异常:
private int stackLength = 1;
public void stackLeak(){
stackLength++;
stackLeak();
}
结果: java.lang.StackOverflowError: stack size 8MB
方法执行时在虚拟机栈中会创建对应栈帧,不加限制的递归调用很快会撑爆虚拟机栈。
栈(OutOfMemoryError)
如果虚拟机在拓展栈时无法申请到足够的空间则抛出OutOfMemoryError异常
while (true) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
dowhile();
}
});
thread.start();
}
private void dowhile() {
while (true) {
}
}
注:上述代码执行有风险,可能会导致系统假死
解决:
如果是多线程导致的OutOfMemoryError,在不能减少线程数或更换64位虚拟机的情况,只能通过减少最大堆和减少栈容量来换取更多的线程;(这个调节思路和 Java 堆出现 OOM 正好相反,Java 堆出现 OOM 要调大堆内存的设置值,而栈出现 OOM 反而要调小)
三、方法区和运行时常量池溢出
首先介绍String.intern()这个本地方法,它的作用是:如果字符串常量池中已经包含一个此String对象的字符串,则返回代表池中这个字符串的String对象,否则,将此String对象包含的字符串添加到常量池中,并且返回此String对象的引用。
在 JDK1.6 我们可通过-XX: PermSize 和 -XX:MaxPermSize限制方法区大小,从而间接限制其中常量池的容量
常量池内存溢出:
//使用List保持着常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<String>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
这段代码在JDK1.6时运行会抛出OutOfMemoryError异常,在 JDK1.7时则不会,程序会一直while循环下去。
方法区内存溢出:思路,动态创建Class对象。大量产生JSP或动态产生JSP文件的应用,JSP第一次运行时会被编译成Class类。
四、本地直接内存溢出
DirectMemory容量可通过 -XX:MaxDirectMemorySize指定,如果不指定,则默认与java堆最大值一样,虽然使用DirectByteBuffer分配内存也会抛出内存溢出异常,但它抛出异常时并没有真正向操作系统申请分配内存,而是通过计算得知内存无法分配,于是手动抛出异常,真正申请分配内存的方法是unsafe.allocateMemory()
private final int_1MB = 1024 * 1024;
Field unsafeField = Unsafe.class.getDeclaredFields()[0];
unsafeField.setAccessible(true);
Unsafe unsafe = (Unsafe) unsafeField.get(null);
while(true){
unsafe.allocateMemory(_1MB);
}
结果:java.lang.OutOfMemoryError