Starting with the constructor of DirectBuffer
The off-heap memory opened by DirectBuffer is actually allocated through Unsafe, take a look at the constructor of DirectBuffer.
|
|
A short dozen lines of code contains a very large amount of information, first of all, the key points
long base = unsafe.allocateMemory(size);
Call Unsafe to allocate memory and return the first address of memory.unsafe.setMemory(base, size, (byte) 0);
Initialize memory to 0. We’ll focus on this line in the next section.Cleaner.create(this, new Deallocator(base, size, cap));
Set the out-of-heap memory recycler, without going into details, you can refer to my previous article “An article on monitoring and recycling out-of-heap memory”.
This scene in the constructor alone makes Unsafe and ByteBuffer inextricably linked, and if you use your imagination, you can think of ByteBuffer as a safe version of the Unsafe family of memory manipulation APIs. ByteBuffer encapsulates the concepts of limit/position/capacity, which I find easier to use than Netty’s post-encapsulation ByteBuffer encapsulates the concepts of limit/position/capacity, which I find easier to use than Netty’s post-encapsulation ByteBuf, but even though it is good, it still has one aspect that people dislike: a lot of boundary checking.
One of the most attractive places for performance challenge competitors to use Unsafe to manipulate memory, rather than ByteBuffer, is bounds checking. As in example code I.
|
|
You don’t need to care what role the above code plays in DirectBuffer, what I want to show you is just its checkBounds and a bunch of if/else, especially for extreme performance scenarios, where geeks see if/else and are nervously aware of the performance degradation of branch prediction, and secondly aware of whether this pile of code can be removed.
If you don’t want a bunch of bounds checks, you can implement a custom ByteBuffer with Unsafe, like the following.
|
|
Unsafe is usually disabled in some competitions to prevent players from entering endless involutions, but there are also competitions that allow some of Unsafe’s capabilities to be used, allowing players to let loose and explore the possibilities. For example, Unsafe#allocateMemory
, which is not restricted by -XX:MaxDirectMemory
and -Xms
, was disabled in this second Cloud Native Programming Challenge, but Unsafe#put
, Unsafe#get
, and Unsafe#copyMemory
were allowed to to be used. If you definitely want to use Unsafe to manipulate out-of-heap memory, you can write code like this, which does the same thing as example code 1.
This is the first key point I want to introduce: DirectByteBuffer can bypass bounds checking by using Unsafe to perform fine-grained operations at the memory level.
Memory initialization of DirectByteBuffer
Notice that there is another operation in the DirectByteBuffer constructor that involves Unsafe: unsafe.setMemory(base, size, (byte) 0);
. In some scenarios or hardware, memory operations can be very expensive, especially when large chunks of memory are opened up, and this code can be a bottleneck for DirectByteBuffer.
If you wish to allocate memory without this initialization logic, you can do so with the help of Unsafe allocation memory and then magic the DirectByteBuffer.
|
|
After all this, we get an uninitialized DirectByteBuffer, but don’t worry, everything is working fine and setMemory for free!
Talking about zero copies of ByteBuffer
When a ByteBuffer is used as a read buffer, some of our partners choose to use locking to access the memory, but this is actually a very wrong approach and should use the duplicate and slice methods provided by ByteBuffer.
Concurrent read buffer options.
This allows the ByteBuffer after slice to be read concurrently without changing the original ByteBuffer pointer.