preface

This chapter describes Netty object pools.

  • How to use Netty object pool to create and recycle objects
  • How does Netty abstract object pools
  • How does Netty implement object pooling
  • How to handle the scenario where the same object is created and recycled on different threads

How to use Netty object pool

Item is a common object that is managed by the object pool.

public static class Item {
    // Class variable, an Item object pool
    private static final ObjectPool<Item> pool = ObjectPool.newPool(new ObjectPool.ObjectCreator<Item>() {
        @Override
        public Item newObject(ObjectPool.Handle<Item> handle) {
            return newItem(handle); }});// Common business attributes
    private int id;
    // The object retrieves the hook
    private final ObjectPool.Handle<Item> handle;

    /** * private constructor */
    private Item(ObjectPool.Handle<Item> handle) {
        this.handle = handle;
    }

    /** * gets item */ from the object pool
    public static Item getInstance(a) {
        return pool.get();
    }

    /** * reclaim */
    public void recycle(a) {
        this.handle.recycle(this);
    }

    public int getId(a) {
        return id;
    }

    public void setId(int id) {
        this.id = id; }}Copy the code

Objects are then created and reclaimed using the object pool.

@Test
public void test(a) {
    Item item01 = Item.getInstance(); // There is no object cache in the pool, a new object is created
    item01.setId(1);
    System.out.println(item01); // 5c7fa833
    item01.recycle(); // Retrieve item01 with the handle hook
    item01 = Item.getInstance(); // Retrieve an object from the pool again
    System.out.println(item01); // 5c7fa833
    System.out.println(item01.getId()); / / 1
}
Copy the code

Object pool abstract structure

Netty designs its own abstraction structure for ObjectPool, including ObjectPool, object creation, and object reclamation. These three abstract definitions are all in ObjectPool class.

1. Object pool

ObjectPool abstract class, which is the top-level abstraction of the ObjectPool, requires subclasses to implement a get method. If there is an object in the pool, it is returned directly by the get method, otherwise a newObject is created by ObjectCreator#newObject(Handle).

/**
 * Light-weight object pool.
 *
 * @param <T> the type of the pooled object
 */
public abstract class ObjectPool<T> {

    ObjectPool() { }

    /**
     * Get a {@link Object} from the {@link ObjectPool}. The returned {@link Object} may be created via
     * {@link ObjectCreator#newObject(Handle)} if no pooled {@link Object} is ready to be reused.
     */
    public abstract T get(a);
}
Copy the code

2. Object creation

ObjectCreator is an abstract interface for object creation. You need to implement a newObject method, pass in Handle, and create a newObject, which can then be recycled by Handle#recycle. By implication, an object needs to hold a Handle that can reclaim the current instance to ObjectPool at some point in the future.

/**
* Creates a new Object which references the given {@link Handle} and calls {@link Handle#recycle(Object)} once
* it can be re-used.
*
* @param <T> the type of the pooled object
*/
public interface ObjectCreator<T> {

    /**
    * Creates an returns a new {@link Object} that can be used and later recycled via
    * {@link Handle#recycle(Object)}.
    */
    T newObject(Handle<T> handle);
}
Copy the code

3. Object reclamation

Handle is passed in ObjectCreator when the object is created. The client needs to recycle the object by using the Recycle method of Handle.

/**
* Handle for an pooled {@link Object} that will be used to notify the {@link ObjectPool} once it can
* reuse the pooled {@link Object} again.
* @param <T>
*/
public interface Handle<T> {
    /**
    * Recycle the {@link Object} if possible and so make it ready to be reused.
    */
    void recycle(T self);
}
Copy the code

Netty implements object pool

Netty implements its own lightweight ObjectPool for the above abstract interface. The realization of this lightweight ObjectPool is the static internal class RecyclerObjectPool of ObjectPool.

private static final class RecyclerObjectPool<T> extends ObjectPool<T> {
    private final Recycler<T> recycler;

    RecyclerObjectPool(final ObjectCreator<T> creator) {
        recycler = new Recycler<T>() {
            @Override
            protected T newObject(Handle<T> handle) {
                returncreator.newObject(handle); }}; }@Override
    public T get(a) {
        returnrecycler.get(); }}Copy the code

As you can see, creating a RecyclerObjectPool requires passing in the implementation class of the ObjectCreator interface and no additional methods. RecyclerObjectPool actually creates a Recycler. By using ObjectCreator, Recycler can create objects when there are no cached objects in the pool.

Netty also provides a static method in ObjectPool to create a RecyclerObjectPool.

/**
* Creates a new {@link ObjectPool} which will use the given {@link ObjectCreator} to create the {@link Object}
* that should be pooled.
*/
public static <T> ObjectPool<T> newPool(final ObjectCreator<T> creator) {
    return new RecyclerObjectPool<T>(ObjectUtil.checkNotNull(creator, "creator"));
}
Copy the code

Fourth, the Recycler

Recycler is a portal to the object pool to retrieve objects, so let’s start with class variables.

The first and most critical is a FastThreadLocal instance, threadLocal. Each thread manages one instance of the Stack, which is the actual pool.

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

Additionally, object pooling is complicated by the fact that object creation and collection may not be done in the same thread. There is also a FastThreadLocal instance, DELAYED_RECYCLED, which holds the WeakOrderQueue container where the stack of the current thread helps the stack of other threads recycle. For example, the Stack of thread A allocates instance X, and the Stack of thread B retrieves instance X. The FastThreadLocal is for thread B, and the Map key is the Stack of thread A, and the value is the WeakOrderQueue created by thread B. The object used to store the Stack collection that helps thread A.

// Stack instances of other threads - A thread helps other threads' stack recycle objects in a WeakOrderQueue container
private static finalFastThreadLocal<Map<Stack<? >, WeakOrderQueue>> DELAYED_RECYCLED =newFastThreadLocal<Map<Stack<? >, WeakOrderQueue>>() {@Override
    protectedMap<Stack<? >, WeakOrderQueue> initialValue() {return new WeakHashMap<Stack<?>, WeakOrderQueue>();
    }
};
Copy the code

Recycler member variables are used to limit the size of the Recycler pool and prevent it from expanding.

private final int maxCapacityPerThread; / / 4096
private final int maxSharedCapacityFactor; / / 2
private final int interval; / / 8
private final int maxDelayedQueuesPerThread; / / auditing * 2
private final int delayedQueueInterval; / / 8
Copy the code
  • MaxCapacityPerThread: Since the object pool is a thread-local cache based on the FastThreadLocal implementation, maxCapacityPerThread controls how many cached instances a Stack can hold in order to prevent too many instances from being cached.
  • MaxSharedCapacityFactor: a factor used to calculate the maximum number of instances that other threads can help the current thread reclaim.
  • Interval/delayedQueueInterval: in order to prevent the object pool, not every recycling object back into the object pool, behind the introduction of various kinds of container, usually once every eight will truly cache.
  • MaxDelayedQueuesPerThread: each thread can help most nuclear number * 2 threads recycling.

The Recycler get method is the access method to the object pool. Use ThreadLocal to get the Stack instance corresponding to the current thread, and manipulate Stack to get Handle and value corresponding to Handle (client object instance).

public final T get(a) {
    / / the default 4096
    if (maxCapacityPerThread == 0) {
        return newObject((Handle<T>) NOOP_HANDLE);
    }
    // Get the Stack of the current thread
    Stack<T> stack = threadLocal.get();
    / / for the Handle
    DefaultHandle<T> handle = stack.pop();
    // If there is a Handle in the stack, return the cached object directly; otherwise, create Handle
    if (handle == null) {
        Handle = new DefaultHandle<>(stack)
        handle = stack.newHandle();
        // Call the newObject method of the anonymous class in the RecycleObjectPool constructor, which is actually the anonymous ObjectCreator implemented by the client
        handle.value = newObject(handle);
    }
    return (T) handle.value;
}
Copy the code

Fifth, DefaultHandle

DefaultHandle is a hook from the Netty object pool to the client to reclaim an object (see the example in section 1).

private static final class DefaultHandle<T> implements Handle<T> {
    int lastRecycledId;
    int recycleId;
    booleanhasBeenRecycled; Stack<? > stack; Object value; DefaultHandle(Stack<? > stack) {this.stack = stack;
    }
    @Override
    public void recycle(Object object) {
        if(object ! = value) {throw new IllegalArgumentException("object does not belong to handle"); } Stack<? > stack =this.stack;
        if(lastRecycledId ! = recycleId || stack ==null) {
            throw new IllegalStateException("recycled already");
        }
        stack.push(this); }}Copy the code

The member variable value is an object created by the client and managed by the object pool. The Recycle method is called by the client, placing DefaultHandle on the Stack.

Sixth, the Stack

Start with the Stack legend and member variables to get an idea of its structure.

private static final class Stack<T> {
    // Recycler belongs to a RecyclerObjectPool. Stack belongs to an object pool instance
    final Recycler<T> parent;
    // userModel -> handle -> stack -> thread
    final WeakReference<Thread> threadRef;
    // The maximum number of objects that can be reclaimed by other threads = Max (4096/2, 16)
    final AtomicInteger availableSharedCapacity;
    // Allow x threads to help recycle (the maximum number of WeakOrderQueue) = number of cores * 2
    private final int maxDelayedQueues;
    // Stack specifies the maximum number of defaulthandles that can be stored
    // Corresponds to the maximum number of DefaultHandle that the current thread can store, since the Stack is controlled by ThreadLocal
    private final int maxCapacity;
    // Handle retrievals the recycle counter, which becomes 0 when interval (8)
    private int handleRecycleCount;
    If handleRecycleCount<8, it will be discarded
    private final int interval;
    // It is similar to interval, but is the threshold when a different thread is reclaimed to WeakOrderQueue. The default value is also 8
    private final int delayedQueueInterval;
    / / store DefaultHandleDefaultHandle<? >[] elements;// The number of actual elements in elements
    int size;
    // Objects created by the current stack are reclaimed by other threads, and these instances are stored in WeakOrderQueue
    private WeakOrderQueue cursor, prev; // consumption WeakOrderQueue Schedule double pointer
    private volatile WeakOrderQueue head; // Stores the head node of WeakOrderQueue
}
Copy the code

Most member variables are the same as Recycler, to prevent the pool of objects from expanding from Recycler. Focus on a few member variables:

  • Elements: Array of Handle instances cached by the current thread (Stack instance ThreadLocal). Initial capacity 256.
  • Head: Linked list of WeakOrderQueue that stores Handle instances that other threads help the current thread reclaim.
  • Cursor /prev: The current Stack gets the progress of Handle consumption from WeakOrderQueue linked list. Here is the double pointer.
  • ThreadRef: Since Stack operates on ThreadLocal, it stores virtual references to the corresponding thread. The reason for using virtual references is as follows: If the current Stack corresponds to a ThreadA, the ThreadB client holds an object allocated by ThreadA, and the client holds the Handle, which holds the Stack. If the Stack strongly references ThreadA, the ThreadA cannot be recycled.
  • HandleRecycleCount/interval: handleRecycleCount is a loop counter, the maximum value is equal to the interval, the initial value is equal to the interval, for each Handle recycling plus 1. If handleRecycleCount is smaller than interval, Handle is not actually put back into the pool, preventing the object pool from swelling. The default interval is 8.

1. Get objects

The POP method is used to get the cached DefaultHandle from the Stack. First fetch from the elements array collected by the current thread. If the Elements array is empty, try to move some DefaultHandle from the WeakOrderQueue into the Elements array and then fetch from the Elements array.

DefaultHandle<T> pop(a) {
    int size = this.size;
    // The current thread does not cache Handle, elements has no elements in the array
    if (size == 0) {
        // Try to get from WeakOrderQueue
        if(! scavenge()) {return null;
        }
        size = this.size;
        if (size <= 0) {
            return null; }}// The current thread has a cache Handle, which is retrieved directly from the Elements array
    size --;
    DefaultHandle ret = elements[size];
    elements[size] = null;
    this.size = size;
    if(ret.lastRecycledId ! = ret.recycleId) {throw new IllegalStateException("recycled multiple times");
    }
    ret.recycleId = 0;
    ret.lastRecycledId = 0;
    return ret;
}
Copy the code

If the Elements array is empty, the Scavenge method tries to iterate the WeakOrderQueue list from cursor and call the WeakOrderQueue transfer method to recycle object instances that other threads help the current thread Stack. Into the elements array of the current Stack.

private boolean scavenge(a) {
    // Walk through the WeakOrderQueue linked list from cursor and try to get some DefaultHandle into the Elements array
    if (scavengeSome()) {
        return true;
    }
    // If obtaining DefaultHandle from WeakOrderQueue fails, reset cursor and prev Pointers
    prev = null;
    cursor = head;
    return false;
}

private boolean scavengeSome(a) {
    WeakOrderQueue prev;
    WeakOrderQueue cursor = this.cursor;
    // cursor is empty, initialize two Pointers
    if (cursor == null) {
        prev = null;
        cursor = head;
        if (cursor == null) {
            return false; }}else {
        prev = this.prev;
    }
    boolean success = false;
    do {
        // Try to get a Handle from another thread's Recovered WeakOrderQueue into its own elements
        if (cursor.transfer(this)) {
            success = true;
            break;
        }
        WeakOrderQueue next = cursor.getNext();
        // If the thread referenced by WeakOrderQueue has been reclaimed, move all handles in the WeakOrderQueue to the elements array of the current Stack
        if (cursor.get() == null) {
            if (cursor.hasFinalData()) {
                for (;;) {
                    if (cursor.transfer(this)) {
                        success = true;
                    } else {
                        break; }}}// Remove the WeakOrderQueue of the reclaimed thread to which the cursor points from the linked list
            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

2. Return the object

The entry point for returning an object is the Recycle method of DefaultHandle, which is called by the client. The PooledByteBuf abstract class, for example, uses an object pool. When the ByteBuf reference count is zero, the DealLocate method is triggered, and then the Recycle method of DefaultHandle is called. Because PooledByteBuf doesn’t actually hold the resource, just the container object that holds it (the real resource is a 16MB memory block), using an object pool can reduce the overhead and GC of new objects.

@Override
protected final void deallocate(a) {
    if (handle >= 0) {
        final long handle = this.handle;
        this.handle = -1;
        memory = null;
        chunk.arena.free(chunk, tmpNioBuf, handle, maxLength, cache);
        tmpNioBuf = null;
        chunk = null; recycle(); }}private void recycle(a) {
    // DefaultHandle#recycle(PooledByteBuf)
    recyclerHandle.recycle(this);
}
Copy the code

The recycle method of DefaultHandle, which calls the Stack’s push method, puts an instance of DefaultHandle on the Stack.

void push(DefaultHandle
        item) {
    Thread currentThread = Thread.currentThread();
    // If the current stack is created by the current thread, and the current thread is reclaiming Handle, put it directly into the Elements array
    if (threadRef.get() == currentThread) {
        pushNow(item);
    }
    // If the current stack is created by another thread, but reclaimed by the current thread, put WeakOrderQueue in WeakOrderQueue
    // Insert WeakOrderQueue into the head of the WeakOrderQueue list of the stack of other threads
    else{ pushLater(item, currentThread); }}Copy the code

The pushNow method puts DefaultHandle directly into the elements array of the current stack, but may discard the Handle to prevent the object pool from overexpanding.

private void pushNow(DefaultHandle
        item) {
    if((item.recycleId | item.lastRecycledId) ! =0) {
        throw new IllegalStateException("recycled already");
    }
    item.recycleId = item.lastRecycledId = OWN_THREAD_ID;

    int size = this.size;
    // If the number of elements exceeds 4096 or dropHandle decides to discard the element to control the object pool size
    if (size >= maxCapacity || dropHandle(item)) {
        return;
    }
    if (size == elements.length) {
        elements = Arrays.copyOf(elements, min(size << 1, maxCapacity));
    }

    elements[size] = item;
    this.size = size + 1;
}

HandleRecycleCount =interval=8 * handleRecycleCount will not be discarded for the first time, 2-8 times, and the ninth time */
boolean dropHandle(DefaultHandle
        handle) {
    if(! handle.hasBeenRecycled) {if (handleRecycleCount < interval) {
            handleRecycleCount++;
            // Drop the object.
            return true;
        }
        handleRecycleCount = 0;
        handle.hasBeenRecycled = true;
    }
    return false;
}
Copy the code

The pushLater method places DefaultHandle in the WeakOrderQueue, or if it is a new WeakOrderQueue, inserts the WeakOrderQueue into the Head of the WeakOrderQueue list on the stack. Weakorderqueue.dummy considers an empty implementation and cannot actually hold elements. When the current thread in DELAYED_RECYCLED helps recycle more than 16 of the stack (or think of it as the number of threads), recycling is stopped.

private void pushLater(DefaultHandle
        item, Thread thread) { Map<Stack<? >, WeakOrderQueue> delayedRecycled = DELAYED_RECYCLED.get(); WeakOrderQueue queue = delayedRecycled.get(this);
    if (queue == null) {
        If the current thread has already helped 16 threads to collect, it will not help other threads to collect
        if (delayedRecycled.size() >= maxDelayedQueues) {
            delayedRecycled.put(this, WeakOrderQueue.DUMMY);
            return;
        }
        // If the stack's corresponding WeakOrderQueue does not exist, create and insert the stack's WeakOrderQueue header
        WeakOrderQueue also has a discard policy, so it may return null
        if ((queue = newWeakOrderQueue(thread)) == null) {
            return;
        }
        delayedRecycled.put(this, queue);
    } else if (queue == WeakOrderQueue.DUMMY) {
        return;
    }
    queue.add(item);
}

private WeakOrderQueue newWeakOrderQueue(Thread thread) {
    return WeakOrderQueue.newQueue(this, thread);
}

static WeakOrderQueue newQueue(Stack
        stack, Thread thread) {
    if(! Head.reserveSpaceForLink(stack.availableSharedCapacity)) {return null;
    }
    // Place the stack and current recycle thread into the WeakOrderQueue
    final WeakOrderQueue queue = new WeakOrderQueue(stack, thread);
    // Insert WeakOrderQueue in the stack as the head node
    stack.setHead(queue);
    return queue;
}
Copy the code

Seven, WeakOrderQueue

WeakOrderQueue inherits the thread that WeakReference virtualizes to perform recycling, and internally stores DefaultHandle that assists the recycling thread in the way of Link + array (Link# Elements).

private static final class WeakOrderQueue extends WeakReference<Thread> {
    / / Link list
    private final Head head;
    private Link tail;
    // Point to the next Queue
    private WeakOrderQueue next;
    // An id flag
    private final int id = ID_GENERATOR.getAndIncrement();
    // Discard policy same Stack interval Default 8 handleRecycleCount initial 8 Value range 0-8
    private final int interval;
    private int handleRecycleCount;
    
    private WeakOrderQueue(Stack
        stack, Thread thread) {
        super(thread);
        tail = new Link();
        head = new Head(stack.availableSharedCapacity);
        head.link = tail;
        interval = stack.delayedQueueInterval;
        handleRecycleCount = interval;
    }
    
    private static final class Head {
        // Max capacity default 2048 = Stack#availableSharedCapacity
        private final AtomicInteger availableSharedCapacity;
        // The actual header node
        Link link;
    }
    
    static final class Link extends AtomicInteger {
        16 / / Handle
        finalDefaultHandle<? >[] elements =new DefaultHandle[LINK_CAPACITY];
        // AtomicInteger serves as write index, which is the next index that can be inserted into elements
        // readIndex as readIndex is the next index to get elements
        int readIndex;
        / / next pointerLink next; }}Copy the code

Where Link has read and write indexes, inheriting AtomicInteger as write index, representing the index of elements that can be inserted next time; ReadIndex represents the subscript of the Elements array for the next available Handle.

There are two key methods for WeakOrderQueue. One is that the Add method stores Handle into Link, and the other is that the transfer method selects all handles in a Link and puts them into elements of the specified Stack.

WeakOrderQueue and Link have their own discarder strategies. The former is similar to Stack, while the latter is limited by maximum capacity Stack#availableSharedCapacity.

void add(DefaultHandle
        handle) {
    handle.lastRecycledId = id;
    // Queue Discard policy
    if (handleRecycleCount < interval) {
        handleRecycleCount++;
        return;
    }
    handleRecycleCount = 0;

    Link tail = this.tail;
    int writeIndex;
    // If the tail Link is full, try creating a new Link
    if ((writeIndex = tail.get()) == LINK_CAPACITY) {
        // Link discard policy
        Link link = head.newLink();
        if (link == null) {
            return;
        }
        this.tail = tail = tail.next = link;

        writeIndex = tail.get();
    }
    tail.elements[writeIndex] = handle;
    handle.stack = null;
    tail.lazySet(writeIndex + 1);
}

Link newLink(a) {
    return reserveSpaceForLink(availableSharedCapacity) ? new Link() : null;
}
/** * If availableSharedCapacity < LINK_CAPACITY(16), return false Discard * otherwise try availableSharedCapacity = availableSharedCapacity -link_capacity (16) */
static boolean reserveSpaceForLink(AtomicInteger availableSharedCapacity) {
    for (;;) {
        int available = availableSharedCapacity.get();
        if (available < LINK_CAPACITY) {
            return false;
        }
        if (availableSharedCapacity.compareAndSet(available, available - LINK_CAPACITY)) {
            return true; }}}Copy the code

Transfer method is relatively long, the main logic is expansion + data migration.

boolean transfer(Stack
        dst) {
    Link head = this.head.link;
    if (head == null) {
        return false;
    }

    if (head.readIndex == LINK_CAPACITY) {
        if (head.next == null) {
            return false;
        }
        head = head.next;
        this.head.relink(head);
    }

    final int srcStart = head.readIndex;
    int srcEnd = head.get();
    final int srcSize = srcEnd - srcStart;
    if (srcSize == 0) {
        return false;
    }

    final int dstSize = dst.size;
    final int expectedCapacity = dstSize + srcSize;
    // The DST capacity is insufficient
    if (expectedCapacity > dst.elements.length) {
        final int actualCapacity = dst.increaseCapacity(expectedCapacity);
        srcEnd = min(srcStart + actualCapacity - dstSize, srcEnd);
    }
    // Move DefaultHandle to elements in DST
    if(srcStart ! = srcEnd) {final DefaultHandle[] srcElems = head.elements;
        final DefaultHandle[] dstElems = dst.elements;
        int newDstSize = dstSize;
        for (inti = srcStart; i < srcEnd; i++) { DefaultHandle<? > element = srcElems[i];if (element.recycleId == 0) {
                element.recycleId = element.lastRecycledId;
            } else if(element.recycleId ! = element.lastRecycledId) {throw new IllegalStateException("recycled already");
            }
            srcElems[i] = null;
			// The DST Stack discard policy may be triggered
            if (dst.dropHandle(element)) {
                // Drop the object.
                continue;
            }
            element.stack = dst;
            dstElems[newDstSize ++] = element;
        }

        if(srcEnd == LINK_CAPACITY && head.next ! =null) {
            this.head.relink(head.next);
        }

        head.readIndex = srcEnd;
        if (dst.size == newDstSize) {
            return false;
        }
        dst.size = newDstSize;
        return true;
    } else {
        return false; }}Copy the code

conclusion

  • How to use Netty object pool to create and recycle objects?

    • You need a constructor that takes objectPool.handle

      private Item(ObjectPool.Handle<Item> handle) {
        	this.handle = handle;
      }
      Copy the code
    • ObjectPool.ObjectCreator Netty provides ObjectPool.ObjectCreator newObject.

      private static final ObjectPool<Item> pool = ObjectPool.newPool(new ObjectPool.ObjectCreator<Item>() {
          @Override
          public Item newObject(ObjectPool.Handle<Item> handle) {
            return newItem(handle); }});Copy the code
    • Provides a recycle method for target pooled objects that recycles the current instance through objectPool.handle.

      public void recycle(a) {
        	this.handle.recycle(this);
      }
      Copy the code
  • How does Netty abstract object pools?

    • ObjectPool: ObjectPool, which provides the get method to obtain objects.
    • ObjectCreator: ObjectCreator that provides the newObject method to create objects.
    • Handle: Object collector that recycles objects using the Recycle method.
  • How does Netty implement object pooling?

    • RecyclerObjectPool realization ObjectPool provides method to obtain objects; The ObjectCreator object creation logic is realized by the user. Recycler.DefaultHandle Recycles objects.
    • Recycler is the inlet class of the ObjectPool. Each ObjectPool holds one Recycler.
    private static final class RecyclerObjectPool<T> extends ObjectPool<T> {
        private final Recycler<T> recycler;
    
        RecyclerObjectPool(final ObjectCreator<T> creator) {
          recycler = new Recycler<T>() {
            @Override
            protected T newObject(Handle<T> handle) {
              returncreator.newObject(handle); }}; }@Override
        public T get(a) {
          returnrecycler.get(); }}Copy the code
    • Thread pooling, where each thread holds a Stack pool of objects, reduces competition for resources and can be managed by Recycler.
    // Recycler
    private finalFastThreadLocal<Stack<T>> threadLocal = ... ;Copy the code

  • How to handle the scenario where the same object is created and recycled on different threads?

    • Premise: For an object pool RecyclerObjectPool (or Recycler) one thread (ThreadLocal) Stack
    • Objects created and reclaimed by the current thread are stored directly in the Elements array of the Stack, where objects are retrieved first
    • Objects created by the current thread can be recycled by other threads and stored in the Recycler’s ThreadLocal. The Map key is the Stack container that creates the Recycler object and the value holds objects recycled by the current thread. Each time an object is fetched, if the Elements array runs out, it is fetched from an object that another thread helps reclaim
    // Recycler
    // Stack instances of other threads - A thread helps other threads' stack recycle objects in a WeakOrderQueue container
    private static finalFastThreadLocal<Map<Stack<? >, WeakOrderQueue>> DELAYED_RECYCLED =newFastThreadLocal<Map<Stack<? >, WeakOrderQueue>>() {@Override
        protectedMap<Stack<? >, WeakOrderQueue> initialValue() {return new WeakHashMap<Stack<?>, WeakOrderQueue>();
        }
    };
    Copy the code