本文主要解析一下JDK的rt包下IOUtil类的read()方法的源码实现,JDK版本是1.8;
首先贴出源码
static int read(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
if (var1.isReadOnly()) {
throw new IllegalArgumentException("Read-only buffer");
} else if (var1 instanceof DirectBuffer) {
return readIntoNativeBuffer(var0, var1, var2, var4);
} else {
ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());
int var7;
try {
int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
var5.flip();
if (var6 > 0) {
var1.put(var5);
}
var7 = var6;
} finally {
Util.offerFirstTemporaryDirectBuffer(var5);
}
return var7;
}
}
这个方法的主要功能是将数据读取到Buffer(源码中参数var1)中。
首先第一段
if (var1.isReadOnly()) {
throw new IllegalArgumentException("Read-only buffer");
}
上述代码是在判断用来接收数据的buffer是不是只读Buffer,如果是只读则抛异常出去,因为只读的buffer无法写入数据。
} else if (var1 instanceof DirectBuffer) {
return readIntoNativeBuffer(var0, var1, var2, var4);
} else {
ByteBuffer var5 = Util.getTemporaryDirectBuffer(var1.remaining());
int var7;
try {
int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
var5.flip();
if (var6 > 0) {
var1.put(var5);
}
var7 = var6;
} finally {
Util.offerFirstTemporaryDirectBuffer(var5);
}
return var7;
}
readIntoNativeBuffer()方法内部是调用原生(native)的read()方法; 上述代码的核心就是判断接收数据的buffer是不是DirectBuffer,如果是DirectorBuffer,则直接调用 readIntoNativeBuffer()方法读取数据,如果不是,则先创建一个跟 var1可用空间一样大的DirectorBuffer (源码中的变量var5),然后再去调用readIntoNativeBuffer()方法,将数据读取到新创建的DirectorBuffer var5中,最后将var5中的数据put到var1中,整个操作结束;
那么问题来了,DirectorBuffer是什么,为什么要区分DirectorBuffer?
DirectorBuffer代表一块分配在堆外空间的缓冲区,与之相对的是HeapBuffer,HeapBuffer是分配在堆内的缓冲区。
以下详细介绍DirectorBuffer 首先,DirectorBuffer在java中的定义如下:
public interface DirectBuffer {
long address();
Object attachment();
Cleaner cleaner();
}
方法 long address(); 返回的是一个堆外空间的地址,该地址在DirectorBuffer的实现类DirectorByteBuffer中被赋值,如下所示,DirectorByteBuffer的构造函数
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
上述代码描述的一个DirectorByteBuffer在初始化时的赋值过程,我们主要关注的是address的赋值操作,在该构造方法中调用了unsafe.allocateMemory()方法,这个方法就是用来直接分配堆外内存的。
也就是说,在java中,作为DirectorBuffer的具体实现类,DirectorByteBuffer是一个对象,位于java堆内空间,但是DirectorByteBuffer有一个成员变量address,该变量指向一个堆外空间的地址。
再来看为什么要用DirectorBuffer.
操作系统在底层给缓冲区读取数据的时候,是不允许缓冲区被重新分配或者释放的,如果缓冲区被分配到堆内空间,则有可能被JVM的GC移动, 而分配在堆外的缓冲区是不会被GC进管理和回收的,这也是此处使用directorBuffer的一个原因。 也正因为如此,如果缓冲区不是DirectorBuffer,则会新建一个DirectorBuffer,并把数据写入该buffer,然后再写到我们传入的buffer中; 也就是说,如果我们在实际使用中,传入的是非DirectorBuffer,底层的IO操作会多一次数据的拷贝,降低了效率;
使用DirectorBuffer还有一个原因,来看其实现类DirectorByteBuffer的继承关系:
class DirectByteBuffer extends MappedByteBuffer implements DirectBuffer
{
...
}
我们可以看到,DiretorByteBuffer除了实现DirectorBuffer之外,还继承了一个MappedByteBuffer;
MappedByteBuffer也是jdk提供的一个Buffer,其官方描述是
A direct byte buffer whose content is a memory-mapped region of a file
翻译过来是一种直接字节缓冲区,其内容是文件的内存映射区域;
这里涉及到了一种文件读取的方式mmap(零拷贝),mmap的含义简单描述就是说,通过虚拟地址映射的技术,在内存中划出一块区域,同时将一个存储在磁盘中的文件所在的地址,直接映射到这块区域中,这样程序就可以像读取内存中的文件一样,直接读取磁盘中的文件内容。
当程序读取到这块内存区域时,发现还没有文件的内容,则会触发一次缺页中断,然后从磁盘中读取该文件内容到该区域中。
从上述描述中可以看出,采用mmap技术后,程序从磁盘读取文件只需要一次IO操作,即直接将文件内容从磁盘中读取到虚拟地址对应的内存区域中。
这块区域就是DirectorBuffer中的堆外内存。
也就是说,使用DirectorBuffer有两个好处:
1.从Java JDK层面而言,减少了一次从DirectorBuffer拷贝到非DirectorBuffer的操作,因为如果是非DirectorBuffer,jdk默认先创建 一个DirectorBuffer,然后读取数据,再从DirectorBuffer中拷贝数据到真正的Buffer。
2.从操作系统层面,DirectorBuffer采用了mmap技术,从磁盘读取数据到内存只需要一次IO操作;
以上是对DirectorBuffer的解释;
最后IOUtil.read()方法中,最后还有一点:
int var6 = readIntoNativeBuffer(var0, var5, var2, var4);
var5.flip()
if (var6 > 0) {
var1.put(var5);
}
var5代表那个创建的DirectorBuffer,该段代码的含义是,当数据已经读取到var5 后,将var5锁定,然后调用var1的put()方法将var5中的数据写入到var1中。
flip()方法会将directorBuffer锁定,锁定后只允许读,不允许写。