java之HeapByteBuffer&DirectByteBuffer以及回收DirectByteBuffer

小辣椒 8年前
   <p>byte buffer一般在网络交互过程中java使用得比较多,尤其是以NIO的框架中;</p>    <p>看名字就知道是以字节码作为缓冲的,先buffer一段,然后flush到终端。</p>    <p>而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。</p>    <p>1、 <strong>HeapByteBuffer</strong> 与 <strong>DirectByteBuffer</strong> ,在原理上,前者可以看出分配的buffer是在heap区域的,其实真正flush到远程的时候会先拷贝得到直接内存,再做下一步操作(考虑细节还会到OS级别的内核区直接内存),其实发送静态文件最快速的方法是通过OS级别的 <strong>send_file</strong> ,只会经过OS一个内核拷贝,而不会来回拷贝;在NIO的框架下,很多框架会采用 <strong>DirectByteBuffer</strong> 来操作,这样分配的内存不再是在 <strong>java heap</strong> 上,而是在 <strong>C heap</strong> 上,经过性能测试,可以得到非常快速的网络交互,在大量的网络交互下,一般速度会比HeapByteBuffer要快速好几倍。</p>    <p>最基本的情况下</p>    <p>分配HeapByteBuffer的方法是:</p>    <pre>  <code class="language-java">ByteBuffer.allocate(int capacity);参数大小为字节的数量</code></pre>    <p>分配DirectByteBuffer的方法是:</p>    <pre>  <code class="language-java">ByteBuffer.allocateDirect(int capacity);//可以看到分配内存是通过unsafe.allocateMemory()来实现的,这个unsafe默认情况下java代码是没有能力可以调用到的,不过你可以通过反射的手段得到实例进而做操作,当然你需要保证的是程序的稳定性,既然叫unsafe的,就是告诉你这不是安全的,其实并不是不安全,而是交给程序员来操作,它可能会因为程序员的能力而导致不安全,而并非它本身不安全。</code></pre>    <p>由于 <strong>HeapByteBuffer</strong> 和 <strong>DirectByteBuffer</strong> 类都是default类型的,所以你无法字节访问到,你只能通过ByteBuffer间接访问到它,因为JVM不想让你访问到它,对了,JVM不想让你访问到它肯定就有它不可告人的秘密;后面我们来跟踪下他的秘密吧。</p>    <p>2、前面说到了,这块区域不是在java heap上,那么这块内存的大小是多少呢?默认是一般是64M,可以通过参数: <strong>-XX:MaxDirectMemorySize</strong> 来控制,你够牛的话,还可以用代码控制,呵呵,这里就不多说了。</p>    <p>3、直接内存好,我们为啥不都用直接内存?请注意,这个直接内存的释放并不是由你控制的,而是由full gc来控制的,直接内存会自己检测情况而调用system.gc(),但是如果参数中使用了DisableExplicitGC 那么这是个坑了,所以啊,这玩意,设置不设置都是一个坑坑,所以java的优化有没有绝对的,只有针对实际情况的,针对实际情况需要对系统做一些拆分做不同的优化。</p>    <p>4、那么full gc不触发,我想自己释放这部分内存有方法吗?可以的,在这里没有什么是不可以的,呵呵!私有属性我们都任意玩他,还有什么不可以玩的;我们看看它的源码中 <strong>DirectByteBuffer</strong> 发现有一个:Cleaner,貌似是用来搞资源回收的,经过查证,的确是,而且又看到这个对象是 <strong>sun.misc</strong> 开头的了,此时既惊喜又郁闷,呵呵,只要我能拿到它,我就能有希望消灭掉了;下面第五步我们来做个试验。</p>    <p>5、因为我们的代码全是私有的,所以我要访问它不能直接访问,我需要通过反射来实现,OK,我知道要调用cleaner()方法来获取它Cleaner对象,进而通过该对象,执行clean方法;(付: <em>以下代码大部分也取自网络上的一篇copy无数次的代码,但是那个代码是有问题的,有问题的部分,我将用红色标识出来,如果没有哪条代码是无法运行的</em> )</p>    <pre>  <code class="language-java">import java.nio.ByteBuffer;  import sun.nio.ch.DirectBuffer;    public class DirectByteBufferCleaner {            public static void clean(final ByteBuffer byteBuffer) {                if (byteBuffer.isDirect()) {                   ((DirectBuffer)byteBuffer).cleaner().clean();                }          }  }</code></pre>    <p>上述类你可以在任何位置建立都可以,这里多谢一楼的回复,以前我的写法是见到DirectByteBuffer类是Default类型的,因此这个类无法直接引用到,是通过反射去找到cleaner的实例,进而调用内部的clean方法,那样做麻烦了,其实并不需要那么麻烦,因为DirectByteBuffer implements了DirectBuffer,而DirectBuffer本身是public的,所以通过接口去调用内部的Clear对象来做clean方法。</p>    <p>我们下面来做测试来证明这个程序是有效地回收的:</p>    <p>在任意一个地方写一段main方法来调用,我这里就直接写在这个类里面了:</p>    <pre>  <code class="language-java">public static void sleep(long i) {      try {            Thread.sleep(i);       }catch(Exception e) {            /*skip*/       }  }  public static void main(String []args) throws Exception {         ByteBuffer buffer = ByteBuffer.allocateDirect(1024 * 1024 * 100);         System.out.println("start");         sleep(10000);         clean(buffer);         System.out.println("end");         sleep(10000);  }</code></pre>    <p>这里分配了100M内存,为了将结果看清楚,在执行前,执行后分别看看延迟10s,当然你可以根据你的要求自己改改。请提前将OS的资源管理器打开,看看当前使用的内存是多少,如果你是linux当然是看看free或者用top等命令来看;本地程序我是用windows完成,在运行前机器的内存如下图所示:</p>    <p>开始运行在输入start后,但是未输出end前,内存直接上升将近100m。</p>    <p><img src="https://simg.open-open.com/show/6c5d182c8a43146b344fb36f61aa1c9e.jpg"></p>    <p>在输入end后发现内存立即降低到2.47m,说明回收是有效的。</p>    <p><img src="https://simg.open-open.com/show/d0b8008f7d3ff8d802344d87544a3f43.jpg"></p>    <p>此时可以观察JVM堆的内存,不会有太多的变化,注意:JVM本身启动后也有一些内存开销,所以不要将那个开销和这个绑定在一起;这里之所以一次性申请100m也是为了看清楚过程,其余的可以做实验玩玩了。</p>    <p>来自: <a href="/misc/goto?guid=4959670738939690467" rel="nofollow">http://www.importnew.com/19191.html</a></p>