您当前的位置: 首页 >  nio

恐龙弟旺仔

暂无认证

  • 0浏览

    0关注

    282博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

NIO源码解析-HeapBuffer与DirectBuffer

恐龙弟旺仔 发布时间:2021-09-06 21:39:28 ,浏览量:0

前言:
        在Buffer这篇博客中,笔者在测试ByteBuffer API时,一直在使用HeapByteBuffer在测试。实际ByteBuffer作为一个抽象类,还有其他实现类。如下图所示:

    本文中会重点介绍其四种实现类,HeapByteBuffer、HeapByteBufferR、DirectByteBuffer、DirectByteBufferR。
    而关于MappedByteBuffer,后续会单独列一篇博客来介绍。
1. HeapByteBuffer
        Heap代表堆空间,顾名思义,这个Buffer是分配在JVM堆上的,该区域受JVM管理,回收也由GC来负责。
        通过查看其源码可以看到其分配操作过程
class HeapByteBuffer
    extends ByteBuffer
{
    // 构造方法
    protected HeapByteBuffer(byte[] buf,
                                   int mark, int pos, int lim, int cap,
                                   int off)
    {
        super(mark, pos, lim, cap, buf, off);
    }
}

// ByteBuffer
public abstract class ByteBuffer
    extends Buffer
    implements Comparable
{
    final byte[] hb;                  // Non-null only for heap buffers
    final int offset;
    boolean isReadOnly;                 // Valid only for heap buffers

    ByteBuffer(int mark, int pos, int lim, int cap,   // package-private
                 byte[] hb, int offset)
    {
        super(mark, pos, lim, cap);
        this.hb = hb;
        this.offset = offset;
    }
}
        通过源码可以很清楚的看到,HeapByteBuffer本质上是一个字节数组,通过position、limit的控制,来反复的操作该字节数组。
    
2.HeapByteBufferR
        那么该类与HeapByteBuffer有什么区别呢?直接看源码
class HeapByteBufferR
    extends HeapByteBuffer
{
    protected HeapByteBufferR(byte[] buf,
                                   int mark, int pos, int lim, int cap,
                                   int off)
    {
        // 直接调用HeapByteBuffer的赋值方法
        super(buf, mark, pos, lim, cap, off);
        // 设置为只读
        this.isReadOnly = true;
    }
}
        可以看到,该类与HeapByteBuffer几乎没有区别,除了属性isReadOnly之外,该属性是属于ByteBuffer的属性。
        那么设置isReadOnly=true,将HeapByteBufferR设置为只读后,具体有哪些限制呢?继续看源码
    public ByteBuffer put(byte x) {
        throw new ReadOnlyBufferException();
    }

    public ByteBuffer put(int i, byte x) {
        throw new ReadOnlyBufferException();
    }
...
        所有的put方法都抛出了异常,不允许对数组中的值进行添加操作了。
        那么问题来了,既然不允许以put方式来对HeapByteBufferR进行赋值操作,那要怎样才能赋值呢,看下面的示例
ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put("risk".getBytes());

// 直接对原HeapByteBuffer进行操作,会生成一个与原HeapByteBuffer一样的buffer,且只读
ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
总结:每一个类型的ByteBuffer都有只读和非只读类型的实现类,只读实现类默认以R结尾。
3. DirectByteBuffer
        字面意思是直接缓冲区。何为直接缓冲呢?
        实际就是堆外内存,该内存块不属于JVM的Heap堆,而是操作系统的其他内存块(本质上就是C语言用malloc进行分配所得到的内存块)。
        通过源码我们可以看到其与HeapByteBuffer分配时的不同
// ByteBuffer
public static ByteBuffer allocateDirect(int capacity) {
    return new DirectByteBuffer(capacity);
}

// DirectByteBuffer
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 {
        // 通过unsafe来分配内存
        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来获取内存地址引用
        address = base;
    }
    cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
    att = null;
}

// Unsafe
public native long allocateMemory(long var1);
    Unsafe.allocateMemory是一个native方法,会调用到本操作系统相关的实现方法。(也就是上述所说的,C语言直接调用malloc创建的内存块)。而关于其put get等方法,都是通过Unsafe来控制的
// DirectByteBuffer
public ByteBuffer put(byte x) {
    unsafe.putByte(ix(nextPutIndex()), ((x)));
    return this;
}

public byte get() {
    return ((unsafe.getByte(ix(nextGetIndex()))));
}
4.DirectByteBuffer与HeapByteBuffer异同

    Q:既然我们已经有了HeapByteBuffer,那为什么还需要DirectByteBuffer呢?

    A:是由于操作系统没法直接访问JVM内存。

    细细想来,这个答案有明显的不合理处,操作系统作为底层设施,所有的进程都运行在其上,内存都由其来分配,怎么可能无法操作JVM的内存呢?

    理论上来说,操作系统是可以访问JVM内存空间的,但是由于JVM需要进行GC,如果当IO设备直接和JVM堆内存数据直接交互,此时JVM进行了GC操作,原来IO设备操作的字节被移动到其他区域,那IO设备便无法正确的获取到该字节数据。

    而DirectByteBuffer,是由操作系统直接分配的,位置不会变动,是可以与IO设置直接进行交互的。所以实际上当IO设备与HeapByteBuffer进行交互时,会先将HeapByteBuffer中的数据临时拷贝到DirectByteBuffer(临时创建的,使用后销毁),然后再从DirectByteBuffer拷贝到IO设置内存空间(一般就是内核空间)

    1)源码释疑

    我们可以通过源码来验证上面这段话的正确性(会涉及到后面Channel的知识点)

// 我们通过FileChannel打开一个文件,然后将HeapByteBuffer中的数据写入到该文件中
RandomAccessFile file = new RandomAccessFile(new File("C:\\Users\\lucky\\Desktop\\filetest.txt"), "rwd");
FileChannel channel = file.getChannel();

ByteBuffer buffer = ByteBuffer.allocate(10);
buffer.put((byte) 'h').put((byte) 'e').put((byte) 'l').put((byte) 'l').put((byte) 'o');

buffer.position(0);
// 通过channel将buffer中的数据写入
channel.write(buffer);

    观察FileChannel.write

// FileChannelImpl
public int write(ByteBuffer var1) throws IOException {
        this.ensureOpen();
        if (!this.writable) {
            throw new NonWritableChannelException();
        } else {
            synchronized(this.positionLock) {
                int var3 = 0;
                int var4 = -1;

                try {
                    this.begin();
                    var4 = this.threads.add();
                    if (!this.isOpen()) {
                        byte var12 = 0;
                        return var12;
                    } else {
                        do {
                            // 通过IOUtil来写入
                            var3 = IOUtil.write(this.fd, var1, -1L, this.nd);
                        } while(var3 == -3 && this.isOpen());

                        int var5 = IOStatus.normalize(var3);
                        return var5;
                    }
                } finally {
                    this.threads.remove(var4);
                    this.end(var3 > 0);

                    assert IOStatus.check(var3);

                }
            }
        }
    }

// IOUtil.java
static int write(FileDescriptor var0, ByteBuffer var1, long var2, NativeDispatcher var4) throws IOException {
        if (var1 instanceof DirectBuffer) {
            // 如果使用的就是DirectByteBuffer,则直接写入
            return writeFromNativeBuffer(var0, var1, var2, var4);
        } else {
            int var5 = var1.position();
            int var6 = var1.limit();

            assert var5             
关注
打赏
1655041699
查看更多评论
0.0743s