Introduction

Why are there so many JAVA programmers in the world? One of the important reasons is that JAVA does not need to consider the release of objects compared to C++, and everything is done by the garbage collector. In the modern programming world that advocates simplicity, there are fewer and fewer masters who know C++, and more and more programmers who know JAVA.

A very important concept in the JVM's garbage collector is the reference count, which is the reference count of the object, which is used to control whether the object is still referenced and whether it can be garbage collected.

Netty also runs in the JVM, so object reference counting in the JVM also applies to objects in netty. The object reference we are talking about here refers to certain objects in netty. The reference count of the object is used to determine whether these objects are still used. If they are no longer used, they (or their shared resources) can be returned to Object pool (or object allocator).

This is called netty's object reference counting technology, and one of the most critical objects is ByteBuf.

ByteBuf and ReferenceCounted

Object reference counting in netty started from version 4.X, and ByteBuf is one of the final applications, which uses reference counting to improve allocation and deallocation performance.

Let's first look at the definition of ByteBuf:

public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf>

You can see that ByteBuf is an abstract class that implements the ReferenceCounted interface.

ReferenceCounted is the basis of object references in netty. It defines the following very important methods, as follows:

int refCnt();

ReferenceCounted retain();

ReferenceCounted retain(int increment);

boolean release();

boolean release(int decrement);

Among them, refCnt returns the current number of references, retain is used to increase references, and release is used to release references.

Basic use of ByteBuf

The number of references to ByteBuf in the case of just allocation is 1:

ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;

After calling his release method, refCnt becomes 0:

boolean destroyed = buf.release();
assert destroyed;
assert buf.refCnt() == 0;

When its retain method is called, refCnt is incremented by one:

ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;
buf.retain();
assert buf.refCnt() == 2;
It should be noted that if the refCnt of the ByteBuf is already 0, it means that the ByteBuf is ready to be recycled. If the retain method is called again, it will throw IllegalReferenceCountException: refCnt: 0, increment: 1

So we must call the retain method before the ByteBuf has been recycled.

Since the retain() method cannot be called when refCnt=0, can other methods be called?

Let's try to call the writeByte method:

        try {
            buf.writeByte(10);
        } catch (IllegalReferenceCountException e) {
            log.error(e.getMessage(),e);
        }

It can be seen that if refCnt=0, calling its writeByte method will throw an IllegalReferenceCountException exception.

In this way, as long as refCnt=0, it means that the object has been recycled and can no longer be used.

ByteBuf recycling

Since refCnt is stored in ByteBuf, who is responsible for the recycling of ByteBuf?

The principle of netty is that whoever consumes ByteBuf is responsible for the recycling of ByteBuf.

In actual work, ByteBuf will be transmitted in the channel. According to the principle of who consumes and is responsible for destruction, the party receiving ByteBuf needs to recycle it if it consumes ByteBuf.

Recycling here refers to calling the release() method of ByteBuf.

Derivative methods of ByteBuf

ByteBuf can spawn many child buffs from a parent buff. These sub-buffs do not have their own reference counts, their reference counts are shared with the parent buff. These methods for providing derived buffs are: ByteBuf.duplicate(), ByteBuf.slice() and ByteBuf.order(ByteOrder).

buf = directBuffer();
        ByteBuf derived = buf.duplicate();
        assert buf.refCnt() == 1;
        assert derived.refCnt() == 1;

Because the derived byteBuf and the parent buff share the reference count, if you want to pass the derived byteBuf to other processes for processing, you need to call the retain() method:

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();
}

Reference counting in ChannelHandler

Netty can be divided into InboundChannelHandler and OutboundChannelHandler according to whether to read or write messages, which are used to read and write messages respectively.

According to the principle of who consumes, who releases, for inbound messages, after reading, the release method of ByteBuf needs to be called:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    try {
        ...
    } finally {
        buf.release();
    }
}

But if you just resend the byteBuf to the channel for other steps to process, you don't need release:

public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf buf = (ByteBuf) msg;
    ...
    ctx.fireChannelRead(buf);
}

Similarly in Outbound, if it is just a simple retransmission, release is not required:

public void write(ChannelHandlerContext ctx, Object message, ChannelPromise promise) {
    System.err.println("Writing: " + message);
    ctx.write(message, promise);
}

If the message is processed, release is required:

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);
    }
}

memory leak

Because the reference count is maintained by netty itself, it needs to be released manually in the program, which will bring about a problem of memory leaks. Because all references are controlled by the program itself, not by the JVM, some object reference counts may not be cleared due to the programmer's personal reasons.

To solve this problem, by default, netty selects 1% of the buffer allocations samples to detect if they have memory leaks.

If a leak occurs, you will get the following log:

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()

A level for detecting memory leaks is mentioned above. Netty provides 4 levels, namely:

  • DISABLED---Disable leak detection
  • SIMPLE --The default detection method, which takes 1% of the buff.
  • ADVANCED - Also 1% buff for detection, but this option will show more leak information.
  • PARANOID - Detects all buffs.

The specific detection options are as follows:

java -Dio.netty.leakDetection.level=advanced ...

Summarize

If you master the reference count in netty, you will master the wealth password of netty!

Examples of this article can be found in: learn-netty4

This article has been included in http://www.flydean.com/43-netty-reference-cound/

The most popular interpretation, the most profound dry goods, the most concise tutorials, and many tricks you don't know are waiting for you to discover!

Welcome to pay attention to my official account: "Program those things", understand technology, understand you better!


flydean
890 声望433 粉丝

欢迎访问我的个人网站:www.flydean.com