zoukankan      html  css  js  c++  java
  • netty UnpooledHeapByteBuf 源码分析

    UnpooledHeapByteBuf 是基于堆内存进行内存分配的字节缓冲区,没有基于对象池技术实现,这意味着每次I/O的读写都会创建一个新的UnpooledHeapByteBuf,频繁进行大块内存的分配和回收对性能会造成一定的影响,但是对比与堆外内存的申请和释放,它的成本会低一些。

    相对与PooledHeapByteBuf,UnpooledHeapByteBuf 的实现原理更加简单,也不容易出现内存管理方面的问题,在满足性能的情况下,尽量使用UnpooledHeapByteBuf 。

    1.成员变量

    private final ByteBufAllocator alloc;//聚合一个ByteBufAllocator,用于UnpooledHeapByteBuf的内存分配
    private byte[] array;//byte 数组作为缓冲区
    private ByteBuffer tmpNioBuf;//用于实现Netty ByteBuf 到 JDK ByteBuffer 的转换

    事实上,如果使用JDK 的ByteBuffer替换byte数组也是可行的 ,直接使用byte数组的根本原因是提升性能和更加便捷的进行位操作。JDK 的ByteBuffer底层实现也是byte数组,如下。

    public abstract class ByteBuffer
        extends Buffer
        implements Comparable<ByteBuffer>{
    
        // These fields are declared here rather than in Heap-X-Buffer in order to
        // reduce the number of virtual method invocations needed to access these
        // values, which is especially costly when coding small buffers.
        //
        final byte[] hb;                  // Non-null only for heap buffers
        final int offset;
        boolean isReadOnly;                 // Valid only for heap buffers

    2.动态扩展缓冲区

    @Override
    public ByteBuf capacity(int newCapacity) {
        ensureAccessible();
        if (newCapacity < 0 || newCapacity > maxCapacity()) {
            throw new IllegalArgumentException("newCapacity: " + newCapacity);
        }
    
        int oldCapacity = array.length;
        if (newCapacity > oldCapacity) {
            byte[] newArray = new byte[newCapacity];
            System.arraycopy(array, 0, newArray, 0, array.length);
            setArray(newArray);
        } else if (newCapacity < oldCapacity) {
            byte[] newArray = new byte[newCapacity];
            int readerIndex = readerIndex();
            if (readerIndex < newCapacity) {
                int writerIndex = writerIndex();
                if (writerIndex > newCapacity) {
                    writerIndex(writerIndex = newCapacity);
                }
                System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);
            } else {
                setIndex(newCapacity, newCapacity);
            }
            setArray(newArray);
        }
        return this;
    }
    /**
     * Should be called by every method that tries to access the buffers content to check
     * if the buffer was released before.
     */
    protected final void ensureAccessible() {
        if (refCnt() == 0) {
            throw new IllegalReferenceCountException(0);
        }
    }

    方法 的入口首先对新容量进行合法性校验,如果大于容量上限或者小于0,则抛出IllegalArgumentException异常。

    判断新的容量值是否大于当前的缓冲区容量,如果大于则需要动态扩展,通过 byte[] newArray = new byte[newCapacity];创建新的缓冲区字节数组,然后通过 System.arraycopy进行内存复制,将旧的字节数组复制到新创建的字节数组中,最后调用setArray(newArray);替换旧的字节数组。

    private void setArray(byte[] initialArray) {
        array = initialArray;
        tmpNioBuf = null;
    }

    动态扩容完成后,需要将原来的视图tmpNioBuf设置为空。

    如果新的容量小于当前缓冲区容量不需要动态扩展,但是需要截取当前缓冲区创建一个新的子缓冲区。先判断下读索引是否小于新的容量值,如果小于进一步判断写索引是否大于新的容量值,如果大于则将写索引设置为新的容量值(防止越界)。更新完写索引之后,通过 System.arraycopy将当前可读的字节数组复制到新创建的子缓冲区中, System.arraycopy(array, readerIndex, newArray, readerIndex, writerIndex - readerIndex);

    如果新的容量值小于读索引,说明没有可读的字节数组需要复制到新创建的缓冲区中,将读写索引为新的容量值即可。最后调用setArray方法替换原来的字节数组。

    3.字节数组复制

    @Override
    public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
        checkSrcIndex(index, length, srcIndex, src.length);
        System.arraycopy(src, srcIndex, array, index, length);
        return this;
    }

    首先做合法性校验。

    protected final void checkSrcIndex(int index, int length, int srcIndex, int srcCapacity) {
        checkIndex(index, length);
        if (srcIndex < 0 || srcIndex > srcCapacity - length) {
            throw new IndexOutOfBoundsException(String.format(
                    "srcIndex: %d, length: %d (expected: range(0, %d))", srcIndex, length, srcCapacity));
        }
    }

    校验index,length的值,如果小于0,抛出IllegalArgumentException异常,然后对两者之和进行判断,如果大于缓冲区的容量,则抛出IndexOutOfBoundsException异常。srcIndex和srcCapacity,与之类似。校验通过之后,调用 System.arraycopy(src, srcIndex, array, index, length)方法进行字节数组的复制。

    protected final void checkIndex(int index, int fieldLength) {
        ensureAccessible();
        if (fieldLength < 0) {
            throw new IllegalArgumentException("length: " + fieldLength + " (expected: >= 0)");
        }
        if (index < 0 || index > capacity() - fieldLength) {
            throw new IndexOutOfBoundsException(String.format(
                    "index: %d, length: %d (expected: range(0, %d))", index, fieldLength, capacity()));
        }
    }

    注意: ButeBuf以set和get开头读写缓冲区的方法不会修改读写索引。

    4.转换为JDK ByteBuffer

    ByteBuffer 是基于byte数组实现,NIO的ByteBuffer提供wrap方法,可以将byte数组转换成ByteBuffer对象。

    /**
         * Wraps a byte array into a buffer.
         *
         * <p> The new buffer will be backed by the given byte array;
         * that is, modifications to the buffer will cause the array to be modified
         * and vice versa.  The new buffer's capacity will be
         * <tt>array.length</tt>, its position will be <tt>offset</tt>, its limit
         * will be <tt>offset + length</tt>, and its mark will be undefined.  Its
         * {@link #array backing array} will be the given array, and
         * its {@link #arrayOffset array offset} will be zero.  </p>
         *
         * @param  array
         *         The array that will back the new buffer
         *
         * @param  offset
         *         The offset of the subarray to be used; must be non-negative and
         *         no larger than <tt>array.length</tt>.  The new buffer's position
         *         will be set to this value.
         *
         * @param  length
         *         The length of the subarray to be used;
         *         must be non-negative and no larger than
         *         <tt>array.length - offset</tt>.
         *         The new buffer's limit will be set to <tt>offset + length</tt>.
         *
         * @return  The new byte buffer
         *
         * @throws  IndexOutOfBoundsException
         *          If the preconditions on the <tt>offset</tt> and <tt>length</tt>
         *          parameters do not hold
         */
        public static ByteBuffer wrap(byte[] array,
                                        int offset, int length)
        {
            try {
                return new HeapByteBuffer(array, offset, length);
            } catch (IllegalArgumentException x) {
                throw new IndexOutOfBoundsException();
            }
        }

    UnpooledHeapByteBuf.java

    @Override
    public ByteBuffer nioBuffer(int index, int length) {
        ensureAccessible();
        return ByteBuffer.wrap(array, index, length).slice();
    }

    调用了ByteBuffer的slice方法,由于每次调用nioBuffer都会创建一个新的ByteBuffer,因此此处的slice方法起不到重用缓冲区的效果,只能保证读写索引的独立性。

    5.与子类相关的方法

    isDirect方法:如果基础堆内存实现的ByteBuf,返回false,

    @Override
    public boolean isDirect() {
        return false;
    }

    hasArray方法:因为UnpooledHeapByteBuf是基于字节数组实现,返回true

    @Override
    public boolean hasArray() {
        return true;
    }

    array方法:因为UnpooledHeapByteBuf是基于字节数组实现,所以返回值是内部的字节数组成员变量。调用array方法之前,可以先使用hasArray方法进行判断,如果返回false说明当前ByteBuf不支持array方法。

    @Override
    public byte[] array() {
        ensureAccessible();
        return array;
    }
  • 相关阅读:
    Java 应用性能调优实践
    Java序列化——实现Serializbale接口
    Bootstrap3快速使用
    C语言程序设计_zju——记录1
    《图解密码技术》——记录1
    电脑win10蓝屏,INACCESSIBLE BOOT DEVICE,处理方法
    读书笔记之《如何高效学习》
    读书笔记之《番茄工作法图解》
    读书笔记之《富爸爸与穷爸爸》
    读书笔记之《微习惯》
  • 原文地址:https://www.cnblogs.com/zwb1234/p/9577757.html
Copyright © 2011-2022 走看看