JVM——15.定位 堆外内存 OOM

文章目录

  • 1. ByteBuffer 堆外内存介绍
  • 2. ByteBuffer 堆外内存申请、释放(源码分析)
    • 2.1 堆外内存申请
    • 2.2 堆外内存释放
  • 3. 什么情况会发生 堆外内存 OOM
  • 4. 模拟 堆外内存 OOM
    • 4.1 模拟1
    • 4.2 模拟2
    • 4.3 模拟3
  • 5. 堆外内存 OOM 的定位及解决

1. ByteBuffer 堆外内存介绍

在介绍OOM那篇文章中,对堆外内存进行了介绍,就直接把它复制过来;

ByteBuffer和DirectByteBuffer:

  • ByteBuffer:字节缓冲区,它有两种实现:
    • HeapByteBuffer:使用jvm堆内存的字节缓冲区;(对应 ByteBuffer源码中的 allocate()方法)
    • DirectByteBuffer:使用堆外内存,不受jvm堆大小限制;(对应 ByteBuffer源码中的allocateDirect()方法)
  • DirectByteBuffer:ByteBuffer对于使用堆外内存的实现,堆外内存直接使用unsafe方法请求堆外内存空间,读写数据;
    • 源码:
      public abstract class ByteBuffer extends Buffer implements Comparable<ByteBuffer> {
             
          // ... 省略
          public static ByteBuffer allocateDirect(int capacity) {
             
              return new DirectByteBuffer(capacity);
          }
          // ... 省略
      }
      

DirectByteBuffer与堆外内存的关系:

  • 我们使用allocateDirect()方法生成的DirectByteBuffer对象,本身是存储在jvm堆中的,但是会在堆外内存中划分一块内存区域与这个对象关联起来;
  • 在YGC或者FGC 回收 DirectByteBuffer对象的时候,会通过虚引用(对虚引用不了解的可以查看前面的文章)来释放它关联的堆外内存空间(由Cleaner类实现);
  • Java NIO 在每次分配堆外内存会进行判断,如果堆外内存空间不足时,使用 System.gc() 尝试释放内存,再次进行判断;

堆外内存空间大小设置:

  • jvm参数指定堆外内存大小:-XX:MaxDirectMemorySize=512m;
  • 但是如果没有手动指定时,用我们前面介绍的获取jvm参数的默认值命令:java -XX:+PrintFlagsFinal -version | grep MaxDirectMemorySize获取到的大小为0;
    其实它这里是使用了VM类中代码来指定的大小:
    public class VM {
         
        private static long directMemory = 64 * 1024 * 1024;
        public static long maxDirectMemory() {
         
            return directMemory;
        }
    }
    
    也就是说堆外内存默认大小为64M;如果指定了jvm参数,就使用指定的大小值;

2. ByteBuffer 堆外内存申请、释放(源码分析)

2.1 堆外内存申请

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(1 * 1024 * 1024);
public static ByteBuffer allocateDirect(int capacity) {
   
    return new DirectByteBuffer(capacity);
}

在使用ByteBuffer的静态方法allocateDirect()申请内存时,会使用 DirectByteBuffer类的构造方法创建一个DirectByteBuffer对象:

DirectByteBuffer(int cap) {
   
    super(-1, 0, cap, cap);
    boolean pa = VM.isDirectMemoryPageAligned();
    int ps = Bits.pageSize();
    long size = Math.max(1L, (long)cap + (pa ? ps : 0));
    // 判断是否有足够的空间可供申请
    // size:根据是否按页对齐,得到的真实需要申请的内存大小
    // cap:用户指定需要的内存大小(<=size)
    Bits.reserveMemory(size, cap);
    long base = 0;
    try {
   
        // 调用 UNsafe方法申请内存
        base = unsafe.

你可能感兴趣的:(jvm,jvm实战,java,java,jvm,jvm.gc,java虚拟机)