14-ReferenceCounted

ReferenceCounted

  • Netty 底層通過引用計數(ReferenceCounted)來對 ByteBuf 進行管理,使用引用計數管理可以在ByteBuf不被使用時回收到資源池,相比垃圾回收而言實時性更好。
  • ReferenceCounted 是引用計數的頂層接口。前面一篇文章講到了Netty中的 ByteBuf,它用於存儲數據,也是 Netty 中所有不同類型 ByteBuf 的頂層父類,而 ByteBuf就實現了 ReferenceCounted 接口,因此 ByteBuf 會實現(可能在其子類實現) ReferenceCounted 接口中定義好的引用計數管理功能。
public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf> {

    ......
}
  • Netty爲什麼要使用引用計數管理ByteBuf,而不是直接用JVM的GC機制?這裏有兩個參考點,參考了引用文章[4]
1:netty爲了實現zero copy使用了Direct Buffer,該buffer從Native Memory分配出來,分配和回收效率要遠低於在Java Heap上
的對象,所以一般full gc來控制的,直接內存會自己檢測情況而調用system.gc(),通過使用引用計數可以自己來控制該buffer的
釋放。具體看PlatformDependent.freeDirectBuffer(buffer)的實現。

2:netty使用了Pooled的功能。有點像c++的對象池,先分配一大塊內存,然後從裏面切出一塊一塊的供你使用,引用計數爲0放
入pool中,供後面使用。如果不按按引用計數規則使用這兩種對象,將會導致內存泄露。所以測試的時候需要測試下是有有內存泄漏。

一、接口定義

1.1 註釋

  • 下面是源碼中關於 ReferenceCounted 接口的註釋
/**
 * netty引用計數接口
 * <p>
 * <p>
 * 一個引用計數對象要求顯示的 deallocation (可以理解爲顯示的釋放內存,allocate的反義詞)
 * A reference-counted object that requires explicit deallocation.
 * <p>
 * ReferenceCounted初始化的時候,引用計數是1,
 * 調用retain()方法引用計數加一,調用release()計數減一
 * 如果引用計數減少到{@code 0},則將顯式釋放對象,訪問該釋放了的對象通常會導致訪問非法。
 * When a new {@link ReferenceCounted} is instantiated, it starts with the reference count of {@code 1}.
 * {@link #retain()} increases the reference count, and {@link #release()} decreases the reference count.
 * If the reference count is decreased to {@code 0}, the object will be deallocated explicitly, and accessing
 * the deallocated object will usually result in an access violation.
 * </p>
 * <p>
 * 如果實現了 ReferenceCounted 接口的是一個容器對象,並且這個對象包含另一個也實現了 ReferenceCounted 接口的對象
 * 那麼當容器對象的引用計數變爲0的時候,內部被包含的對象也會被釋放
 * If an object that implements {@link ReferenceCounted} is a container of other objects that implement
 * {@link ReferenceCounted}, the contained objects will also be released via {@link #release()} when the container's
 * reference count becomes 0.
 * </p>
 */

1.2 方法

  • 主要方法:主要是對引用計數的增減操作
public interface ReferenceCounted {

    /**
     * 獲得對象的引用計數,如果是0以爲這對象會被釋放
     * <p>
     * Returns the reference count of this object.  If {@code 0}, it means this object has been deallocated.
     */
    int refCnt();

    /**
     * 引用計數加1
     * <p>
     * Increases the reference count by {@code 1}.
     */
    ReferenceCounted retain();

    /**
     * 引用計數加 n
     * <p>
     * Increases the reference count by the specified {@code increment}.
     */
    ReferenceCounted retain(int increment);
    
    /**
     * 引用計數減1
     * 當引用計數爲 0 時,釋放,當且僅當引用計數爲0時,對象會被釋放
     * <p>
     * Decreases the reference count by {@code 1} and deallocates this object if the reference count reaches at
     * {@code 0}.
     *
     * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
     */
    boolean release();

    /**
     * 引用計數減n
     * 當引用計數爲 0 時,釋放,當且僅當引用計數爲0時,對象會被釋放
     * <p>
     * Decreases the reference count by the specified {@code decrement} and deallocates this object if the reference
     * count reaches at {@code 0}.
     *
     * @return {@code true} if and only if the reference count became {@code 0} and this object has been deallocated
     */
    boolean release(int decrement);
}
  • 其他方法:調試相關方法
public interface ReferenceCounted {
 
    /**
     * 等價於調用 `#touch(null)` 方法,即 hint 方法參數傳遞爲 null 。
     * <p>
     * Records the current access location of this object for debugging purposes.
     * If this object is determined to be leaked, the information recorded by this operation will be provided to you
     * via {@link ResourceLeakDetector}.  This method is a shortcut to {@link #touch(Object) touch(null)}.
     */
    ReferenceCounted touch();

    /**
     * 出於調試目的,用一個額外的任意的(arbitrary)信息記錄這個對象的當前訪問地址.
     * 如果這個對象被檢測到泄露了, 這個操作記錄的信息將通過ResourceLeakDetector 提供.
     * <p>
     * <p>
     * Records the current access location of this object with an additional arbitrary information for debugging
     * purposes.  If this object is determined to be leaked, the information recorded by this operation will be
     * provided to you via {@link ResourceLeakDetector}.
     */
    ReferenceCounted touch(Object hint);

}

二、ReferenceCounted的實現

2.1 ByteBuf

  • ByteBuf 中對 ReferenceCounted 的實現: ByteBuf 是一個抽象類,雖然實現了 ReferenceCounted 接口但是對很多方法並沒有實現,對實現的方法也是定義成 abstract ,交給子類去實現,比如下面四個方法:
    @Override
    public abstract ByteBuf retain(int increment);

    @Override
    public abstract ByteBuf retain();

    @Override
    public abstract ByteBuf touch();

    @Override
    public abstract ByteBuf touch(Object hint);

2.2 AbstractReferenceCountedByteBuf

  • ReferenceCounted 的方法幾乎都在 AbstractReferenceCountedByteBuf 中實現 ,下面是繼承圖,從圖中看到 ByteBuf 抽象類實現了 ReferenceCounted 接口,ReferenceCounted 接口的方法在 AbstractReferenceCountedByteBuf 抽象類中得到實現,幾乎全部的子類都會從 AbstractReferenceCountedByteBuf 繼承這些方法,能夠使用 ReferenceCounted 的引用計數機制來管理,這種設計方式在源碼中經常可見。

在這裏插入圖片描述

  • AbstractReferenceCountedByteBuf 源碼如下,一些讀取方法省略了:
/**
 * ByteBuf 的用於實現引用計數的抽象類
 * Abstract base class for {@link ByteBuf} implementations that count references.
 */
public abstract class AbstractReferenceCountedByteBuf extends AbstractByteBuf {

    /**
     * {@link #refCnt} 一樣計數的更新器,原子更新對象的引用計數成員變量,
     * 所有的ByteBuf實現類都繼承自AbstractReferenceCountedByteBuf,因此對於所有的實例而言引用計數成員變量都是
     * AbstractReferenceCountedByteBuf類的 refCnt 字段
     */
    private static final AtomicIntegerFieldUpdater<AbstractReferenceCountedByteBuf> refCntUpdater = AtomicIntegerFieldUpdater.newUpdater(AbstractReferenceCountedByteBuf.class, "refCnt");

    /**
     * 引用計數成員變量,volatile保證可見性
     */
    private volatile int refCnt;

    /**
     * 一個ByteBuf對象一旦被創建,引用計數就是1
     */
    protected AbstractReferenceCountedByteBuf(int maxCapacity) {
        // 設置最大容量
        super(maxCapacity);
        // 初始 refCnt 爲 1
        refCntUpdater.set(this, 1);
    }

    /**
     * 直接修改 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) {
        refCntUpdater.set(this, refCnt);
    }

    /**
     * 引用計數自增
     */
    @Override
    public ByteBuf retain() {
        return retain0(1);
    }

    /**
     * 引用計數增加 increment,校驗 increment 爲正數
     */
    @Override
    public ByteBuf retain(int increment) {
        return retain0(checkPositive(increment, "increment"));
    }

    /**
     * 引用計數增加的實現方法
     */
    private ByteBuf retain0(final int increment) {
        //1.由更新器來實現增加,方法返回的是舊值
        int oldRef = refCntUpdater.getAndAdd(this, increment);
        //2.如果 舊值<=0,或者 increment<=0,那麼就還原舊值,並且拋出異常
        if (oldRef <= 0 || oldRef + increment < oldRef) {
            // Ensure we don't resurrect (which means the refCnt was 0) and also that we encountered an overflow.
            // 加回去,負負得正。
            refCntUpdater.getAndAdd(this, -increment);
            // 拋出 IllegalReferenceCountException 異常
            throw new IllegalReferenceCountException(oldRef, increment);
        }
        return this;
    }

    @Override
    public ByteBuf touch() {
        return this;
    }

    @Override
    public ByteBuf touch(Object hint) {
        return this;
    }

    /**
     * 引用計數釋放
     */
    @Override
    public boolean release() {
        return release0(1);
    }

    /**
     * 引用計數釋放 decrement
     */
    @Override
    public boolean release(int decrement) {
        return release0(checkPositive(decrement, "decrement"));
    }

    /**
     * 引用計數釋放的實現方法
     */
    @SuppressWarnings("Duplicates")
    private boolean release0(int decrement) {
        //1.減少
        int oldRef = refCntUpdater.getAndAdd(this, -decrement);
        //2.oldRef等於減少的值,說明減去後爲0,需要釋放
        if (oldRef == decrement) {
            // 釋放
            deallocate();
            return true;
            //3.減少的值得大於原有oldRef會導致減去後爲負數,不允許,或者decrement爲負數,也不允許
        } else if (oldRef < decrement || oldRef - decrement > oldRef) {
            // Ensure we don't over-release, and avoid underflow.
            // 加回去,負負得正。
            refCntUpdater.getAndAdd(this, decrement);
            // 拋出 IllegalReferenceCountException 異常
            throw new IllegalReferenceCountException(oldRef, -decrement);
        }
        return false;
    }

    /**
     * Called once {@link #refCnt()} is equals 0.
     * 當引用計數減少爲0時,調用
     */
    protected abstract void deallocate();
}
  • 代碼不復雜,都是圍繞着內部的引用計數變量 refCnt 的操作,還是比較簡單清晰的,主要是用到了 AtomicIntegerFieldUpdater 來實現字段原子更新

2.2.1 AtomicIntegerFieldUpdater

  • AtomicIntegerFieldUpdater 可以原子的更新一個類的對象的指定字段,對應的類和字段名稱通過構造方法指定,AtomicIntegerFieldUpdater是JUC包下的工具類,和 AtomicInteger 等原子變量在一個包下,底層使用的是CAS自旋鎖機制,關於自旋鎖部分可以閱讀參考文章[1]

2.2.2 deallocate方法

  • 當一個 ByteBuf 對象的引用計數減少爲0時,該對應即不能被訪問,同時需要釋放該對象,AbstractReferenceCountedByteBuf 中定義了 deallocate 方法釋放對象方法但是並未實現,因爲對於不同類型的 ByteBuf其實現方式有所不同
  /**
     * Called once {@link #refCnt()} is equals 0.
     * 當引用計數減少爲0時,調用
     */
    protected abstract void deallocate();

三、deallocate的實現

  • 看看不同類型ByteBuf下,deallocate方法的實現

3.1 PooledByteBuf

  • PooledByteBuf的deallocate實現:前面PooledByteBuf的四個實現類,都是使用 PooledByteBuf的deallocate實現
    @Override
    protected final void deallocate() {
        //handle代表內存塊所處位置
        if (handle >= 0) {
            // 屬性重置
            final long handle = this.handle;
            this.handle = -1;
            memory = null;
            tmpNioBuf = null;
            // 釋放內存回 Arena 中
            chunk.arena.free(chunk, handle, maxLength, cache);
            chunk = null;
            // 回收對象
            recycle();
        }
    }
  • PooledByteBuf會使用一個內存緩衝池,因此會對內存塊進行回收以及相關引用屬性的重置

3.2 UnpooledHeapByteBuf

  • UnpooledHeapByteBuf 是非池化的堆上分配ByteBuf,實現如下:
    @Override
    protected void deallocate() {
        // 釋放老數組 ,array 爲內部的字節數組
        freeArray(array);
        // 設置爲空字節數組
        array = EmptyArrays.EMPTY_BYTES;
    }
  • freeArray方法由子類實現,我們看一個 UnpooledHeapByteBuf 的實現類 InstrumentedUnpooledHeapByteBuf 的實現,該類是內存分配器UnpooledByteBufAllocator的內部類,其內部實現是通過內存分配器來釋放內存的
    private static final class InstrumentedUnpooledHeapByteBuf extends UnpooledHeapByteBuf {
        ...
        
        @Override
        protected void freeArray(byte[] array) {
            int length = array.length;
            super.freeArray(array);
            // Metric
            ((UnpooledByteBufAllocator) alloc()).decrementHeap(length);
        }
    }

3.3 UnpooledDirectByteBuf

  • UnpooledDirectByteBuf 是非池化的直接內存上分配ByteBuf,實現如下:
    @Override
    protected void deallocate() {
        ByteBuffer buffer = this.buffer;
        //buffer是直接內存的引用對象,將其置爲null即可
        //,如果已經是null則返回
        if (buffer == null) {
            return;
        }
        //將buffer置null
        this.buffer = null;

        // 釋放 buffer 對象
        if (!doNotFree) {
            freeDirect(buffer);
        }
    }
  • freeDirect 方法:通過平臺依賴類來釋放直接內存
    protected void freeDirect(ByteBuffer buffer) {
        PlatformDependent.freeDirectBuffer(buffer);
    }

四、ReferenceCounted操作

  • 前面是關於引用計數介紹,在具體的使用過程中,爲了保證引用計數機制正常工作,我們需要正確的調用相關的方法,因此在Netty中對於引用計數方法調用有相關的約定原則,Netty wiki給出的建議是:誰最後訪問引用計數對象,誰就負責銷燬它。具體有下面的各種情況(可以閱讀參考文章的[2]和[3]):

4.1 常規操作

  • 初始值爲1
ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;
  • release 計數減一:當引用計數爲0時,對象會被銷燬或者歸還到對象池
assert buf.refCnt() == 1;
// release() returns true only if the reference count becomes 0.
boolean destroyed = buf.release();
assert destroyed;
assert buf.refCnt() == 0;
  • retain 計數器加一;只要對象尚未銷燬即可加一(爲0後不能再加)
ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;

buf.retain();
assert buf.refCnt() == 2;

boolean destroyed = buf.release();
assert !destroyed;
assert buf.refCnt() == 1;
  • 無效訪問:訪問一個引用計數爲0的對象將拋出:IllegalReferenceCountException
assert buf.refCnt() == 0;
try {
  buf.writeLong(0xdeadbeef);
  throw new Error("should not reach here");
} catch (IllegalReferenceCountExeception e) {
  // Expected
}

4.2 銷燬

4.2.1 銷燬時機

按照經驗,誰最後一次訪問引用計數對象,誰負責銷燬它。

1.一個組件將引用計數對象傳遞給另一個組件,通常發送的組件不需要銷燬引用計數,而是延遲由接收組件來完成
2.如果某個組件使用了一個引用計數對象,並且知道沒有其他東西可以再訪問它了(即,沒有將引用傳遞給另一個組件),則該組件應該銷燬它。

4.2.2 派生緩衝區

  • ByteBuf.duplicate(), ByteBuf.slice() and ByteBuf.order(ByteOrder) 方法會創建一個派生緩衝區,緩衝區和parent使用相同的內存區域,派生緩衝器沒有自己獨立的引用計數,而是共享parent的引用計數
ByteBuf parent = ctx.alloc().directBuffer();
ByteBuf derived = parent.duplicate();

// Creating a derived buffer does not increase the reference count.
assert parent.refCnt() == 1;
assert derived.refCnt() == 1;
  • 需要注意的是:創建派生緩衝器,引用計數並不會加一,因此如果將一個派生緩衝器傳遞給另一個組件,需要先調用retain()來自增引用計數
ByteBuf parent = ctx.alloc().directBuffer(512);
parent.writeBytes(...);

try {
    while (parent.isReadable(16)) {
        ByteBuf derived = parent.readSlice(16);
        derived.retain();
        process(derived);
    }
} finally {
    parent.release();
}
...

public void process(ByteBuf buf) {
    ...
    buf.release();
}
  • ByteBuf.copy() 和 ByteBuf.readBytes(int) 這樣的方法不是使用派生緩衝區,分配並返回的ByteBuf需要釋放

4.3 入站出站處理器

4.3.1 入站處理器和引用計數

  • 如果一個事件循環讀取到數據到 ByteBuf 並且觸發 channelRead 事件,那麼 pipeline 對應的 ChannelHandler 需要去釋放,因此接收到對應ByteBuf的handler 需要在 channelRead 方法裏面釋放,代碼如下:
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    try {
        ...
    } finally {
        buf.release();
    }
}
  • 按照前面說的,如果一個 handler只是把 ByteBuf傳遞給下一個 handler,那麼就不需要釋放
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    ...
    ctx.fireChannelRead(buf);
}
  • 需要注意的是: ByteBuf 並不是唯一的引用計數對象,如果在處理消息解碼器生成的消息,那麼消息也很可能是引用計數類型的:
// Assuming your handler is placed next to `HttpRequestDecoder`
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    if (msg instanceof HttpRequest) {
        HttpRequest req = (HttpRequest) msg;
        ...
    }
    if (msg instanceof HttpContent) {
        HttpContent content = (HttpContent) msg;
        try {
            ...
        } finally {
            content.release();
        }
    }
}
  • 如果你懷疑或者想簡化釋放消息,可以使用 ReferenceCountUtil.release(): 方法
public void channelRead(ChannelHandlerContext ctx, Object msg) {
    try {
        ...
    } finally {
        ReferenceCountUtil.release(msg);
    }
}
  • 或者,你可以繼承 SimpleChannelHandler ,SimpleChannelHandler 使用ReferenceCountUtil.release(msg) 來釋放消息

4.3.2 出站處理器和引用計數

  • 和入站處理器不同,出站消息是由應用創建的,因此寫到對端之後,由 Netty 負責釋放消息,中間攔截出站消息的處理器需要確保正確的釋放中間消息
/ Simple-pass through
public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {
    System.err.println("Writing: " + message);
    ctx.write(message, promise);
}

// Transformation
public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {
    if (message instanceof HttpContent) {
        // Transform HttpContent to ByteBuf.
        HttpContent content = (HttpContent) message;
        try {
            ByteBuf transformed = ctx.alloc().buffer();
            ....
            ctx.write(transformed, promise);
        } finally {
            content.release();
        }
    } else {
        // Pass non-HttpContent through.
        ctx.write(message, promise);
    }
}

4.4 內存泄露故障排除

4.4.1 泄露檢測

  • buffer 泄露故障排除: 引用計數的缺點是容易導致應用計數對象內存泄露,因爲JVM並不能感知Netty實現的引用計數,一旦對應對象不可訪問就會被GC掉,即使引用計數不是0。一個對象被GC後無法被恢復,這回導致其無法被還原到對象池,從而導致內存泄露
  • 幸運的是,儘管Netty很難找到泄漏,但它默認會對大約1%的緩衝區分配進行採樣,以檢查應用程序中是否存在泄漏。如果發生泄漏,您將發現以下日誌消息:
LEAK: ByteBuf.release() was not called before it's garbage-collected. Enable advanced leak reporting to find out where the leak occurred. To enable advanced leak reporting, specify the JVM option '-Dio.netty.leakDetectionLevel=advanced' or call ResourceLeakDetector.setLevel()
  • 使用上面提到的JVM選項(-Dio.netty.leakDetectionLevel=advanced)重新啓動應用程序,然後您將看到應用程序最近訪問泄漏緩衝區的位置。以下輸出顯示了單元測試(XmlFrameDecoderTest.testDecodeWithXml())的泄漏:

  • 以下示例顯示泄漏的緩衝區由名爲 EchoServerHandler#0 的處理程序處理,然後進行垃圾回收,這意味着EchoServerHandler#0可能忘記釋放緩衝區:

12:05:24.374 [nioEventLoop-1-1] ERROR io.netty.util.ResourceLeakDetector - LEAK: ByteBuf.release() was not called before it's garbage-collected.
Recent access records: 2
#2:
	Hint: 'EchoServerHandler#0' will handle the message from this point.
	io.netty.channel.DefaultChannelHandlerContext.fireChannelRead(DefaultChannelHandlerContext.java:329)
	io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:846)
	io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:133)
	io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
	io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
	io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
	io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
	java.lang.Thread.run(Thread.java:744)
#1:
	io.netty.buffer.AdvancedLeakAwareByteBuf.writeBytes(AdvancedLeakAwareByteBuf.java:589)
	io.netty.channel.socket.nio.NioSocketChannel.doReadBytes(NioSocketChannel.java:208)
	io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:125)
	io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
	io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
	io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
	io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
	java.lang.Thread.run(Thread.java:744)
Created at:
	io.netty.buffer.UnpooledByteBufAllocator.newDirectBuffer(UnpooledByteBufAllocator.java:55)
	io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:155)
	io.netty.buffer.AbstractByteBufAllocator.directBuffer(AbstractByteBufAllocator.java:146)
	io.netty.buffer.AbstractByteBufAllocator.ioBuffer(AbstractByteBufAllocator.java:107)
	io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:123)
	io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:485)
	io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:452)
	io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:346)
	io.netty.util.concurrent.SingleThreadEventExecutor$5.run(SingleThreadEventExecutor.java:794)
	java.lang.Thread.run(Thread.java:744)

  • 泄露檢測級別,一共有四個不同的內存泄露檢測級別,通過參數指定:java -Dio.netty.leakDetection.level=advanced
DISABLED:關閉,不建議
SIMPLE:1%採樣檢測是否有內存泄露  
ADVANCED:1%採樣,告訴我們內存泄露的位置 
PARANOID:和 ADVANCED 類似,但是會針對每一個buffer去檢測,自動化測試階段很有用,如果輸出包含LEAK,則很可能有內存泄露

4.4.2 最佳實踐

  • 避免內存泄露的最佳實踐
1.在 PARANOID 檢測級別運行單元測試和集成測試    
2.在相當長的一段時間內以簡單級別部署到整個集羣之前,先對應用程序進行Canary處理,以查看是否存在泄漏。
3.如果有泄露 ,再次  以獲取一些關於哪裏內存泄露的線索
4.不要將內存泄露的應用部署到整個集羣
  • 單元測試修復內存泄露:在單元測試中忘記釋放緩衝區或消息是非常容易的。它將生成泄漏警告,但這並不一定意味着您的應用程序存在泄漏。可以使用ReferenceCountUtil.releaseLater方法,而不是使用try-finally塊包裝單元測試代碼來釋放所有緩衝區
import static io.netty.util.ReferenceCountUtil.*;

@Test
public void testSomething() throws Exception {
    // ReferenceCountUtil.releaseLater() will keep the reference of buf,
    // and then release it when the test thread is terminated.
    ByteBuf buf = releaseLater(Unpooled.directBuffer(512));
    ...
}

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章