Buffer详解
java中的nio由channel,buffer,Selector组成核心API。
Buffer是一个继承于object的抽象类,主要有下面几种。
- ByteBuffer
- CharBuffer
- DoubleBuffer
- FloatBuffer
- IntBuffer
- LongBuffer
- ShortBuffer
基本覆盖了基本类型的支持,看下byteBuffer的实现
1 2 3 4 5 6
|
ByteBuffer bf1=ByteBuffer.allocate(16);
ByteBuffer bf2=ByteBuffer.allocateDirect(16);
|
ByteBuffer提供两种方式来获取缓存区内存,间接或者直接。
字节缓冲区要么是直接的,要么是非直接的。
如果为直接字节缓冲区,则 Java 虚拟机会尽最大努力直接在此缓冲区上执行本机 I/O 操作。
也就是说,在每次调用基础操作系统的一个本机 I/O 操作之前(或之后),
虚拟机都会尽量避免将缓冲区的内容复制到中间缓冲区中(或从中间缓冲区中复制内容)。
直接缓冲区的内容可以驻留在常规的垃圾回收堆之外,不会被GC回收。
间接缓冲区与本地操作系统低层次的i/o交互需要一个中间缓冲区来实现,因此性能比直接缓存区要低。
直接缓冲区通过sun.misc.Unsafe类调用jni本地方法来分配内存。
因为绕过jvm,因此分配内存的开销较大,并且需要用户自己当成普通资源来主动释放。
堆外内存的分配
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| DirectByteBuffer(int cap) { 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)) {
address = base + ps - (base & (ps - 1)); } else { address = base; } cleaner = Cleaner.create(this, new Deallocator(base, size, cap)); att = null; }
|
可以看出通过unsafe.allocateMemory方法来分配堆外内存,同时创建一个对应的Cleaner
sun.misc.Cleaner.create方法两个参数。
- 需要监控的堆内存对象,就是堆外内存关联的对象。
- 程序释放资源的回调。
当JVM进行GC的时候,如果发现我们监控的对象,不存在强引用了,(Cleaner本身是幽灵引用),
就会调用预先绑定的回调方法,我们一般在回调方法里面释放堆外内存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| private static class Deallocator implements Runnable { private static Unsafe unsafe = Unsafe.getUnsafe(); private long address; private long size; private int capacity; private Deallocator(long address, long size, int capacity) { assert (address != 0); this.address = address; this.size = size; this.capacity = capacity; } public void run() { if (address == 0) {
return; } unsafe.freeMemory(address); address = 0; Bits.unreserveMemory(size, capacity); } }
|
最终通过unsafe类提供freeMemory来释放之前分配的对外内存。
堆外内存大小可以通过jvm启动参数限制: -XX:MaxDirectMemorySize