“This is the fifth day of my participation in the First Challenge 2022. For details: First Challenge 2022.”

Introduction to the

Why are there so many JAVA programmers in the world? One of the most important reasons is that JAVA, in contrast to C++, does not need to worry about object release, everything is done by the garbage collector. In the modern programming world, where simplicity is prized, there are fewer and fewer C++ masters and more and more JAVA programmers.

An important concept in the JVM garbage collector is the Reference count, which controls whether an object is still referenced and can be garbage collected.

Netty also runs in the JVM, so object reference counts in the JVM also apply to objects in Netty. Object references refer to specific objects in Netty. The reference count is used to determine whether these objects are still in use. If they are no longer in use, they (or their shared resources) can be returned to the object pool (or object allocator).

This is called Netty’s object reference counting technique, and one of the key objects is ByteBuf.

ByteBuf and ReferenceCounted

Object reference counting in Netty started with version 4.X, and ByteBuf was the final application to use reference counting to improve allocation and release performance.

Let’s look at the definition of ByteBuf:

public abstract class ByteBuf implements ReferenceCounted, Comparable<ByteBuf>
Copy the code

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

ReferenceCounted is the basis for object references in NetTY, and it defines several very important methods, as shown below:

int refCnt();

ReferenceCounted retain();

ReferenceCounted retain(int increment);

boolean release();

boolean release(int decrement);

Copy the code

RefCnt returns the current number of references, retain is used to add references, and release is used to release references.

Basic use of ByteBuf

The number of references to ByteBuf in the newly allocated case is 1:

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

When his release method is called, refCnt becomes 0:

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

When its retain method is called, refCnt increments one:

ByteBuf buf = ctx.alloc().directBuffer();
assert buf.refCnt() == 1;
buf.retain();
assert buf.refCnt() == 2;
Copy the code

Note that if the ByteBuf refCnt is 0, it means the ByteBuf ready to be recycled, if I call the retain method again, would be thrown IllegalReferenceCountException: refCnt: 0, increment: 1

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

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

Let’s call the writeByte method:

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

As you can see, if refCnt = 0, call its writeByte method throws IllegalReferenceCountException anomalies.

If refCnt=0, the object has been reclaimed and can no longer be used.

ByteBuf recycling

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

Netty’s principle is that whoever consumes ByteBuf is responsible for recycling ByteBuf.

In practice, ByteBuf will be transmitted in a channel, and the party receiving ByteBuf will need to reclaim it if it consumes it, according to the principle of who is responsible for destroying it.

By reclaim, we mean calling the release() method of ByteBuf.

A derivative of ByteBuf

ByteBuf can spawn many child buffs from a parent buff. These child buffs do not have their own reference count. Their reference count is shared with the parent buff. Bytebuf.duplicate (), bytebuf.slice () and bytebuf.order (ByteOrder).

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

Since the derived byteBuf and parent buff share reference counts, if you want to pass the derived byteBuf to another process 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();
}

Copy the code

Reference count in ChannelHandler

Netty can be classified into InboundChannelHandler and OutboundChannelHandler according to whether messages are read or written. The InboundChannelHandler and OutboundChannelHandler are used to read and write messages respectively.

ByteBuf’s release method is called after the Inbound message is read, according to the principle of who consumes, who releases:

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

But if you just re-send byteBuf to a channel for other steps to handle, there is no need to release:

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

Similarly, in Outbound, if it is only 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);
}
Copy the code

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); }}Copy the code

Memory leaks

Because reference count is maintained by Netty itself, it needs to be released manually in the program, which brings a problem of memory leakage. Because all references are controlled by the program itself, rather than by the JVM, some object reference counts may not be cleared due to personal programmer reasons.

To solve this problem, Netty by default selects 1% buffer allocations samples to detect whether or not they are leaking memory.

If a leak occurs, you 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()
Copy the code

Netty provides four levels for detecting memory leaks:

  • DISABLED Indicates that leak detection is DISABLED
  • SIMPLE – The default detection method, takes 1% buff.
  • ADVANCED – also checks for 1% buffs, but this option shows more leaks.
  • PARANOID – Checks all buffs.

Specific detection options are as follows:

java -Dio.netty.leakDetection.level=advanced ...
Copy the code

conclusion

Master reference counting in Netty, and master netty’s wealth password!

Learn -netty4 for an example of this article

This article is available at www.flydean.com/43-netty-re…

The most popular interpretation, the most profound dry goods, the most concise tutorial, many tips you didn’t know waiting for you to discover!

Welcome to pay attention to my public number: “procedures those things”, understand technology, more understand you!