Netty学习之旅----ByteBuf源码解读之初探UnpooledHeapByteBuf、UnpooledDirectByteBuf

来源:互联网 发布:人工智能下肢辅助支架 编辑:程序博客网 时间:2024/04/28 05:39
前沿:
    在读源码的过程中我发现有如下几个点,感觉有问题:
1)UnpooledDirectByteBuf的 capacity(int newCapacity)方法,在readerIndex大于或等于newCapacity时,此时不需要将原ByteBuffer中的数据写入到新的ByteBuffer中,这可以理解,但为什么要调用setIndex(newCapacity,newCapacity)将readerIndex,writerIndex设置为capacity呢?设置之后,该ByteBuf不能读也不能写。
2)setByteBuffer 方法,在第一次oldBuffer不为空的时候,此时doNotFree为true(在构造方法中设置),为什么第一次oldBuffer不为空的时候,不释放掉该部分内存,不明白。

1、首先,我们先看一下ByteBuf的类设计图,从中更进一步了解ByteBuf。


ByteBuf继承自ReferenceCounted,引用计数,也就是ByteBuf的内存回收使用的是引用计数器来实现。
UnpooledHeapByteBuf是非池化的堆内存实现,而UnpooledDirectByteBuf是非池化的堆外内存(直接内存)。非池化的ByteBuf就是利用完之后就需要销毁,无法重用。
2、UnpooledHeapByteBuf  的继承链   UnpooledHeapByteBuf  --> AbstractReferenceCountedByteBuf-->AbstractByteBuf
2.1、AbstractByteBuf源码分析
AbstractByteBuf定义ByteBuf的基本属性,诸如 readerIndex,writerIndex,markedReaderIndex,markedWriterIndex,maxCapacity,我们知道ByteBuf的容量是可以自动扩容的。
AbstractByteBuf的这两个属性,应该引起我们的注意:
一个是SwappedByteBuf swappedBuf; 这个是大端序列与小端序列的转换。
二个是ResourceLeakDetector<ByteBuf> leakDetector = new ResourceLeakDetector<ByteBuf>(ByteBuf.class);  //这个又是一个很关键点,下篇文章将重点分析,Netty用来解决内存泄漏检测机制。非常重要。
这里截取一下SwappedByteBuf的源码,采用了典型的装饰模式来设计。SwappedByteBuf采用
public class SwappedByteBuf extends ByteBuf {

    private final ByteBuf buf;
    private final ByteOrder order;

    public SwappedByteBuf(ByteBuf buf) {
        if (buf == null) {
            throw new NullPointerException("buf");
        }
        this.buf = buf;
        if (buf.order() == ByteOrder.BIG_ENDIAN) {
            order = ByteOrder.LITTLE_ENDIAN;
        } else {
            order = ByteOrder.BIG_ENDIAN;
        }
    }

    @Override
    public ByteOrder order() {
        return order;
    }

    @Override
    public ByteBuf order(ByteOrder endianness) {
        if (endianness == null) {
            throw new NullPointerException("endianness");
        }
        if (endianness == order) {
            return this;
        }
        return buf;
    }
}
关于其他AbstractByteBuf,该类设计使用了典型的模板模式,对ByteBuf提供的类,实现时,提供一种模板,然后再提供一个钩子方法,供子类实现,比如_getLong方法,_setLong等方法,由于该类的实现原理不复杂,就不做进一步的源码解读。
2.2 AbstractReferenceCountedByteBuf源码实现,该类主要是实现引用计算的常规方法,充分利用voliate内存可见性与CAS操作完成refCnt变量的维护。
其源码实现如下:
package io.netty.buffer;import io.netty.util.IllegalReferenceCountException;import io.netty.util.internal.PlatformDependent;import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;/** * Abstract base class for {@link ByteBuf} implementations that count references. */public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {    private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater;    static {        AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> updater =                PlatformDependent.newAtomicIntegerFieldUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");        if (updater == null) {            updater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");        }        refCntUpdater = updater;    }    private volatile int refCnt = 1;    protected AbstractReferenceCountedByteBuf(int maxCapacity) {        super(maxCapacity);    }    @Override    public final int refCnt() {        return refCnt;    }    /**     * An unsafe operation intended for use by a subclass that sets the reference count of the buffer directly     */    protected final void setRefCnt(int refCnt) {        this.refCnt = refCnt;    }    @Override    public ByteBuf retain() {        for (;;) {            int refCnt = this.refCnt;            if (refCnt == 0) {                throw new IllegalReferenceCountException(0, 1);            }            if (refCnt == Integer.MAX_VALUE) {                throw new IllegalReferenceCountException(Integer.MAX_VALUE, 1);            }            if (refCntUpdater.compareAndSet(this, refCnt, refCnt + 1)) {                break;            }        }        return this;    }    @Override    public ByteBuf retain(int increment) {        if (increment <= 0) {            throw new IllegalArgumentException("increment: " + increment + " (expected: > 0)");        }        for (;;) {            int refCnt = this.refCnt;            if (refCnt == 0) {                throw new IllegalReferenceCountException(0, increment);            }            if (refCnt > Integer.MAX_VALUE - increment) {                throw new IllegalReferenceCountException(refCnt, increment);            }            if (refCntUpdater.compareAndSet(this, refCnt, refCnt + increment)) {                break;            }        }        return this;    }    @Override    public ByteBuf touch() {        return this;    }    @Override    public ByteBuf touch(Object hint) {        return this;    }    @Override    public final boolean release() {        for (;;) {            int refCnt = this.refCnt;            if (refCnt == 0) {                throw new IllegalReferenceCountException(0, -1);            }            if (refCntUpdater.compareAndSet(this, refCnt, refCnt - 1)) {                if (refCnt == 1) {                    deallocate();                    return true;                }                return false;            }        }    }    @Override    public final boolean release(int decrement) {        if (decrement <= 0) {            throw new IllegalArgumentException("decrement: " + decrement + " (expected: > 0)");        }        for (;;) {            int refCnt = this.refCnt;            if (refCnt < decrement) {                throw new IllegalReferenceCountException(refCnt, -decrement);            }            if (refCntUpdater.compareAndSet(this, refCnt, refCnt - decrement)) {                if (refCnt == decrement) {                    deallocate();                    return true;                }                return false;            }        }    }    /**     * Called once {@link #refCnt()} is equals 0.     */    protected abstract void deallocate();}
该类,我们只需要了解,当一个ByteBuf被引用的次数为0时,dealocate()方法将被调用,该方法就是具体回收ByteBuf的操作,由具体的子类去实现。

2.3 UnpooledHeapByteBuf与UnpooledDirectByteBuf源码分析:
首先该类的内部结构如下:



对于非池化的UnpooledByteBuf,内部就是使用array来存储数据,相对简单,所以源码分析,我还是侧重于UnpooledDirectByteBuf。
1、容量的扩容
2、内存的分配
2.3.1 我们重点关注一下capacity(int newCapacity)方法
public ByteBuf capacity(int newCapacity) {        ensureAccessible();     // @1         if (newCapacity < 0 || newCapacity > maxCapacity()) {            throw new IllegalArgumentException("newCapacity: " + newCapacity);        }        int readerIndex = readerIndex();        int writerIndex = writerIndex();        int oldCapacity = capacity;        if (newCapacity > oldCapacity) {   // @2            ByteBuffer oldBuffer = buffer;            ByteBuffer newBuffer = allocateDirect(newCapacity);  //@21            oldBuffer.position(0).limit(oldBuffer.capacity());          //@22            newBuffer.position(0).limit(oldBuffer.capacity());        //@23            newBuffer.put(oldBuffer);                                            //@24            newBuffer.clear();                                                         //@25            setByteBuffer(newBuffer);                                            //@26        } else if (newCapacity < oldCapacity) { //@3            ByteBuffer oldBuffer = buffer;            ByteBuffer newBuffer = allocateDirect(newCapacity);            if (readerIndex < newCapacity) {                if (writerIndex > newCapacity) {                    writerIndex(writerIndex = newCapacity);                }                oldBuffer.position(readerIndex).limit(writerIndex);                newBuffer.position(readerIndex).limit(writerIndex);                newBuffer.put(oldBuffer);                newBuffer.clear();            } else {                setIndex(newCapacity, newCapacity);            }            setByteBuffer(newBuffer);        }        return this;    }
代码@1,检测一下访问性,可达性,就是引用数必须大于0,否则该ByteBuf的内部空间已经被回收了(堆外内存)
代码@2,扩容操作,思路新建一个缓存区,然后将原先缓存区的数据全部写入到新的缓存区,然后释放旧的缓存区。
代码@21、22,申请一个直接缓存区,然后将原缓冲区的postion设置为0,将limit设置为capacity,处于释放状态(从缓存区读)。
代码@23,将新缓存区的postion,limit属性设置为0,老缓存区limit。
代码@24,将原缓冲区写入到新的缓存区,然后将缓存区置的position设置为0,limt设置为capacity,其实这里设置position,capacity的意义不大,因为ByteBuf并不会利用内部的ByteBuffer的limit,postion属性,而是使用readerIndex,wriateIndex。
代码@26,关联新的ByteBuffer,并释放原缓存区的空间。
代码@3,压缩缓存区。实现思路是新建一个缓存区,如果readerIndex大于新建的ByteBuffer的capacity,则无需将旧的缓存区内容写入到新的缓存区中。如果readerIndex小于新capacity,那需要将readerIndex 至(  Math.min(writerIndex, newCapacity) )直接的内容写入到新的缓存,然后释放旧的缓存区。值得注意一点是,如果readerIndex > newCapcity,该ByteBuf的 readerIndex,writerIndex将会被设置为容量值,意味着如果不对readerIndex设置为0,或调用discardReadBytes,该缓存区是不可以使用的,所以,我不明白这里为什么要这样做,是为了安全?。
我们在重点关注一下setByteBuffer(newBuffer)方法,该方法还负责销毁原先的ByteBuffer。
private void setByteBuffer(ByteBuffer buffer) {        ByteBuffer oldBuffer = this.buffer;        if (oldBuffer != null) {            if (doNotFree) {                doNotFree = false;            } else {                freeDirect(oldBuffer);            }        }        this.buffer = buffer;        tmpNioBuf = null;        capacity = buffer.remaining();    }
释放原先的内存。
2.3.2 内存的分配。
Netty在为内存的分配,单独封装,相关类图:

目前,先关注UnpooledByteBufAllocator,对象池的ByteBuf在后续章节中重点关注。
结合原代码,有如下两个方法引起了我的注意:
1、容量扩容规则(容量增长规则)calculateNewCapacity方法
2、直接内存的分配。newDirectBuffer方法
2.3.2.1 calculateNewCapacity
public int calculateNewCapacity(int minNewCapacity, int maxCapacity) {        if (minNewCapacity < 0) {            throw new IllegalArgumentException("minNewCapacity: " + minNewCapacity + " (expectd: 0+)");        }        if (minNewCapacity > maxCapacity) {            throw new IllegalArgumentException(String.format(                    "minNewCapacity: %d (expected: not greater than maxCapacity(%d)",                    minNewCapacity, maxCapacity));        }        final int threshold = 1048576 * 4; // 4 MiB page        if (minNewCapacity == threshold) {            return threshold;        }        // If over threshold, do not double but just increase by threshold.        if (minNewCapacity > threshold) {               //@1            int newCapacity = minNewCapacity / threshold * threshold;            if (newCapacity > maxCapacity - threshold) {                newCapacity = maxCapacity;            } else {                newCapacity += threshold;            }            return newCapacity;        }        // Not over threshold. Double up to 4 MiB, starting from 64.        int newCapacity = 64;c   // @2        while (newCapacity < minNewCapacity) {            newCapacity <<= 1;        }        return Math.min(newCapacity, maxCapacity);    }
参数:minNewCapacity,本次需要申请的最小内存
参数:macCapacity,最大总内存申请值。
代码@1,如果最小需要的内存超过设置的 threshold(阔值的话),则循环,每次增加threshold,然后看是否达到本次申请目标。
代码@2,如果需要申请的内存小于阔值,则以64个字节以2的幂增长。
这里提现了内存扩容时的一个优化点。
2.3.2.2 newDirectBuffer方法

@Override    protected ByteBuf newDirectBuffer(int initialCapacity, int maxCapacity) {        ByteBuf buf;        if (PlatformDependent.hasUnsafe()) {            buf = new UnpooledUnsafeDirectByteBuf(this, initialCapacity, maxCapacity);        } else {            buf = new UnpooledDirectByteBuf(this, initialCapacity, maxCapacity);        }        return toLeakAwareBuffer(buf);    }

该方法中,除了见到申请一个直接内存外,还将该buf 变成一个可感知的对象 toLeakAwareBuffer方法,用于该对象被引用的情况,因为UnpooledDirectByteBuf是一个聚合对象,内部维护了一个java.nio.ByteBuffer的直接对外内存空间,在什么是释放UnpooledDirectByteBuf中的堆外内存呢?在UnpooledDirectByteBuf被java垃圾回收的时候,应该于此同时需要释放指向的堆外内存,但堆外内存不受JVM GC的管理,所以我们只有感知到UnpooledDirectByteBuf被JVM虚拟机回收后,手动去释放堆外内存,大家想想都知道,我们可以通过JAVA提供的引用机制,来实现跟踪垃圾回收器的收集工作,虚引用的作用来了,下一篇,我将会以这个为入口点,重点分析Netty堆外内存如何管理,也就是内存泄露检测等方面的课题。






0 0
原创粉丝点击