ByteBuffer 提供了两种方式创建缓冲区,一个是在heap内分配即DirectBuffer,一个是在heap外分配。一个比较普遍的说法是,heap外分配的内存不太好把控,JVM不会回收其内存。然而事实上真的如此吗?我做了一个实验,我写了一个NIO服务端程序不断读取客户端发送过来的内容,然后回写到客户端。回写的时候用了DirectBuffer, 并且开辟的内存故意设的比较大128M。请看如下服务器代码片段。
/**
* 处理读取客户端发来的信息 的事件
* @param key
* @throws IOException
*/
public void read(SelectionKey key) throws IOException{
// 服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(256);
// ByteBuffer buffer =ByteBuffer.allocateDirect(1024*1024);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服务端收到信息:"+msg);
//ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
ByteBuffer buffer2 =ByteBuffer.allocateDirect(1024*1024*128);
buffer2.put(msg.getBytes());
channel.write(buffer2);// 将消息回送给客户端
System.out.println(buffer2.getClass().getName());
}
运行服务端程序后我们观察内存变化情况。我们先看看程序运行前CPU跟内存情况。
如红色标记出来的片段显示,内存非常平稳,cpu也几乎闲着。运行代码后,我们再观察
观察发现内存有了波动,但是基本稳定在2.91GB,比运行前增加的内存与direct buffer分配的内存大致相当。每次在内存波动比较大的情况下,CPU都有个冲浪,然后内存又被压下来,说明在回收内存,而并没有出现内存泄漏。因此JVM会做DirectBuffer的垃圾回收。
如果对JVM不放心,那我们可自己回收吗? 答案也是 "Big Yes"
我将代码增加清除的逻辑,并在分配内存之后睡眠10秒,然后再清除方便观察内存情况。发现分配后内存每次都会增加,但是清除之后立马内存就降下来了,请看代码不再贴图。有人会觉得奇怪这里为什么用反射去调用方法,原因是不同jre返回的class不一样,但是都提供了clean方法。
public void read(SelectionKey key) throws IOException{
// 服务器可读取消息:得到事件发生的Socket通道
SocketChannel channel = (SocketChannel) key.channel();
// 创建读取的缓冲区
ByteBuffer buffer = ByteBuffer.allocate(256);
// ByteBuffer buffer =ByteBuffer.allocateDirect(1024*1024);
channel.read(buffer);
byte[] data = buffer.array();
String msg = new String(data).trim();
System.out.println("服务端收到信息:"+msg);
//ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
ByteBuffer buffer2 =ByteBuffer.allocateDirect(1024*1024*128);
buffer2.put(msg.getBytes());
channel.write(buffer2);// 将消息回送给客户端
clean(buffer);
}
public void clean(ByteBuffer buffer){
try {
System.out.println("buffer已分配,睡10秒钟,请注意观察内存变化,,,");
Thread.sleep(10000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
try {
Method cleanerMethod = buffer.getClass().getMethod("cleaner");
cleanerMethod.setAccessible(true);
Object cleaner = cleanerMethod.invoke(buffer);
Method cleanMethod = cleaner.getClass().getMethod("clean");
cleanMethod.setAccessible(true);
cleanMethod.invoke(cleaner);
Thread.sleep(3000);
} catch (IllegalAccessException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (IllegalArgumentException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
既然DirectBuffer效率也高,回收也没问题,那为何不用DirectBuffer呢? 答案是,DirectBuffer的创建跟回收会比Heap的开销大,对于字节数不多的通信,两者甚至heap的缓冲效率略高,如果DirectBuffer会持续长时间或者会被重用,那用DirectBuffer会是不错的选择。因此说大文件的传输用DirectBuffer是比较合适的。