Netty, as a high-performance network IO framework, has made a lot of optimization at the code level to reduce GC pressure and make objects reusable as much as possible, avoiding frequent creation and destruction.
The Recycler abstract class is a lightweight object reuse pool implemented by Netty based on thread-local Stack variables. The get() method is called to get reusable objects from the object pool first, and newObject() is automatically triggered to create new objects when no objects are available in the pool. Io.net ty. Util. Internal. ObjectPool. Handle the recycle recycle () method can help object, the default implementation class is DefaultHandle, collection when it will be to push the object to the thread binding Stack.
1. Usage cases
Here is an example of how to recycle and reuse Person:
public class RecyclerDemo {
static class Person {
private ObjectPool.Handle<Person> handle;
public Person(ObjectPool.Handle<Person> handle) {
this.handle = handle;
}
public void recycle(a) {
handle.recycle(this); }}public static void main(String[] args) {
ObjectPool<Person> pool = ObjectPool.newPool(new ObjectPool.ObjectCreator<Person>() {
@Override
public Person newObject(ObjectPool.Handle<Person> handle) {
return newPerson(handle); }}); Person person = pool.get(); person.recycle(); }}Copy the code
2. Source code analysis
2.1 ObjectPool
First look at the ObjectPool class, which defines a lightweight pool of objects from which the get() method can retrieve an object:
// Lightweight object pool
public abstract class ObjectPool<T> {
ObjectPool() { }
/ / object from the pool of access to an object, the object may be through the ObjectPool. ObjectCreator, newObject () to create
public abstract T get(a);
}
Copy the code
The internal interface Handle is responsible for defining object reclamation:
// Object collection processing
public interface Handle<T> {
// Reuse objects
void recycle(T self);
}
Copy the code
The internal interface ObjectCreator is responsible for defining the creation of new objects:
// Object creator
public interface ObjectCreator<T> {
// Create a new object based on Handle, which calls handle.recycle() when reusable.
T newObject(Handle<T> handle);
}
Copy the code
The static newPool method can quickly create an instance of an object pool:
public static <T> ObjectPool<T> newPool(final ObjectCreator<T> creator) {
return new RecyclerObjectPool<T>(ObjectUtil.checkNotNull(creator, "creator"));
}
Copy the code
Netty provides a RecyclerObjectPool class by default, which is very simple to recycle, so we need to recycle Recycler.
// A reusable object pool
private static final class RecyclerObjectPool<T> extends ObjectPool<T> {
private final Recycler<T> recycler;
RecyclerObjectPool(final ObjectCreator<T> creator) {
recycler = new Recycler<T>() {
// This method is called to create a new object when there are no objects available in the pool
@Override
protected T newObject(Handle<T> handle) {
returncreator.newObject(handle); }}; }@Override
public T get(a) {
returnrecycler.get(); }}Copy the code
2.2 Recycler
Recycler is at the core of Recycler, so let’s look at the Recycler’s properties first. It provides a number of static constants to define default values that can be adjusted by setting JVM parameters as follows:
// The Handle of the object does not need to be reclaimed
@SuppressWarnings("rawtypes")
private static final Handle NOOP_HANDLE = new Handle() {
@Override
public void recycle(Object object) {
// NOOP}};// WeakOrderQueue and recycleId generators need to be used
private static final AtomicInteger ID_GENERATOR = new AtomicInteger(Integer.MIN_VALUE);
// When an object is recycled, the recycleId is set to OWN_THREAD_ID, which indicates that it is recycled
private static final int OWN_THREAD_ID = ID_GENERATOR.getAndIncrement();
// The maximum capacity of the thread-local variable Stack is 4096
private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024; // Use 4k instances as default.
// The default maximum capacity of the thread-local Stack variable
private static final int DEFAULT_MAX_CAPACITY_PER_THREAD;
// The initial Stack capacity is expanded from 256 to 4096
private static final int INITIAL_CAPACITY;
// The maximum shareable capacity factor, which is 2 by default, and the non-main thread reclaiming capacity, which is half by default
private static final int MAX_SHARED_CAPACITY_FACTOR;
// Maximum number of delay queues Default number of CPU cores x 2
private static final int MAX_DELAYED_QUEUES_PER_THREAD;
// The elements array length of the Link node
private static final int LINK_CAPACITY;
// The frequency of object recycling is 1 in 8 by default, so as to avoid the rapid growth of WeakOrderQueue and reduce the sensitivity to fast recycling
private static final int RATIO;
private static final int DELAYED_QUEUE_RATIO;
Copy the code
There is no code attached to the instance constant. It has the same meaning as a static constant, but simply does an assignment.
Use Recycler threads to store a Stack of recyclable objects in the local variables of the thread:
// Store the collected objects in the thread's local Stack variable
private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {
@Override
protected Stack<T> initialValue(a) {
return new Stack<T>(Recycler.this, Thread.currentThread(), maxCapacityPerThread, maxSharedCapacityFactor,
interval, maxDelayedQueuesPerThread, delayedQueueInterval);
}
@Override
protected void onRemoval(Stack<T> value) {
if (value.threadRef.get() == Thread.currentThread()) {
if(DELAYED_RECYCLED.isSet()) { DELAYED_RECYCLED.get().remove(value); }}}};Copy the code
2.2.1 Get () Obtains objects
If maxCapacityPerThread is 0,0 will not recycle the object, and a new object will be created each time. Otherwise the Stack is fetched from the thread’s local variable, the pop() method is called to try to pop an available object from the Stack, and if none is available, newObject() is called to create a newObject.
// Get an object, if not in the pool, create a new object
@SuppressWarnings("unchecked")
public final T get(a) {
if (maxCapacityPerThread == 0) {
// The thread-local Stack variable has a capacity of 0, indicating that objects are not reused and NOOP_HANDLE is not reclaimed
return newObject((Handle<T>) NOOP_HANDLE);
}
// Get the thread-local Stack variable
Stack<T> stack = threadLocal.get();
// The object is wrapped with DefaultHandle and pops one from the stack
DefaultHandle<T> handle = stack.pop();
if (handle == null) {
// There are no more objects available. Create a new object based on handle
handle = stack.newHandle();
handle.value = newObject(handle);
}
return (T) handle.value;
}
Copy the code
The stack.pop() method is very important and needs your attention!!
Stack is an internal class Recycler that uses arrays to implement a simple Stack structure. When pop() is called, it determines whether there are any available elements in the current Stack and pops them up. Otherwise, it calls scavenge() to transfer objects from WeakOrderQueue to the Stack.
Why do WE need WeakOrderQueue?? This is because objects can be reclaimed by other threads besides the main thread. When it is reclaimed by the main thread, it will be directly pushed to the Stack. If it is reclaimed by the non-main thread, the object will be put into WeakOrderQueue of Stack binding, and then transferred from Queue to Stack when pop().
Look directly at the pop() code:
DefaultHandle<T> pop(a) {
// The number of available objects is increased when recycle() is used
int size = this.size;
if (size == 0) {
// No object is available, try to clean up
if(! scavenge()) {return null;
}
size = this.size;
if (size <= 0) {
// double check, avoid races
return null; }}// There are reusable objects, taken directly from the array
size--;
DefaultHandle ret = elements[size];
elements[size] = null;
this.size = size;
if(ret.lastRecycledId ! = ret.recycleId) {throw new IllegalStateException("recycled multiple times");
}
// Change recycleId and lastRecycledId after the object is removed
ret.recycleId = 0;
ret.lastRecycledId = 0;
return ret;
}
Copy the code
When the Stack has objects available, it’s easy to just pop them up. When no object is available, try to move the object recovered by another thread from WeakOrderQueue to Stack.
// Try to move the object in WeakOrderQueue to Stack
private boolean scavenge(a) {
// continue an existing scavenge, if any
if (scavengeSome()) {
return true;
}
// No object can be moved, reset pointer
prev = null;
cursor = head;
return false;
}
Copy the code
The concurrently () person will traverse the WeakOrderQueue, transferring all the elements in the Link to the Stack.
WeakOrderQueue is a one-way linked list consisting of a series of Link nodes, each of which stores an array of Elements to store reclaimed objects.
// Try to move objects from Queue to Stack
private boolean scavengeSome(a) {
WeakOrderQueue prev;
WeakOrderQueue cursor = this.cursor;
if (cursor == null) {
prev = null;
cursor = head;
if (cursor == null) {// The object has not been reclaimed and cannot be cleaned
return false; }}else {
prev = this.prev;
}
boolean success = false;
do {
if (cursor.transfer(this)) {
success = true;
break;
}
// Get the next node
WeakOrderQueue next = cursor.getNext();
if (cursor.get() == null) {
// A weak reference was used, the thread that detected the Queue binding was destroyed, so the node needs to be disconnected after the object is moved
if (cursor.hasFinalData()) {
for (;;) {
if (cursor.transfer(this)) {
success = true;
} else {
break; }}}if(prev ! =null) { cursor.reclaimAllSpaceAndUnlink(); prev.setNext(next); }}else {
prev = cursor;
}
cursor = next;
} while(cursor ! =null && !success);
this.prev = prev;
this.cursor = cursor;
return success;
}
Copy the code
Io.net ty. Util. Recycler. WeakOrderQueue. Transfer () method will Link node objects in the transfer to the Stack, transfer readIndex represents the progress of the pointer, to nodes LINK_CAPACITY shows that transfer is completed, I’m going to continue to transfer the next node, so I’m not going to post the code here, so if you’re interested, take a look.
2.2.2 recycle() Recycle objects
The other core main process is handle.recycle(), which is responsible for retrieving objects. Object of the recovery will be handed over to Handle processing, the default implementation is io.net ty. Util. Recycler. DefaultHandle. It first fetches the Stack container, does some validation, and then pushes the object onto the Stack for recycling.
// Reclaim the object
@Override
public void recycle(Object object) {
if(object ! = value) {/ / found empty
throw new IllegalArgumentException("object does not belong to handle");
}
// Get the bound StackStack<? > stack =this.stack;
if(lastRecycledId ! = recycleId || stack ==null) {
// The object has been reclaimed
throw new IllegalStateException("recycled already");
}
stack.push(this);
}
Copy the code
The core logic is still in stack.push(), where it will determine whether the reclaimed thread is the main thread of the stack binding. If so, it will be directly pushed; otherwise, it will be put into WeakOrderQueue of the stack binding and wait for the main thread to transfer itself.
void push(DefaultHandle
item) {
Thread currentThread = Thread.currentThread();
if (threadRef.get() == currentThread) {
// The recycle thread is the main thread, added directly to the array
pushNow(item);
} else {
/* Reclaim the thread that is not the main thread, add it to WeakOrderQueue, and move the object in Queue to Stack */ when pop() followspushLater(item, currentThread); }}Copy the code
Start with simple pushNow(), which pushes the object onto the main thread.
// the main thread is reclaimed and the object is pushed
private void pushNow(DefaultHandle
item) {
RecycleId= 0; lastRecycleId=OWN_THREAD_ID
if(item.recycleId ! =0| |! item.compareAndSetLastRecycledId(0, OWN_THREAD_ID)) {
throw new IllegalStateException("recycled already");
}
// 对象回收将 recycleId设为OWN_THREAD_ID
item.recycleId = OWN_THREAD_ID;
int size = this.size;
if (size >= maxCapacity || dropHandle(item)) {
return;
}
if (size == elements.length) {
/ / capacity
elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
}
elements[size] = item;
this.size = size + 1;
}
Copy the code
For non-main thread collection, it is a little more complicated, and objects are added to WeakOrderQueue of the Stack binding, and if the number of WeakOrderQueues reaches the upper limit, objects are no longer recycled.
// Non-main thread reclaim object
private void pushLater(DefaultHandle
item, Thread thread) {
if (maxDelayedQueues == 0) {
// Collection between threads is not supported
return;
}
// Get the mapping between Stack and QueueMap<Stack<? >, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get();// Queue of the current Stack binding
WeakOrderQueue queue = delayedRecycled.get(this);
if (queue == null) {// If Queue is null
if (delayedRecycled.size() >= maxDelayedQueues) {
// The number of delay queues has reached the upper limit, put a placeholder
delayedRecycled.put(this, WeakOrderQueue.DUMMY);
return;
}
// Create a Queue bound to the Stack
if ((queue = newWeakOrderQueue(thread)) == null) {
return;
}
delayedRecycled.put(this, queue);
} else if (queue == WeakOrderQueue.DUMMY) {
// The number of queues reached the upper limit
return;
}
// Add to Queue
queue.add(item);
}
Copy the code
At this point the object collection process is basically over.
3. Summary
Recycler is a lightweight recycling object pool implemented by Netty based on the local thread variable Stack. The goal is to reduce the pressure on GC to create and destroy objects frequently. Objects like ByteBuf and Entry use Recycler for recycling and reuse.
It uses FastThreadLocal to store a Stack structure in the local variable of each thread to hold the reclaimed objects. Which thread creates objects that only the thread can reuse, so the Stack is bound to a main thread. Recycle objects can be recycled by any thread. If it is the main thread, recycle objects can be pushed directly. For non-main thread recycling, Recycler can temporarily put objects into the Stack bound delay queue WeakOrderQueue. In order to avoid the rapid growth of The WeakOrderQueue caused by the excessive speed of object recycling, the ratio property is allowed to set the recycling frequency. When recycling objects are put into WeakOrderQueue, the main thread will try to migrate objects in WeakOrderQueue to Stack if there are no objects available when retrieving objects. This is the general process summary of Recycler recycling.