• 【JVM】关于OOM的二三事


    组织架构

    严格来说,StackOverflowError和OutOfMemoryError都属于错误,而不是异常。

    java.lang.StackOverflowError

    1 public class StackOverflowErrorDemo {
    2     public static void method(){
    3         method();
    4     }
    5     public static void main(String[] args) {
    6         method();
    7     }
    8 }

    在上例中,方法的深度调用,导致栈溢出。

    java.lang.OutOfMemoryError

    java.lang.OutOfMemoryError:Java heap space

     1 public class JavaHeapSpaceDemo {
     2     private static List<String> list = new ArrayList<>();
     3     public static void main(String[] args) {
     4         String str = "";
     5         while(true){
     6             str += str + new Random().nextInt(111111);
     7             list.add(str);
     8         }
     9     }
    10 }

    Java堆用于存储对象实例,不断创建对象,保证这些对象到GC Roots有可达路径,可以避免对象被垃圾回收,很快对象数量就会达到最大堆的容量限制,产生内存溢出异常。

    java.lang.OutOfMemoryError:GC overhead limit exceeded

    jdk1.6新增的错误类型,GC回收时间过长时会抛出OOM。超过98%的时间用来做GC,但是只回收了2%的堆内存,连续多次垃圾回收,只回收了不到2%的极端情况才会抛出。

    经过垃圾回收释放的2%可用内存空间会快速的被填满,迫使GC再次执行,出现频繁的执行GC操作, 服务器会因为频繁的执行GC垃圾回收操作而达到100%的使用率,服务器运行变慢,应用系统会出现卡死现象,平常只需几毫秒就可以执行的操作,现在需要更长时间,甚至是好几分钟才可以完成。

    如果不抛出GC overhead limit exceeded,GC会频繁的执行,但是被占用的内存,经过多次长时间的GC操作都无法回收,导致可用内存越来越少,俗称内存泄露。

    1 public class GCOverheadDemo {
    2     public static void main(String[] args) {
    3         int i =0;
    4         List<String> list = new ArrayList<>();
    5         while (true){
    6             list.add(String.valueOf(++i).intern());
    7         }
    8     }
    9 }

    String.intern()是一个Native方法,底层调用C++的 StringTable::intern方法实现。

    当通过语句str.intern()调用intern()方法后,JVM 就会在当前类的常量池中查找是否存在与str等值的String,若存在则直接返回常量池中相应Strnig的引用;若不存在,则会在常量池中创建一个等值的String,然后返回这个String在常量池中的引用。

    在此为了快速产生这种异常,首先配置VM options为【-Xms10m -Xmx10m -XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m】,再通过intern()循环创建字符串。从GC的打印日志可以看出,虽然一直在JVM执行GC,但是回收没有效果,最终抛出异常。

     

    java.lang.OutOfMemoryError:Direct buffer memory

    产生这种异常的原因是,写NIO程序经常使用ByteBuffer来读取或写入,NIO是一种基于通道和缓冲区的IO方式,它可以使用本地函数库直接分配堆外内存,通过存储在Java堆中的DirectByteBuffer对象作为这块内存的引用进行操作,这样可以在一些情况下显著提升性能,它避免了在Java堆和Native堆中来回复制数据。

    ByteBuffer有两种分配内存的方式:

    1. allocate:分配JVM堆内存,属于GC范畴,需要拷贝数据,速度慢。
    2. allocateDirect:分配OS本地内存,不属于GC范畴,不需要内存拷贝,速度较快。

    如果不断分配本地内存,堆内存很少使用,JVM也就不需要GC,DirectByteBuffer对象就不会被回收,如果本地内存快被用完了,之后再分配本地内存就会抛出OOM。

    java.lang.OutOfMemoryError:unable to create new native thread

    高并发请求服务器的时候,经常会出现该异常,该异常与平台有关,导致的原因是应用创建了太多线程,一个应用进程创建多个线程,超过系统承载的极限,服务器不允许创建这么多线程,linux默认允许单个进程创建的线程数是1024个。如果超过允许的值,就会抛出异常。

    解决的方法是减少创建线程的数量,尽可能少的创建线程。或者修改服务器配置,扩大限制的线程数量。

    java.lang.OutOfMemoryError:Metaspace

    Java8之后元空间取代了永久代,元空间的本质和永久代类似,都是JVM规范中方法区的实现,其区别在于元空间不虚拟机中,而是在本地内存中,默认情况下,元空间大小仅受内存限制。主要存放:虚拟机加载的类信息、常量池、静态变量、即时编译后的代码。

    解决的方法是通过配置-XX:MaxMetaspaceSize=512m参数,增大Metaspace的空间。

    还可以直接去掉 Metaspace 的大小限制。 但是,如果不限制Metaspace内存的大小,当物理内存过载的时候,有可能会引起内存交换,严重拖累系统性能。此外,还可能造成native内存分配失败等问题。

  • 相关阅读:
    sql查询指定表外键约束
    C#6.0新特性
    事务嵌套
    怎么在项目中应用委托
    单线程与多线程
    winform线程下载网页信息
    Linux笔记 FHS目录结构
    Linux笔记 Linux文件系统
    Linux笔记 软件管理
    Linux笔记 vi/vim编辑器
  • 原文地址:https://www.cnblogs.com/xdcat/p/13034342.html
Copyright © 2020-2023  润新知