Java内存溢出的几种情况_codestorm_新浪博客

本文通过几个示例代码模拟并解析了Java中的各种内存溢出异常情况,包括Java堆、虚拟机栈、方法区及本机直接内存溢出等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


目录

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

正文

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

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

回到顶部
1. Java堆溢出

1.1 模拟场景

Java堆用于存储对象,只要不断的创建对象,并保证GC Roots到对象之间有可达路径来避免垃圾回收机制清除这些对象,

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

复制代码
package com.lindaxuan.outofmemory;

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

public class HeapOOM {

    static class OOMObject {
    }

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

        while (true) {
            list.add(new OOMObject());
        }
    }
}
复制代码
1.2 用内存影响分析工具分析堆快照

回到顶部


 



回到顶部
2.虚拟机栈和本地方法栈溢出

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

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

复制代码
package com.lindaxuan.outofmemory;

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;
        }
    }
}

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

2.2 OutOfMemoryError异常

复制代码
package com.lindaxuan.outofmemory;

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();
    }
}
复制代码
回到顶部
3.方法区和运行时常量池溢出

3.1 运行时常量区溢出

下面这段代码需要jdk1.6模拟。

复制代码
package com.lindaxuan.outofmemory;

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

public class RuntimeConstantPoolOOM {

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

这就涉及到创建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;

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 {

    }
}
复制代码
回到顶部
4.本机直接内存溢出

DirectMemory容量可通过-XX: MaxDirectMemorySize指定,如果不指定,则默认与Java堆最大值 (-Xmx指定)一样。

复制代码
package com.lindaxuan.outofmemory;

import sun.misc.Unsafe;

import java.lang.reflect.Field;

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异常,有轻微调整。

虚拟机的参数根据机器性能不同可以灵活调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值