zoukankan      html  css  js  c++  java
  • 【转载】 DirectByteBuffer内存释放

    http://www.tianshouzhi.com/api/tutorials/netty/331

    我们已经知道,在网络编程中,为了避免频繁的在用户空间与内核空间拷贝数据,通常会直接从内核空间中申请内存,存放数据,在Java中,把内核空间的内存称之为直接内存,nio包中的ByteBufferallocateDirect方法,就是帮助我们申请直接内存的,代码如下所示:

    1. public static ByteBuffer allocateDirect(int capacity) {
    2.     return new DirectByteBuffer(capacity);
    3. }

    在上述代码片段中,返回的是一个DirectByteBuffer对象,其是ByteBuffer的子类,对于直接内存的分配,就是在这个类中实现的。

    有经验的读者可能知道,在java中,直接内存的申请与释放是通过Unsafe类的allocateMemory方法和freeMemory方法来实现的,且对于直接内存的释放,必须手工调用freeMemory方法,因为JVM只能帮我们管理堆内存,直接内存不在其管理范围之内。

    DirectByteBuffer帮我们简化了直接内存的使用,我们不需要直接操作Unsafe类来进行直接内存的申请与释放,那么其是如何实现的呢?

    直接内存的申请:

    在DirectByteBuffer实例通过构造方法创建的时候,会通过Unsafe类的allocateMemory方法 帮我们申请直接内存资源。

    直接内存的释放:

    DirectByteBuffer本身是一个Java对象,其是位于堆内存中的,JDK的GC机制可以自动帮我们回收,但是其申请的直接内存,不再GC范围之内,无法自动回收。好在JDK提供了一种机制,可以为堆内存对象注册一个钩子函数(其实就是实现Runnable接口的子类),当堆内存对象被GC回收的时候,会回调run方法,我们可以在这个方法中执行释放DirectByteBuffer引用的直接内存,即在run方法中调用Unsafe 的freeMemory 方法。注册是通过sun.misc.Cleaner类来实现的。

    下面通过源码进行分析:

    1. class DirectByteBuffer extends MappedByteBuffer  implements DirectBuffer
    2. {
    3.     ....
    4.     //构造方法
    5.     DirectByteBuffer(int cap) {                   // package-private
    6.     
    7.         super(-1, 0, cap, cap);
    8.         boolean pa = VM.isDirectMemoryPageAligned();
    9.         int ps = Bits.pageSize();
    10.         long size = Math.max(1L, (long)cap + (pa ? ps : 0));//对申请的直接内存大小,进行重新计算
    11.         Bits.reserveMemory(size, cap);
    12.     
    13.         long base = 0;
    14.         try {
    15.             base = unsafe.allocateMemory(size); //分配直接内存,base表示的是直接内存的开始地址
    16.         } catch (OutOfMemoryError x) {
    17.             Bits.unreserveMemory(size, cap);
    18.             throw x;
    19.         }
    20.         unsafe.setMemory(base, size, (byte) 0);
    21.         if (pa && (base % ps != 0)) {
    22.             // Round up to page boundary
    23.             address = base + ps - (base & (ps - 1));
    24.         } else {
    25.             address = base;
    26.         }
    27.         cleaner = Cleaner.create(this, new Deallocator(base, size, cap));//注册钩子函数,释放直接内存
    28.         att = null;
    29.     
    30.     }
    31.       ....
    32. }

    可以看到构造方法中的确是用了unsafe.allocateMemory方法帮我们分配了直接内存,另外,在构造方法的最后,通过 Cleaner.create方法注册了一个钩子函数,用于清除直接内存的引用。

    Cleaner.create方法声明如下所示:

    1. public static Cleaner create(Object heapObj, Runnable task)

    其中第一个参数是一个堆内存对象,第二个参数是一个Runnable任务,表示这个堆内存对象被回收的时候,需要执行的回调方法。我们可以看到在DirectByteBuffer的最后一行中,传入的这两个参数分别是this,和一个Deallocator(实现了Runnable接口),其中this表示就是当前DirectByteBuffer实例,也就是当前DirectByteBuffer被回收的时候,回调Deallocator的run方法

    Deallocator就是用于清除DirectByteBuffer引用的直接内存,代码如下所示:

    1. private static class Deallocator
    2.     implements Runnable
    3. {
    4.  
    5.     private static Unsafe unsafe = Unsafe.getUnsafe();
    6.  
    7.     private long address;
    8.     private long size;
    9.     private int capacity;
    10.  
    11.     private Deallocator(long address, long size, int capacity) {
    12.         assert (address != 0);
    13.         this.address = address;
    14.         this.size = size;
    15.         this.capacity = capacity;
    16.     }
    17.  
    18.     public void run() {
    19.         if (address == 0) {
    20.             // Paranoia
    21.             return;
    22.         }
    23.         unsafe.freeMemory(address);//清除直接内存
    24.         address = 0;
    25.         Bits.unreserveMemory(size, capacity);
    26.     }
    27.  
    28. }

    可以看到run方法中调用了unsafe.freeMemory方法释放了直接内存的引用。

    关于System.gc对直接内存释放的影响

    细心的读者,可能注意到了,在DirectByteBuffer实例创建的时候,分配内存之前调用了Bits.reserveMemory方法,如果分配失败调用了Bits.unreserveMemory,同时在Deallocator释放完直接内存的时候,也调用了Bits.unreserveMemory方法。

    这两个方法,主要是记录jdk已经使用的直接内存的数量,当分配直接内存时,需要进行增加,当释放时,需要减少,源码如下:

    1. static void reserveMemory(long size, int cap) {
    2.     //如果直接有足够多的直接内存可以用,直接增加直接内存引用的计数
    3.     synchronized (Bits.class) {
    4.         if (!memoryLimitSet && VM.isBooted()) {
    5.             maxMemory = VM.maxDirectMemory();
    6.             memoryLimitSet = true;
    7.         }
    8.         // -XX:MaxDirectMemorySize limits the total capacity rather than the
    9.         // actual memory usage, which will differ when buffers are page
    10.         // aligned.
    11.         if (cap <= maxMemory - totalCapacity) {//维护已经使用的直接内存的数量
    12.             reservedMemory += size;
    13.             totalCapacity += cap;
    14.             count++;
    15.             return;
    16.         }
    17.     }
    18.    //如果没有有足够多的直接内存可以用,先进行垃圾回收
    19.     System.gc();
    20.     try {
    21.         Thread.sleep(100);//休眠100秒,等待垃圾回收完成
    22.     } catch (InterruptedException x) {
    23.         // Restore interrupt status
    24.         Thread.currentThread().interrupt();
    25.     }
    26.     synchronized (Bits.class) {//休眠100毫秒后,增加直接内存引用的计数
    27.         if (totalCapacity + cap > maxMemory)
    28.             throw new OutOfMemoryError("Direct buffer memory");
    29.         reservedMemory += size;
    30.         totalCapacity += cap;
    31.         count++;
    32.     }
    33.  
    34. }
    35. //释放内存时,减少引用直接内存的计数
    36. static synchronized void unreserveMemory(long size, int cap) {
    37.     if (reservedMemory > 0) {
    38.         reservedMemory -= size;
    39.         totalCapacity -= cap;
    40.         count--;
    41.         assert (reservedMemory > -1);
    42.     }
    43. }

    通过上面代码的分析,我们事实上可以认为Bits类是直接内存的分配担保,当有足够的直接内存可以用时,增加直接内存应用计数,否则,调用System.gc,进行垃圾回收,需要注意的是,System.gc只会回收堆内存中的对象,但是我们前面已经讲过,DirectByteBuffer对象被回收时,那么其引用的直接内存也会被回收,试想现在刚好有其他的DirectByteBuffer可以被回收,那么其被回收的直接内存就可以用于本次DirectByteBuffer直接的内存的分配。

    因此我们经常看到,有一些文章讲解在使用Nio的时候,不要禁用System.gc,也就是启动JVM的时候,不要传入-XX:+DisableExplicitGC参数,因为这样可能会造成直接内存溢出。道理很明显,因为直接内存的释放与获取比堆内存更加耗时,每次创建DirectByteBuffer实例分配直接内存的时候,都调用System.gc,可以让已经使用完的DirectByteBuffer得到及时的回收。

    虽然System.gc只是建议JVM去垃圾回收,可能JVM并不会立即回收,但是频繁的建议,JVM总不会视而不见。

    不过,这并不是绝对的,因为System.gc导致的是FullGC,可能会暂停用户线程,也就是JVM不能继续响应用户的请求,对于一些要求延时比较短的应用,是不希望JVM频繁的进行FullGC的。

    所以笔者的建议是:禁用System.gc,调大最大可以使用的直接内存。如:

    -XX:+DisableExplicitGC -XX:MaxDirectMemorySize=256M
     
  • 相关阅读:
    Validation failed for one or more entities. See 'EntityValidationErrors' property for more details
    Visual Studio断点调试, 无法监视变量, 提示无法计算表达式
    ASP.NET MVC中MaxLength特性设置无效
    项目从.NET 4.5迁移到.NET 4.0遇到的问题
    发布网站时应该把debug设置false
    什么时候用var关键字
    扩展方法略好于帮助方法
    在基类构造器中调用虚方法需谨慎
    ASP.NET MVC中商品模块小样
    ASP.NET MVC中实现属性和属性值的组合,即笛卡尔乘积02, 在界面实现
  • 原文地址:https://www.cnblogs.com/exmyth/p/14188683.html
Copyright © 2011-2022 走看看