开发者

JAVA 聚焦 OutOfMemoryError 异常问题记录

目录
  • 一、Java 堆溢出
    • 原理
    • 示例代码
    • 解决思路
  • 二、虚拟机栈和本地方法栈溢出
    • 原理
    • 示例代码
    • 解决思路
  • 三、方法区和运行时常量池溢出
    • 原理
    • 示例代码
    • 解决思路
  • 四、直接内存溢出
    • 原理
    • 示例代码
    • 解决思路
  • 解决思路

    在 Java 开发中,内存溢出异常是影响程序稳定性的关键问题。了解其原理和应对方法,对开发者至关重要。

    一、Java 堆溢出

    原理

    Java 堆用于存储对象实例。不断创建对象,且阻止垃圾回收器回收,对象数量超出堆容量时,就会引发堆溢出。

    示例代码

    // VM Args: -Xmx20m -Xms20m -XX:+HeapDumpOnOutOfMemoryError
    public class HeapOOM {
        static class OOMObject {}
        public static void main(String[] args) {
            List<OOMObject> list = new ArrayList<>();
            while (true) {
                list.add(new OOMObject());
            }
        }
    }

    解决思路

    • 利用内存映像分析工具(如 Eclipse Memory Analyzer )分析堆转储快照。
    • 区分内存泄漏和内存溢出:若存在无用对象长期占用内存,是内存泄漏;若对象都有用但堆空间不足,可调整堆参数(-Xmx 与 - Xms ),并优化代码减少内存占用。

    二、虚拟机栈和本地方法栈溢出

    原理

    • 线程请求栈深度超虚拟机允许值,抛出StackOverflowError 。
    • 虚拟机栈若支持动态扩展,扩展时内存申请失败,抛出OutOfMemoryError (HotSpot 不支持栈动态扩展 )。

    示例代码

    测试StackOverflowError

    // VM Args: -Xss128k
    public class JavaVMStackSOF {
        private int stackLength = 1;
        public void stackLeak() {
            stackLength++;
            stackLeak();
        }
        public static void main(String[] args) throws Throwable {
            JjavascriptavaVMStackSOF oom = new JaWvrfttkXvaVMStackSOF();
            try {
                oom.stackLeak();
            } catch (Exception e) {
                System.out.println("stack length:" + oom.stackLength);
                throw e;
            }
        }
    }

    测试大量线程导致内存溢出

    // VM Args: -Xss2M
    public class JavaVMStackOOM {
        private void dontStop() {
            while (true) {}
        }
        public void stackLeakByThread() {
            while (true) {
                Thread thread = new Thread(() -> dontStop());
                thread.start();
            }
        }
        public static void main(String[] args) throws Throwable {
            JavaVMStackOOM oom = new JavaVMStackOOM();
            oom.stackLeakByThread();
        }
    }

    解决思路

    • 出现StackOverflowError 时,可根据错误堆栈分析递归调用等问题代码。
    • 对于大量线程导致的内存溢出,可减少线程数量、调整栈内存大小(-Xss ),或升级 64 位虚拟机以获取更多内存。

    三、方法区和运行时常量池溢出

    原理

    • 方法区存储类信息、常量池等。运行时动态生成大量类(如使用 CGLib ),会耗尽方法区空间。
    • 运行时常量池是方法区一部分,字符串操作(如String.intern() )不当,可能导致常量池溢出。

    示例代码

    方法区溢出测试(借助 CGLib )

    // VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
    import net.sf.cglib.proxy.Enhancer;
    import net.sf.cglib.proxy.MethodInterceptor;
    import net.sf.cglib.proxy.MethodProxy;
    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 obj, java.lang.reflectWvrfttkX.Method method, Object[] args, MethodProxy proxy) throws Throwable {
                        return proxy.invokeSuper(obj, args);
                    }
                });
                enhancer.create();
            }
        }
    }

    运行时常量池溢出测试(String.intern()

    // JDK 6 运行:-XX:PermSize=6M -XX:MaxPermSize=6M
    // JDK 7及以上运行:-Xmx6M
    public class RuntimeConstantPoolOOM {
        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); 
        }
    }

    解决思路

    • 方法区溢出时,调整方法区相关参数(如 JDK 8 前的 - XX:PermSize 和 - XX:MaxPermSize ,JDK 8 及以后的 - XX:MetASPaceSize 等 ),优化代码减少动态类生成。
    • 针对常量池溢出,合理使用String.intern() 方法,避免无意义的字符串入池操作。

    四、直接内存溢出

    原理

    直接内存容量由-XX:MaxDirectMemorySize 参数控制,默认与 Java 堆最大值相同。直接或间接使用DirectByteBufferUnsafe 等分配内存超出限制,会引发溢出。

    示例代码

    // VM Args: -Xmx20M -XX:MaxDirectMemorySize=编程客栈10M
    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);
            }
        }
    }

    解决思路

    • 合理设置-XX:MaxDirectMemorySize 参数。
    • 排查代码中直接内存分配操作,如 NIO 相关代码,确保内存分配合理。
    ld.get(null);
    while (true) {
    unsafe.allocateMemory(_1MB);
    }
    }
    }

    解决思路

    - 合理设置`-XX:MaxDirectMemorySize` 参数。

    - 排查代码中直接内存分配操作,如 NIO 相关代码,确保内存分配合理。

    通过深入理解 Java 内存溢出异常原理,结合具体代码示例和解决思路,开发者能更好地定位和解决内存问题,保障 Java 程序稳定运行。

    到此这篇关于JAVA 聚焦 OutOfMemoryError 异常问题记录的文章就介绍到这了,更多相关java OutOfMemoryError 异常内容请搜索编程客栈(www.devze.com)以前的文章或继续浏览下面的相关文章android希望大家以后多多支持编程客栈(www.devze.com)!

    0

    上一篇:

    下一篇:

    精彩评论

    暂无评论...
    验证码 换一张
    取 消

    最新开发

    开发排行榜