教你写Bug,常见的 OOM 异常分析( 三 )

四、Direct buffer memory我们使用 NIO 的时候经常需要使用 ByteBuffer 来读取或写入数据,这是一种基于 Channel(通道) 和 Buffer(缓冲区)的 I/O 方式,它可以使用 Native 函数库直接分配堆外内存,然后通过一个存储在 Java 堆里面的 DirectByteBuffer 对象作为这块内存的引用进行操作 。这样在一些场景就避免了 Java 堆和 Native 中来回复制数据,所以性能会有所提高 。

Java 允许应用程序通过 Direct ByteBuffer 直接访问堆外内存,许多高性能程序通过 Direct ByteBuffer 结合内存映射文件(Memory MApped File)实现高速 IO 。
4.1 写个 bug
  • ByteBuffer.allocate(capability) 是分配 JVM 堆内存,属于 GC 管辖范围,需要内存拷贝所以速度相对较慢;
  • ByteBuffer.allocateDirect(capability) 是分配 OS 本地内存,不属于 GC 管辖范围,由于不需要内存拷贝所以速度相对较快;
如果不断分配本地内存,堆内存很少使用,那么 JVM 就不需要执行 GC,DirectByteBuffer 对象就不会被回收,这时虽然堆内存充足,但本地内存可能已经不够用了,就会出现 OOM,本地直接内存溢出 。
/** *  VM Options:-Xms10m,-Xmx10m,-XX:+PrintGCDetails -XX:MaxDirectMemorySize=5m */public class DirectBufferMemoryDemo {    public static void main(String[] args) {        System.out.println("maxDirectMemory is:"+sun.misc.VM.maxDirectMemory() / 1024 / 1024 + "MB");        //ByteBuffer buffer = ByteBuffer.allocate(6*1024*1024);        ByteBuffer buffer = ByteBuffer.allocateDirect(6*1024*1024);    }}最大直接内存,默认是电脑内存的 1/4,所以我们设小点,然后使用直接内存超过这个值,就会出现 OOM 。
maxDirectMemory is:5MBException in thread "main" java.lang.OutOfMemoryError: Direct buffer memory4.2 解决方案
  1. Java 只能通过 ByteBuffer.allocateDirect 方法使用 Direct ByteBuffer,因此,可以通过 Arthas 等在线诊断工具拦截该方法进行排查
  2. 检查是否直接或间接使用了 NIO,如 netty,jetty 等
  3. 通过启动参数 -XX:MaxDirectMemorySize 调整 Direct ByteBuffer 的上限值
  4. 检查 JVM 参数是否有 -XX:+DisableExplicitGC 选项,如果有就去掉,因为该参数会使 System.gc() 失效
  5. 检查堆外内存使用代码,确认是否存在内存泄漏;或者通过反射调用 sun.misc.Cleaner 的 clean() 方法来主动释放被 Direct ByteBuffer 持有的内存空间
  6. 内存容量确实不足,升级配置
五、Unable to create new native thread每个 Java 线程都需要占用一定的内存空间,当 JVM 向底层操作系统请求创建一个新的 native 线程时,如果没有足够的资源分配就会报此类错误 。
5.1 写个 bugpublic static void main(String[] args) {  while(true){    new Thread(() -> {      try {        Thread.sleep(Integer.MAX_VALUE);      } catch(InterruptedException e) { }    }).start();  }}Error occurred during initialization of VMjava.lang.OutOfMemoryError: unable to create new native thread5.2 原因分析
教你写Bug,常见的 OOM 异常分析

文章插图
 
JVM 向 OS 请求创建 native 线程失败,就会抛出 Unableto createnewnativethread,常见的原因包括以下几类:
  • 线程数超过操作系统最大线程数限制(和平台有关)
  • 线程数超过 kernel.pid_max(只能重启)
  • native 内存不足;该问题发生的常见过程主要包括以下几步:
  1. JVM 内部的应用程序请求创建一个新的 Java 线程;
  2. JVM native 方法代理了该次请求,并向操作系统请求创建一个 native 线程;
  3. 操作系统尝试创建一个新的 native 线程,并为其分配内存;
  4. 如果操作系统的虚拟内存已耗尽,或是受到 32 位进程的地址空间限制,操作系统就会拒绝本次 native 内存分配;
  5. JVM 将抛出 java.lang.OutOfMemoryError:Unableto createnewnativethread 错误 。
5.3 解决方案
  1. 想办法降低程序中创建线程的数量,分析应用是否真的需要创建这么多线程
  2. 如果确实需要创建很多线程,调高 OS 层面的线程最大数:执行 ulimia-a 查看最大线程数限制,使用 ulimit-u xxx 调整最大线程数限制


    推荐阅读