精灵王


  • 首页

  • 文章归档

  • 所有分类

  • 关于我

  • 搜索
设计模式-行为型 设计模式-创建型 设计模式-结构型 设计 系统设计 设计模式之美 分布式 Redis 并发编程 个人成长 周志明的软件架构课 架构 单元测试 LeetCode 工具 位运算 读书笔记 操作系统 MySQL 异步编程 技术方案设计 集合 设计模式 三亚 游玩 转载 Linux 观察者模式 事件 Spring SpringCloud 实战 实战,SpringCloud 源码分析 线程池 同步 锁 线程 线程模型 动态代理 字节码 类加载 垃圾收集器 垃圾回收算法 对象创建 虚拟机内存 内存结构 Java

Java 内存中几种常见的OOM 异常

发表于 2020-09-24 | 分类于 Java虚拟机 | 0

在JAVA虚拟机规范说明中,除了程序计数器不会出现OutOfMemoryError(简称OOM),其他几个内存区域都有可能发生OOM异常。

JAVA堆溢出

JAVA堆用于存储对象实例,只要不断的new对象,并且保证GC roots到对象之间有可达路径来避免垃圾回收这些对象,在对象达到一定的数量之后,就会产生OOM异常。

示例:

  1. 使用JVM参数限制JAVA堆的大小

    -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=D:/var/log/gc_dump.dump

  2. JAVA程序代码示例:

    public class TestOOM{
        static class OOMObejct{
    
        }
        public static void main(String[] args){
            List<OOMObejct> list = new ArrayList<>();
            while(true){
                list.add(new OOMObejct());
            }
        }
    }
    
  3. 运行结果:

    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 net.admol.jingling.demo.jvm.TestOOM.main(TestOOM.java:19)
    

当异常信息出现“java.lang.OutOfMemoryError”,并且后面有提示"Java heap space"时,就是JAVA堆出现了内存溢出。

栈溢出

由于虚拟机中并不区分虚拟机栈和本地方法栈, 栈的容量通过JVM参数 -Xss 设置,栈异常有两种可能异常:

  1. 线程的栈请求深度大于虚拟机所允许的最大深度时,会抛出StackOverFlowError异常
  2. 虚拟机栈在扩展栈时无法申请到足够的空间,会抛出OutOfMemoryError异常

异常示例:

  1. JVM参数限制栈大小

    -Xss128k

  2. JAVA程序代码示例:

    public class TestJVMStackOME{
        private int length = 1;
        public void stackLeak(){
            length++;
            stackLeak();
        }
        public static void main(String[] args){
            TestJVMStackOME test = new TestJVMStackOME();
            try{
                test.stackLeak();
            }catch(Exception e){
                System.out.println("stack length:"+test.length);
                throw e;
            }
        }
    }
    
  3. 运行结果:

    Exception in thread "main" java.lang.StackOverflowError
    	at net.admol.jingling.demo.jvm.TestJVMStackOME.stackLeak(TestJVMStackOME.java:11)
    	at net.admol.jingling.demo.jvm.TestJVMStackOME.stackLeak(TestJVMStackOME.java:12)
      ......省略更多的输出
    

方法区溢出

因为运行时常量池是方法区的一部分,所以它们抛出的异常都是一样的。

String.intern()是一个Native方法,它的作用是:如果常量池中已经包含了一个等于此String对象的字符串,则返回代表池中这个字符串的String对象;否则,将此String 对象包含的字符串添加到常量池中,并且返回此String对象的引用。

在JDK1.6及之前,由于常量池分配在永久代内,我们可以通过-XX:PermSize 和 -XX:MaxPermSize 参数来限制方法区大小,从而间接限制常量池的大小。

示例:

  1. 设置JVM参数限制方法区大小

    XX:PermSize=5M -XX:MaxPermSize=5M

  2. JAVA程序代码示例:

    public class TestMethodOOM{
        public static void main(String[] args){
            List<String> list = new ArrayList<>();
            int i = 100;
            while(true){
                list.add((String.valueOf(i++).intern()));
            }
        }
    }
    
  3. 在jdk1.6及之前会提示java.lang.OutOfMemoryError:PermGen space

    因为jdk1.8移除了PermGen(永久代),替换成了元空间(Metaspace),所以1.7及之后都不会报错

直接堆外内存溢出

DirectMemory容量可通过-XX:MaxDirectMemory 指定,如果不指定,则默认和JAVA堆最大值一致。

溢出示例:

  1. 设置JVM参数

    Xmx10m -XX:MaxDirectMemorySize=5M

  2. JAVA程序代码示例

    public class TestDirectMemoryOOM{
        public static void main(String[] args) throws IllegalAccessException{
            Field field = Unsafe.class.getDeclaredFields()[0];
            field.setAccessible(true);
            Unsafe unsafe = (Unsafe)field.get(null);
            long _1M = 1024*1024;
            while(true){
                unsafe.allocateMemory(_1M);
            }
        }
    }
    
  3. 运行结果

    Exception in thread "main" java.lang.OutOfMemoryError
    	at sun.misc.Unsafe.allocateMemory(Native Method)
    	at net.admol.jingling.demo.jvm.TestDirectMemoryOOM.main(TestDirectMemoryOOM.java:18)
    

因为DirectMemory导致的内存溢出, 一个明显的特征是在Heap Dump文件中不会看见明显的异常, 如果发现dump的文件比较小, 而程序中又直接或间接使用了NIO,那就需要好好思考一下是否是DirectMemory这方面的原因了。

精 灵 王 wechat
👆🏼欢迎扫码关注微信公众号👆🏼
  • 本文作者: 精 灵 王
  • 本文链接: https://jinglingwang.cn/archives/oom
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# 设计模式-行为型 # 设计模式-创建型 # 设计模式-结构型 # 设计 # 系统设计 # 设计模式之美 # 分布式 # Redis # 并发编程 # 个人成长 # 周志明的软件架构课 # 架构 # 单元测试 # LeetCode # 工具 # 位运算 # 读书笔记 # 操作系统 # MySQL # 异步编程 # 技术方案设计 # 集合 # 设计模式 # 三亚 # 游玩 # 转载 # Linux # 观察者模式 # 事件 # Spring # SpringCloud # 实战 # 实战,SpringCloud # 源码分析 # 线程池 # 同步 # 锁 # 线程 # 线程模型 # 动态代理 # 字节码 # 类加载 # 垃圾收集器 # 垃圾回收算法 # 对象创建 # 虚拟机内存 # 内存结构 # Java
自己写一个简单的线程池
Java 线程模型与线程调度
  • 文章目录
  • 站点概览
精 灵 王

精 灵 王

青春岁月,以此为伴

106 日志
14 分类
48 标签
RSS
Github E-mail
Creative Commons
Links
  • 添加友链说明
© 2023 精 灵 王
渝ICP备2020013371号
0%