byte buffer通常在网络交互过程当中java使用得比较多,尤为是以NIO的框架中;java
看名字就知道是以字节码做为缓冲的,先buffer一段,而后flush到终端。linux
而本文要说的一个重点就是HeapByteBuffer与DirectByteBuffer,以及如何合理使用DirectByteBuffer。程序员
一、HeapByteBuffer与DirectByteBuffer,在原理上,前者能够看出分配的buffer是在heap区域的,其实真正flush到远程的时候会先拷贝获得直接内存,再作下一步操做(考虑细节还会到OS级别的内核区直接内存),其实发送静态文件最快速的方法是经过OS级别的send_file,只会通过OS一个内核拷贝,而不会来回拷贝;在NIO的框架下,不少框架会采用DirectByteBuffer来操做,这样分配的内存再也不是在java heap上,而是在C heap上,通过性能测试,能够获得很是快速的网络交互,在大量的网络交互下,通常速度会比HeapByteBuffer要快速好几倍。windows
最基本的状况下安全
分配HeapByteBuffer的方法是:网络
[java] view plaincopy框架
ByteBuffer.allocate(int capacity);参数大小为字节的数量 性能
分配DirectByteBuffer的方法是:测试
[java] view plaincopy优化
ByteBuffer.allocateDirect(int capacity);//能够看到分配内存是经过unsafe.allocateMemory()来实现的,这个unsafe默认状况下java代码是没有能力能够调用到的,不过你能够经过反射的手段获得实例进而作操做,固然你须要保证的是程序的稳定性,既然叫unsafe的,就是告诉你这不是安全的,其实并非不安全,而是交给程序员来操做,它可能会由于程序员的能力而致使不安全,而并不是它自己不安全。
因为HeapByteBuffer和DirectByteBuffer类都是default类型的,因此你没法字节访问到,你只能经过ByteBuffer间接访问到它,由于JVM不想让你访问到它,对了,JVM不想让你访问到它确定就有它不可告人的秘密;后面咱们来跟踪下他的秘密吧。
二、前面说到了,这块区域不是在java heap上,那么这块内存的大小是多少呢?默认是通常是64M,能够经过参数:-XX:MaxDirectMemorySize来控制,你够牛的话,还能够用代码控制,呵呵,这里就很少说了。
三、直接内存好,咱们为啥不都用直接内存?请注意,这个直接内存的释放并非由你控制的,而是由full gc来控制的,直接内存会本身检测状况而调用system.gc(),可是若是参数中使用了DisableExplicitGC 那么这是个坑了,因此啊,这玩意,设置不设置都是一个坑坑,因此java的优化有没有绝对的,只有针对实际状况的,针对实际状况须要对系统作一些拆分作不一样的优化。
四、那么full gc不触发,我想本身释放这部份内存有方法吗?能够的,在这里没有什么是不能够的,呵呵!私有属性咱们都任意玩他,还有什么不能够玩的;咱们看看它的源码中DirectByteBuffer发现有一个:Cleaner,貌似是用来搞资源回收的,通过查证,的确是,并且又看到这个对象是sun.misc开头的了,此时既惊喜又郁闷,呵呵,只要我能拿到它,我就能有但愿消灭掉了;下面第五步咱们来作个试验。
五、由于咱们的代码全是私有的,因此我要访问它不能直接访问,我须要经过反射来实现,OK,我知道要调用cleaner()方法来获取它Cleaner对象,进而经过该对象,执行clean方法;(付:如下代码大部分也取自网络上的一篇copy无数次的代码,可是那个代码是有问题的,有问题的部分,我将用红色标识出来,若是没有哪条代码是没法运行的)
[java] view plaincopy
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();
}
}
}
上述类你能够在任何位置创建均可以,这里多谢一楼的回复,之前个人写法是见到DirectByteBuffer类是Default类型的,所以这个类没法直接引用到,是经过反射去找到cleaner的实例,进而调用内部的clean方法,那样作麻烦了,其实并不须要那么麻烦,由于DirectByteBuffer implements了DirectBuffer,而DirectBuffer自己是public的,因此经过接口去调用内部的Clear对象来作clean方法。
咱们下面来作测试来证实这个程序是有效地回收的:
在任意一个地方写一段main方法来调用,我这里就直接写在这个类里面了:
[java] view plaincopy
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);
}
这里分配了100M内存,为了将结果看清楚,在执行前,执行后分别看看延迟10s,固然你能够根据你的要求本身改改。请提早将OS的资源管理器打开,看看当前使用的内存是多少,若是你是linux固然是看看free或者用top等命令来看;本地程序我是用windows完成,在运行前机器的内存以下图所示:
开始运行在输入start后,可是未输出end前,内存直接上升将近100m。
在输入end后发现内存当即下降到2.47m,说明回收是有效的。
此时能够观察JVM堆的内存,不会有太多的变化,注意:JVM自己启动后也有一些内存开销,因此不要将那个开销和这个绑定在一块儿;这里之因此一次性申请100m也是为了看清楚过程,其他的能够作实验玩玩了。