View Javadoc
1   /*
2    * Copyright 2022 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty.buffer;
17  
18  import io.netty.util.ByteProcessor;
19  import io.netty.util.CharsetUtil;
20  import io.netty.util.IllegalReferenceCountException;
21  import io.netty.util.NettyRuntime;
22  import io.netty.util.Recycler;
23  import io.netty.util.Recycler.EnhancedHandle;
24  import io.netty.util.ReferenceCounted;
25  import io.netty.util.concurrent.FastThreadLocal;
26  import io.netty.util.concurrent.FastThreadLocalThread;
27  import io.netty.util.concurrent.MpscIntQueue;
28  import io.netty.util.internal.AtomicReferenceCountUpdater;
29  import io.netty.util.internal.ObjectPool;
30  import io.netty.util.internal.ObjectUtil;
31  import io.netty.util.internal.PlatformDependent;
32  import io.netty.util.internal.ReferenceCountUpdater;
33  import io.netty.util.internal.SystemPropertyUtil;
34  import io.netty.util.internal.ThreadExecutorMap;
35  import io.netty.util.internal.UnsafeReferenceCountUpdater;
36  import io.netty.util.internal.UnstableApi;
37  import io.netty.util.internal.VarHandleReferenceCountUpdater;
38  
39  import java.io.IOException;
40  import java.io.InputStream;
41  import java.io.OutputStream;
42  import java.lang.invoke.MethodHandles;
43  import java.lang.invoke.VarHandle;
44  import java.nio.ByteBuffer;
45  import java.nio.ByteOrder;
46  import java.nio.channels.ClosedChannelException;
47  import java.nio.channels.FileChannel;
48  import java.nio.channels.GatheringByteChannel;
49  import java.nio.channels.ScatteringByteChannel;
50  import java.nio.charset.Charset;
51  import java.util.Arrays;
52  import java.util.Queue;
53  import java.util.Set;
54  import java.util.concurrent.ConcurrentHashMap;
55  import java.util.concurrent.ConcurrentLinkedQueue;
56  import java.util.concurrent.ThreadLocalRandom;
57  import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
58  import java.util.concurrent.atomic.AtomicLong;
59  import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
60  import java.util.concurrent.locks.StampedLock;
61  import java.util.function.IntSupplier;
62  
63  import static io.netty.util.internal.ReferenceCountUpdater.getUnsafeOffset;
64  import static java.util.concurrent.atomic.AtomicIntegerFieldUpdater.newUpdater;
65  
66  /**
67   * An auto-tuning pooling allocator, that follows an anti-generational hypothesis.
68   * <p>
69   * The allocator is organized into a list of Magazines, and each magazine has a chunk-buffer that they allocate buffers
70   * from.
71   * <p>
72   * The magazines hold the mutexes that ensure the thread-safety of the allocator, and each thread picks a magazine
73   * based on the id of the thread. This spreads the contention of multi-threaded access across the magazines.
74   * If contention is detected above a certain threshold, the number of magazines are increased in response to the
75   * contention.
76   * <p>
77   * The magazines maintain histograms of the sizes of the allocations they do. The histograms are used to compute the
78   * preferred chunk size. The preferred chunk size is one that is big enough to service 10 allocations of the
79   * 99-percentile size. This way, the chunk size is adapted to the allocation patterns.
80   * <p>
81   * Computing the preferred chunk size is a somewhat expensive operation. Therefore, the frequency with which this is
82   * done, is also adapted to the allocation pattern. If a newly computed preferred chunk is the same as the previous
83   * preferred chunk size, then the frequency is reduced. Otherwise, the frequency is increased.
84   * <p>
85   * This allows the allocator to quickly respond to changes in the application workload,
86   * without suffering undue overhead from maintaining its statistics.
87   * <p>
88   * Since magazines are "relatively thread-local", the allocator has a central queue that allow excess chunks from any
89   * magazine, to be shared with other magazines.
90   * The {@link #createSharedChunkQueue()} method can be overridden to customize this queue.
91   */
92  @UnstableApi
93  final class AdaptivePoolingAllocator {
94      /**
95       * The 128 KiB minimum chunk size is chosen to encourage the system allocator to delegate to mmap for chunk
96       * allocations. For instance, glibc will do this.
97       * This pushes any fragmentation from chunk size deviations off physical memory, onto virtual memory,
98       * which is a much, much larger space. Chunks are also allocated in whole multiples of the minimum
99       * chunk size, which itself is a whole multiple of popular page sizes like 4 KiB, 16 KiB, and 64 KiB.
100      */
101     static final int MIN_CHUNK_SIZE = 128 * 1024;
102     private static final int EXPANSION_ATTEMPTS = 3;
103     private static final int INITIAL_MAGAZINES = 1;
104     private static final int RETIRE_CAPACITY = 256;
105     private static final int MAX_STRIPES = NettyRuntime.availableProcessors() * 2;
106     private static final int BUFS_PER_CHUNK = 8; // For large buffers, aim to have about this many buffers per chunk.
107 
108     /**
109      * The maximum size of a pooled chunk, in bytes. Allocations bigger than this will never be pooled.
110      * <p>
111      * This number is 8 MiB, and is derived from the limitations of internal histograms.
112      */
113     private static final int MAX_CHUNK_SIZE = 8 * 1024 * 1024; // 8 MiB.
114     private static final int MAX_POOLED_BUF_SIZE = MAX_CHUNK_SIZE / BUFS_PER_CHUNK;
115 
116     /**
117      * The capacity if the chunk reuse queues, that allow chunks to be shared across magazines in a group.
118      * The default size is twice {@link NettyRuntime#availableProcessors()},
119      * same as the maximum number of magazines per magazine group.
120      */
121     private static final int CHUNK_REUSE_QUEUE = Math.max(2, SystemPropertyUtil.getInt(
122             "io.netty.allocator.chunkReuseQueueCapacity", NettyRuntime.availableProcessors() * 2));
123 
124     /**
125      * The capacity if the magazine local buffer queue. This queue just pools the outer ByteBuf instance and not
126      * the actual memory and so helps to reduce GC pressure.
127      */
128     private static final int MAGAZINE_BUFFER_QUEUE_CAPACITY = SystemPropertyUtil.getInt(
129             "io.netty.allocator.magazineBufferQueueCapacity", 1024);
130 
131     /**
132      * The size classes are chosen based on the following observation:
133      * <p>
134      * Most allocations, particularly ones above 256 bytes, aim to be a power-of-2. However, many use cases, such
135      * as framing protocols, are themselves operating or moving power-of-2 sized payloads, to which they add a
136      * small amount of overhead, such as headers or checksums.
137      * This means we seem to get a lot of mileage out of having both power-of-2 sizes, and power-of-2-plus-a-bit.
138      * <p>
139      * On the conflicting requirements of both having as few chunks as possible, and having as little wasted
140      * memory within each chunk as possible, this seems to strike a surprisingly good balance for the use cases
141      * tested so far.
142      */
143     private static final int[] SIZE_CLASSES = {
144             32,
145             64,
146             128,
147             256,
148             512,
149             640, // 512 + 128
150             1024,
151             1152, // 1024 + 128
152             2048,
153             2304, // 2048 + 256
154             4096,
155             4352, // 4096 + 256
156             8192,
157             8704, // 8192 + 512
158             16384,
159             16896, // 16384 + 512
160     };
161 
162     private static final int SIZE_CLASSES_COUNT = SIZE_CLASSES.length;
163     private static final byte[] SIZE_INDEXES = new byte[(SIZE_CLASSES[SIZE_CLASSES_COUNT - 1] / 32) + 1];
164 
165     static {
166         if (MAGAZINE_BUFFER_QUEUE_CAPACITY < 2) {
167             throw new IllegalArgumentException("MAGAZINE_BUFFER_QUEUE_CAPACITY: " + MAGAZINE_BUFFER_QUEUE_CAPACITY
168                     + " (expected: >= " + 2 + ')');
169         }
170         int lastIndex = 0;
171         for (int i = 0; i < SIZE_CLASSES_COUNT; i++) {
172             int sizeClass = SIZE_CLASSES[i];
173             //noinspection ConstantValue
174             assert (sizeClass & 5) == 0 : "Size class must be a multiple of 32";
175             int sizeIndex = sizeIndexOf(sizeClass);
176             Arrays.fill(SIZE_INDEXES, lastIndex + 1, sizeIndex + 1, (byte) i);
177             lastIndex = sizeIndex;
178         }
179     }
180 
181     private final ChunkAllocator chunkAllocator;
182     private final Set<Chunk> chunkRegistry;
183     private final MagazineGroup[] sizeClassedMagazineGroups;
184     private final MagazineGroup largeBufferMagazineGroup;
185     private final FastThreadLocal<MagazineGroup[]> threadLocalGroup;
186 
187     AdaptivePoolingAllocator(ChunkAllocator chunkAllocator, boolean useCacheForNonEventLoopThreads) {
188         this.chunkAllocator = ObjectUtil.checkNotNull(chunkAllocator, "chunkAllocator");
189         chunkRegistry = ConcurrentHashMap.newKeySet();
190         sizeClassedMagazineGroups = createMagazineGroupSizeClasses(this, false);
191         largeBufferMagazineGroup = new MagazineGroup(
192                 this, chunkAllocator, new HistogramChunkControllerFactory(true), false);
193 
194         threadLocalGroup = new FastThreadLocal<MagazineGroup[]>() {
195             @Override
196             protected MagazineGroup[] initialValue() {
197                 if (useCacheForNonEventLoopThreads || ThreadExecutorMap.currentExecutor() != null) {
198                     return createMagazineGroupSizeClasses(AdaptivePoolingAllocator.this, true);
199                 }
200                 return null;
201             }
202 
203             @Override
204             protected void onRemoval(final MagazineGroup[] groups) throws Exception {
205                 if (groups != null) {
206                     for (MagazineGroup group : groups) {
207                         group.free();
208                     }
209                 }
210             }
211         };
212     }
213 
214     private static MagazineGroup[] createMagazineGroupSizeClasses(
215             AdaptivePoolingAllocator allocator, boolean isThreadLocal) {
216         MagazineGroup[] groups = new MagazineGroup[SIZE_CLASSES.length];
217         for (int i = 0; i < SIZE_CLASSES.length; i++) {
218             int segmentSize = SIZE_CLASSES[i];
219             groups[i] = new MagazineGroup(allocator, allocator.chunkAllocator,
220                     new SizeClassChunkControllerFactory(segmentSize), isThreadLocal);
221         }
222         return groups;
223     }
224 
225     /**
226      * Create a thread-safe multi-producer, multi-consumer queue to hold chunks that spill over from the
227      * internal Magazines.
228      * <p>
229      * Each Magazine can only hold two chunks at any one time: the chunk it currently allocates from,
230      * and the next-in-line chunk which will be used for allocation once the current one has been used up.
231      * This queue will be used by magazines to share any excess chunks they allocate, so that they don't need to
232      * allocate new chunks when their current and next-in-line chunks have both been used up.
233      * <p>
234      * The simplest implementation of this method is to return a new {@link ConcurrentLinkedQueue}.
235      * However, the {@code CLQ} is unbounded, and this means there's no limit to how many chunks can be cached in this
236      * queue.
237      * <p>
238      * Each chunk in this queue can be up to {@link #MAX_CHUNK_SIZE} in size, so it is recommended to use a bounded
239      * queue to limit the maximum memory usage.
240      * <p>
241      * The default implementation will create a bounded queue with a capacity of {@link #CHUNK_REUSE_QUEUE}.
242      *
243      * @return A new multi-producer, multi-consumer queue.
244      */
245     private static Queue<Chunk> createSharedChunkQueue() {
246         return PlatformDependent.newFixedMpmcQueue(CHUNK_REUSE_QUEUE);
247     }
248 
249     ByteBuf allocate(int size, int maxCapacity) {
250         return allocate(size, maxCapacity, Thread.currentThread(), null);
251     }
252 
253     private AdaptiveByteBuf allocate(int size, int maxCapacity, Thread currentThread, AdaptiveByteBuf buf) {
254         AdaptiveByteBuf allocated = null;
255         if (size <= MAX_POOLED_BUF_SIZE) {
256             final int index = sizeClassIndexOf(size);
257             MagazineGroup[] magazineGroups;
258             if (!FastThreadLocalThread.currentThreadWillCleanupFastThreadLocals() ||
259                     (magazineGroups = threadLocalGroup.get()) == null) {
260                 magazineGroups =  sizeClassedMagazineGroups;
261             }
262             if (index < magazineGroups.length) {
263                 allocated = magazineGroups[index].allocate(size, maxCapacity, currentThread, buf);
264             } else {
265                 allocated = largeBufferMagazineGroup.allocate(size, maxCapacity, currentThread, buf);
266             }
267         }
268         if (allocated == null) {
269             allocated = allocateFallback(size, maxCapacity, currentThread, buf);
270         }
271         return allocated;
272     }
273 
274     private static int sizeIndexOf(final int size) {
275         // this is aligning the size to the next multiple of 32 and dividing by 32 to get the size index.
276         return size + 31 >> 5;
277     }
278 
279     static int sizeClassIndexOf(int size) {
280         int sizeIndex = sizeIndexOf(size);
281         if (sizeIndex < SIZE_INDEXES.length) {
282             return SIZE_INDEXES[sizeIndex];
283         }
284         return SIZE_CLASSES_COUNT;
285     }
286 
287     static int[] getSizeClasses() {
288         return SIZE_CLASSES.clone();
289     }
290 
291     private AdaptiveByteBuf allocateFallback(int size, int maxCapacity, Thread currentThread,
292                                              AdaptiveByteBuf buf) {
293         // If we don't already have a buffer, obtain one from the most conveniently available magazine.
294         Magazine magazine;
295         if (buf != null) {
296             Chunk chunk = buf.chunk;
297             if (chunk == null || chunk == Magazine.MAGAZINE_FREED || (magazine = chunk.currentMagazine()) == null) {
298                 magazine = getFallbackMagazine(currentThread);
299             }
300         } else {
301             magazine = getFallbackMagazine(currentThread);
302             buf = magazine.newBuffer();
303         }
304         // Create a one-off chunk for this allocation.
305         AbstractByteBuf innerChunk = chunkAllocator.allocate(size, maxCapacity);
306         Chunk chunk = new Chunk(innerChunk, magazine, false, chunkSize -> true);
307         try {
308             chunk.readInitInto(buf, size, size, maxCapacity);
309         } finally {
310             // As the chunk is an one-off we need to always call release explicitly as readInitInto(...)
311             // will take care of retain once when successful. Once The AdaptiveByteBuf is released it will
312             // completely release the Chunk and so the contained innerChunk.
313             chunk.release();
314         }
315         return buf;
316     }
317 
318     private Magazine getFallbackMagazine(Thread currentThread) {
319         Magazine[] mags = largeBufferMagazineGroup.magazines;
320         return mags[(int) currentThread.getId() & mags.length - 1];
321     }
322 
323     /**
324      * Allocate into the given buffer. Used by {@link AdaptiveByteBuf#capacity(int)}.
325      */
326     void reallocate(int size, int maxCapacity, AdaptiveByteBuf into) {
327         AdaptiveByteBuf result = allocate(size, maxCapacity, Thread.currentThread(), into);
328         assert result == into: "Re-allocation created separate buffer instance";
329     }
330 
331     long usedMemory() {
332         long sum = 0;
333         for (Chunk chunk : chunkRegistry) {
334             sum += chunk.capacity();
335         }
336         return sum;
337     }
338 
339     // Ensure that we release all previous pooled resources when this object is finalized. This is needed as otherwise
340     // we might end up with leaks. While these leaks are usually harmless in reality it would still at least be
341     // very confusing for users.
342     @SuppressWarnings({"FinalizeDeclaration", "deprecation"})
343     @Override
344     protected void finalize() throws Throwable {
345         try {
346             super.finalize();
347         } finally {
348             free();
349         }
350     }
351 
352     private void free() {
353         largeBufferMagazineGroup.free();
354     }
355 
356     static int sizeToBucket(int size) {
357         return HistogramChunkController.sizeToBucket(size);
358     }
359 
360     private static final class MagazineGroup {
361         private final AdaptivePoolingAllocator allocator;
362         private final ChunkAllocator chunkAllocator;
363         private final ChunkControllerFactory chunkControllerFactory;
364         private final Queue<Chunk> chunkReuseQueue;
365         private final StampedLock magazineExpandLock;
366         private final Magazine threadLocalMagazine;
367         private volatile Magazine[] magazines;
368         private volatile boolean freed;
369 
370         MagazineGroup(AdaptivePoolingAllocator allocator,
371                       ChunkAllocator chunkAllocator,
372                       ChunkControllerFactory chunkControllerFactory,
373                       boolean isThreadLocal) {
374             this.allocator = allocator;
375             this.chunkAllocator = chunkAllocator;
376             this.chunkControllerFactory = chunkControllerFactory;
377             chunkReuseQueue = createSharedChunkQueue();
378             if (isThreadLocal) {
379                 magazineExpandLock = null;
380                 threadLocalMagazine = new Magazine(this, false, chunkReuseQueue, chunkControllerFactory.create(this));
381             } else {
382                 magazineExpandLock = new StampedLock();
383                 threadLocalMagazine = null;
384                 Magazine[] mags = new Magazine[INITIAL_MAGAZINES];
385                 for (int i = 0; i < mags.length; i++) {
386                     mags[i] = new Magazine(this, true, chunkReuseQueue, chunkControllerFactory.create(this));
387                 }
388                 magazines = mags;
389             }
390         }
391 
392         public AdaptiveByteBuf allocate(int size, int maxCapacity, Thread currentThread, AdaptiveByteBuf buf) {
393             boolean reallocate = buf != null;
394 
395             // Path for thread-local allocation.
396             Magazine tlMag = threadLocalMagazine;
397             if (tlMag != null) {
398                 if (buf == null) {
399                     buf = tlMag.newBuffer();
400                 }
401                 boolean allocated = tlMag.tryAllocate(size, maxCapacity, buf, reallocate);
402                 assert allocated : "Allocation of threadLocalMagazine must always succeed";
403                 return buf;
404             }
405 
406             // Path for concurrent allocation.
407             long threadId = currentThread.getId();
408             Magazine[] mags;
409             int expansions = 0;
410             do {
411                 mags = magazines;
412                 int mask = mags.length - 1;
413                 int index = (int) (threadId & mask);
414                 for (int i = 0, m = mags.length << 1; i < m; i++) {
415                     Magazine mag = mags[index + i & mask];
416                     if (buf == null) {
417                         buf = mag.newBuffer();
418                     }
419                     if (mag.tryAllocate(size, maxCapacity, buf, reallocate)) {
420                         // Was able to allocate.
421                         return buf;
422                     }
423                 }
424                 expansions++;
425             } while (expansions <= EXPANSION_ATTEMPTS && tryExpandMagazines(mags.length));
426 
427             // The magazines failed us; contention too high and we don't want to spend more effort expanding the array.
428             if (!reallocate && buf != null) {
429                 buf.release(); // Release the previously claimed buffer before we return.
430             }
431             return null;
432         }
433 
434         private boolean tryExpandMagazines(int currentLength) {
435             if (currentLength >= MAX_STRIPES) {
436                 return true;
437             }
438             final Magazine[] mags;
439             long writeLock = magazineExpandLock.tryWriteLock();
440             if (writeLock != 0) {
441                 try {
442                     mags = magazines;
443                     if (mags.length >= MAX_STRIPES || mags.length > currentLength || freed) {
444                         return true;
445                     }
446                     Magazine firstMagazine = mags[0];
447                     Magazine[] expanded = new Magazine[mags.length * 2];
448                     for (int i = 0, l = expanded.length; i < l; i++) {
449                         Magazine m = new Magazine(this, true, chunkReuseQueue, chunkControllerFactory.create(this));
450                         firstMagazine.initializeSharedStateIn(m);
451                         expanded[i] = m;
452                     }
453                     magazines = expanded;
454                 } finally {
455                     magazineExpandLock.unlockWrite(writeLock);
456                 }
457                 for (Magazine magazine : mags) {
458                     magazine.free();
459                 }
460             }
461             return true;
462         }
463 
464         boolean offerToQueue(Chunk buffer) {
465             if (freed) {
466                 return false;
467             }
468 
469             boolean isAdded = chunkReuseQueue.offer(buffer);
470             if (freed && isAdded) {
471                 // Help to free the reuse queue.
472                 freeChunkReuseQueue();
473             }
474             return isAdded;
475         }
476 
477         private void free() {
478             freed = true;
479             if (threadLocalMagazine != null) {
480                 threadLocalMagazine.free();
481             } else {
482                 long stamp = magazineExpandLock.writeLock();
483                 try {
484                     Magazine[] mags = magazines;
485                     for (Magazine magazine : mags) {
486                         magazine.free();
487                     }
488                 } finally {
489                     magazineExpandLock.unlockWrite(stamp);
490                 }
491             }
492             freeChunkReuseQueue();
493         }
494 
495         private void freeChunkReuseQueue() {
496             for (;;) {
497                 Chunk chunk = chunkReuseQueue.poll();
498                 if (chunk == null) {
499                     break;
500                 }
501                 chunk.release();
502             }
503         }
504     }
505 
506     private interface ChunkControllerFactory {
507         ChunkController create(MagazineGroup group);
508     }
509 
510     private interface ChunkController {
511         /**
512          * Compute the "fast max capacity" value for the buffer.
513          */
514         int computeBufferCapacity(int requestedSize, int maxCapacity, boolean isReallocation);
515 
516         /**
517          * Initialize the given chunk factory with shared statistics state (if any) from this factory.
518          */
519         void initializeSharedStateIn(ChunkController chunkController);
520 
521         /**
522          * Allocate a new {@link Chunk} for the given {@link Magazine}.
523          */
524         Chunk newChunkAllocation(int promptingSize, Magazine magazine);
525     }
526 
527     private interface ChunkReleasePredicate {
528         boolean shouldReleaseChunk(int chunkSize);
529     }
530 
531     private static final class SizeClassChunkControllerFactory implements ChunkControllerFactory {
532         // To amortize activation/deactivation of chunks, we should have a minimum number of segments per chunk.
533         // We choose 32 because it seems neither too small nor too big.
534         // For segments of 16 KiB, the chunks will be half a megabyte.
535         private static final int MIN_SEGMENTS_PER_CHUNK = 32;
536         private final int segmentSize;
537         private final int chunkSize;
538         private final int[] segmentOffsets;
539 
540         private SizeClassChunkControllerFactory(int segmentSize) {
541             this.segmentSize = ObjectUtil.checkPositive(segmentSize, "segmentSize");
542             this.chunkSize = Math.max(MIN_CHUNK_SIZE, segmentSize * MIN_SEGMENTS_PER_CHUNK);
543             int segmentsCount = chunkSize / segmentSize;
544             this.segmentOffsets = new int[segmentsCount];
545             for (int i = 0; i < segmentsCount; i++) {
546                 segmentOffsets[i] = i * segmentSize;
547             }
548         }
549 
550         @Override
551         public ChunkController create(MagazineGroup group) {
552             return new SizeClassChunkController(group, segmentSize, chunkSize, segmentOffsets);
553         }
554     }
555 
556     private static final class SizeClassChunkController implements ChunkController {
557 
558         private final ChunkAllocator chunkAllocator;
559         private final int segmentSize;
560         private final int chunkSize;
561         private final Set<Chunk> chunkRegistry;
562         private final int[] segmentOffsets;
563 
564         private SizeClassChunkController(MagazineGroup group, int segmentSize, int chunkSize, int[] segmentOffsets) {
565             chunkAllocator = group.chunkAllocator;
566             this.segmentSize = segmentSize;
567             this.chunkSize = chunkSize;
568             chunkRegistry = group.allocator.chunkRegistry;
569             this.segmentOffsets = segmentOffsets;
570         }
571 
572         @Override
573         public int computeBufferCapacity(
574                 int requestedSize, int maxCapacity, boolean isReallocation) {
575             return Math.min(segmentSize, maxCapacity);
576         }
577 
578         @Override
579         public void initializeSharedStateIn(ChunkController chunkController) {
580             // NOOP
581         }
582 
583         @Override
584         public Chunk newChunkAllocation(int promptingSize, Magazine magazine) {
585             AbstractByteBuf chunkBuffer = chunkAllocator.allocate(chunkSize, chunkSize);
586             assert chunkBuffer.capacity() == chunkSize;
587             SizeClassedChunk chunk = new SizeClassedChunk(chunkBuffer, magazine, true,
588                     segmentSize, segmentOffsets, size -> false);
589             chunkRegistry.add(chunk);
590             return chunk;
591         }
592     }
593 
594     private static final class HistogramChunkControllerFactory implements ChunkControllerFactory {
595         private final boolean shareable;
596 
597         private HistogramChunkControllerFactory(boolean shareable) {
598             this.shareable = shareable;
599         }
600 
601         @Override
602         public ChunkController create(MagazineGroup group) {
603             return new HistogramChunkController(group, shareable);
604         }
605     }
606 
607     private static final class HistogramChunkController implements ChunkController, ChunkReleasePredicate {
608         private static final int MIN_DATUM_TARGET = 1024;
609         private static final int MAX_DATUM_TARGET = 65534;
610         private static final int INIT_DATUM_TARGET = 9;
611         private static final int HISTO_BUCKET_COUNT = 16;
612         private static final int[] HISTO_BUCKETS = {
613                 16 * 1024,
614                 24 * 1024,
615                 32 * 1024,
616                 48 * 1024,
617                 64 * 1024,
618                 96 * 1024,
619                 128 * 1024,
620                 192 * 1024,
621                 256 * 1024,
622                 384 * 1024,
623                 512 * 1024,
624                 768 * 1024,
625                 1024 * 1024,
626                 1792 * 1024,
627                 2048 * 1024,
628                 3072 * 1024
629         };
630 
631         private final MagazineGroup group;
632         private final boolean shareable;
633         private final short[][] histos = {
634                 new short[HISTO_BUCKET_COUNT], new short[HISTO_BUCKET_COUNT],
635                 new short[HISTO_BUCKET_COUNT], new short[HISTO_BUCKET_COUNT],
636         };
637         private final Set<Chunk> chunkRegistry;
638         private short[] histo = histos[0];
639         private final int[] sums = new int[HISTO_BUCKET_COUNT];
640 
641         private int histoIndex;
642         private int datumCount;
643         private int datumTarget = INIT_DATUM_TARGET;
644         private boolean hasHadRotation;
645         private volatile int sharedPrefChunkSize = MIN_CHUNK_SIZE;
646         private volatile int localPrefChunkSize = MIN_CHUNK_SIZE;
647         private volatile int localUpperBufSize;
648 
649         private HistogramChunkController(MagazineGroup group, boolean shareable) {
650             this.group = group;
651             this.shareable = shareable;
652             chunkRegistry = group.allocator.chunkRegistry;
653         }
654 
655         @Override
656         public int computeBufferCapacity(
657                 int requestedSize, int maxCapacity, boolean isReallocation) {
658             if (!isReallocation) {
659                 // Only record allocation size if it's not caused by a reallocation that was triggered by capacity
660                 // change of the buffer.
661                 recordAllocationSize(requestedSize);
662             }
663 
664             // Predict starting capacity from localUpperBufSize, but place limits on the max starting capacity
665             // based on the requested size, because localUpperBufSize can potentially be quite large.
666             int startCapLimits;
667             if (requestedSize <= 32768) { // Less than or equal to 32 KiB.
668                 startCapLimits = 65536; // Use at most 64 KiB, which is also the AdaptiveRecvByteBufAllocator max.
669             } else {
670                 startCapLimits = requestedSize * 2; // Otherwise use at most twice the requested memory.
671             }
672             int startingCapacity = Math.min(startCapLimits, localUpperBufSize);
673             startingCapacity = Math.max(requestedSize, Math.min(maxCapacity, startingCapacity));
674             return startingCapacity;
675         }
676 
677         private void recordAllocationSize(int bufferSizeToRecord) {
678             // Use the preserved size from the reused AdaptiveByteBuf, if available.
679             // Otherwise, use the requested buffer size.
680             // This way, we better take into account
681             if (bufferSizeToRecord == 0) {
682                 return;
683             }
684             int bucket = sizeToBucket(bufferSizeToRecord);
685             histo[bucket]++;
686             if (datumCount++ == datumTarget) {
687                 rotateHistograms();
688             }
689         }
690 
691         static int sizeToBucket(int size) {
692             int index = binarySearchInsertionPoint(Arrays.binarySearch(HISTO_BUCKETS, size));
693             return index >= HISTO_BUCKETS.length ? HISTO_BUCKETS.length - 1 : index;
694         }
695 
696         private static int binarySearchInsertionPoint(int index) {
697             if (index < 0) {
698                 index = -(index + 1);
699             }
700             return index;
701         }
702 
703         static int bucketToSize(int sizeBucket) {
704             return HISTO_BUCKETS[sizeBucket];
705         }
706 
707         private void rotateHistograms() {
708             short[][] hs = histos;
709             for (int i = 0; i < HISTO_BUCKET_COUNT; i++) {
710                 sums[i] = (hs[0][i] & 0xFFFF) + (hs[1][i] & 0xFFFF) + (hs[2][i] & 0xFFFF) + (hs[3][i] & 0xFFFF);
711             }
712             int sum = 0;
713             for (int count : sums) {
714                 sum  += count;
715             }
716             int targetPercentile = (int) (sum * 0.99);
717             int sizeBucket = 0;
718             for (; sizeBucket < sums.length; sizeBucket++) {
719                 if (sums[sizeBucket] > targetPercentile) {
720                     break;
721                 }
722                 targetPercentile -= sums[sizeBucket];
723             }
724             hasHadRotation = true;
725             int percentileSize = bucketToSize(sizeBucket);
726             int prefChunkSize = Math.max(percentileSize * BUFS_PER_CHUNK, MIN_CHUNK_SIZE);
727             localUpperBufSize = percentileSize;
728             localPrefChunkSize = prefChunkSize;
729             if (shareable) {
730                 for (Magazine mag : group.magazines) {
731                     HistogramChunkController statistics = (HistogramChunkController) mag.chunkController;
732                     prefChunkSize = Math.max(prefChunkSize, statistics.localPrefChunkSize);
733                 }
734             }
735             if (sharedPrefChunkSize != prefChunkSize) {
736                 // Preferred chunk size changed. Increase check frequency.
737                 datumTarget = Math.max(datumTarget >> 1, MIN_DATUM_TARGET);
738                 sharedPrefChunkSize = prefChunkSize;
739             } else {
740                 // Preferred chunk size did not change. Check less often.
741                 datumTarget = Math.min(datumTarget << 1, MAX_DATUM_TARGET);
742             }
743 
744             histoIndex = histoIndex + 1 & 3;
745             histo = histos[histoIndex];
746             datumCount = 0;
747             Arrays.fill(histo, (short) 0);
748         }
749 
750         /**
751          * Get the preferred chunk size, based on statistics from the {@linkplain #recordAllocationSize(int) recorded}
752          * allocation sizes.
753          * <p>
754          * This method must be thread-safe.
755          *
756          * @return The currently preferred chunk allocation size.
757          */
758         int preferredChunkSize() {
759             return sharedPrefChunkSize;
760         }
761 
762         @Override
763         public void initializeSharedStateIn(ChunkController chunkController) {
764             HistogramChunkController statistics = (HistogramChunkController) chunkController;
765             int sharedPrefChunkSize = this.sharedPrefChunkSize;
766             statistics.localPrefChunkSize = sharedPrefChunkSize;
767             statistics.sharedPrefChunkSize = sharedPrefChunkSize;
768         }
769 
770         @Override
771         public Chunk newChunkAllocation(int promptingSize, Magazine magazine) {
772             int size = Math.max(promptingSize * BUFS_PER_CHUNK, preferredChunkSize());
773             int minChunks = size / MIN_CHUNK_SIZE;
774             if (MIN_CHUNK_SIZE * minChunks < size) {
775                 // Round up to nearest whole MIN_CHUNK_SIZE unit. The MIN_CHUNK_SIZE is an even multiple of many
776                 // popular small page sizes, like 4k, 16k, and 64k, which makes it easier for the system allocator
777                 // to manage the memory in terms of whole pages. This reduces memory fragmentation,
778                 // but without the potentially high overhead that power-of-2 chunk sizes would bring.
779                 size = MIN_CHUNK_SIZE * (1 + minChunks);
780             }
781 
782             // Limit chunks to the max size, even if the histogram suggests to go above it.
783             size = Math.min(size, MAX_CHUNK_SIZE);
784 
785             // If we haven't rotated the histogram yet, optimisticly record this chunk size as our preferred.
786             if (!hasHadRotation && sharedPrefChunkSize == MIN_CHUNK_SIZE) {
787                 sharedPrefChunkSize = size;
788             }
789 
790             ChunkAllocator chunkAllocator = group.chunkAllocator;
791             Chunk chunk = new Chunk(chunkAllocator.allocate(size, size), magazine, true, this);
792             chunkRegistry.add(chunk);
793             return chunk;
794         }
795 
796         @Override
797         public boolean shouldReleaseChunk(int chunkSize) {
798             int preferredSize = preferredChunkSize();
799             int givenChunks = chunkSize / MIN_CHUNK_SIZE;
800             int preferredChunks = preferredSize / MIN_CHUNK_SIZE;
801             int deviation = Math.abs(givenChunks - preferredChunks);
802 
803             // Retire chunks with a 5% probability per unit of MIN_CHUNK_SIZE deviation from preference.
804             return deviation != 0 &&
805                     ThreadLocalRandom.current().nextDouble() * 20.0 < deviation;
806         }
807     }
808 
809     private static final class Magazine {
810         private static final AtomicReferenceFieldUpdater<Magazine, Chunk> NEXT_IN_LINE;
811         static {
812             NEXT_IN_LINE = AtomicReferenceFieldUpdater.newUpdater(Magazine.class, Chunk.class, "nextInLine");
813         }
814         private static final Chunk MAGAZINE_FREED = new Chunk();
815 
816         private static final Recycler<AdaptiveByteBuf> EVENT_LOOP_LOCAL_BUFFER_POOL = new Recycler<AdaptiveByteBuf>() {
817             @Override
818             protected AdaptiveByteBuf newObject(Handle<AdaptiveByteBuf> handle) {
819                 return new AdaptiveByteBuf(handle);
820             }
821         };
822 
823         private Chunk current;
824         @SuppressWarnings("unused") // updated via NEXT_IN_LINE
825         private volatile Chunk nextInLine;
826         private final MagazineGroup group;
827         private final ChunkController chunkController;
828         private final AtomicLong usedMemory;
829         private final StampedLock allocationLock;
830         private final Queue<AdaptiveByteBuf> bufferQueue;
831         private final ObjectPool.Handle<AdaptiveByteBuf> handle;
832         private final Queue<Chunk> sharedChunkQueue;
833 
834         Magazine(MagazineGroup group, boolean shareable, Queue<Chunk> sharedChunkQueue,
835                  ChunkController chunkController) {
836             this.group = group;
837             this.chunkController = chunkController;
838 
839             if (shareable) {
840                 // We only need the StampedLock if this Magazine will be shared across threads.
841                 allocationLock = new StampedLock();
842                 bufferQueue = PlatformDependent.newFixedMpmcQueue(MAGAZINE_BUFFER_QUEUE_CAPACITY);
843                 handle = new ObjectPool.Handle<AdaptiveByteBuf>() {
844                     @Override
845                     public void recycle(AdaptiveByteBuf self) {
846                         bufferQueue.offer(self);
847                     }
848                 };
849             } else {
850                 allocationLock = null;
851                 bufferQueue = null;
852                 handle = null;
853             }
854             usedMemory = new AtomicLong();
855             this.sharedChunkQueue = sharedChunkQueue;
856         }
857 
858         public boolean tryAllocate(int size, int maxCapacity, AdaptiveByteBuf buf, boolean reallocate) {
859             if (allocationLock == null) {
860                 // This magazine is not shared across threads, just allocate directly.
861                 return allocate(size, maxCapacity, buf, reallocate);
862             }
863 
864             // Try to retrieve the lock and if successful allocate.
865             long writeLock = allocationLock.tryWriteLock();
866             if (writeLock != 0) {
867                 try {
868                     return allocate(size, maxCapacity, buf, reallocate);
869                 } finally {
870                     allocationLock.unlockWrite(writeLock);
871                 }
872             }
873             return allocateWithoutLock(size, maxCapacity, buf);
874         }
875 
876         private boolean allocateWithoutLock(int size, int maxCapacity, AdaptiveByteBuf buf) {
877             Chunk curr = NEXT_IN_LINE.getAndSet(this, null);
878             if (curr == MAGAZINE_FREED) {
879                 // Allocation raced with a stripe-resize that freed this magazine.
880                 restoreMagazineFreed();
881                 return false;
882             }
883             if (curr == null) {
884                 curr = sharedChunkQueue.poll();
885                 if (curr == null) {
886                     return false;
887                 }
888                 curr.attachToMagazine(this);
889             }
890             boolean allocated = false;
891             int remainingCapacity = curr.remainingCapacity();
892             int startingCapacity = chunkController.computeBufferCapacity(
893                     size, maxCapacity, true /* never update stats as we don't hold the magazine lock */);
894             if (remainingCapacity >= size) {
895                 curr.readInitInto(buf, size, Math.min(remainingCapacity, startingCapacity), maxCapacity);
896                 allocated = true;
897             }
898             try {
899                 if (remainingCapacity >= RETIRE_CAPACITY) {
900                     transferToNextInLineOrRelease(curr);
901                     curr = null;
902                 }
903             } finally {
904                 if (curr != null) {
905                     curr.releaseFromMagazine();
906                 }
907             }
908             return allocated;
909         }
910 
911         private boolean allocate(int size, int maxCapacity, AdaptiveByteBuf buf, boolean reallocate) {
912             int startingCapacity = chunkController.computeBufferCapacity(size, maxCapacity, reallocate);
913             Chunk curr = current;
914             if (curr != null) {
915                 // We have a Chunk that has some space left.
916                 int remainingCapacity = curr.remainingCapacity();
917                 if (remainingCapacity > startingCapacity) {
918                     curr.readInitInto(buf, size, startingCapacity, maxCapacity);
919                     // We still have some bytes left that we can use for the next allocation, just early return.
920                     return true;
921                 }
922 
923                 // At this point we know that this will be the last time current will be used, so directly set it to
924                 // null and release it once we are done.
925                 current = null;
926                 if (remainingCapacity >= size) {
927                     try {
928                         curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
929                         return true;
930                     } finally {
931                         curr.releaseFromMagazine();
932                     }
933                 }
934 
935                 // Check if we either retain the chunk in the nextInLine cache or releasing it.
936                 if (remainingCapacity < RETIRE_CAPACITY) {
937                     curr.releaseFromMagazine();
938                 } else {
939                     // See if it makes sense to transfer the Chunk to the nextInLine cache for later usage.
940                     // This method will release curr if this is not the case
941                     transferToNextInLineOrRelease(curr);
942                 }
943             }
944 
945             assert current == null;
946             // The fast-path for allocations did not work.
947             //
948             // Try to fetch the next "Magazine local" Chunk first, if this fails because we don't have a
949             // next-in-line chunk available, we will poll our centralQueue.
950             // If this fails as well we will just allocate a new Chunk.
951             //
952             // In any case we will store the Chunk as the current so it will be used again for the next allocation and
953             // thus be "reserved" by this Magazine for exclusive usage.
954             curr = NEXT_IN_LINE.getAndSet(this, null);
955             if (curr != null) {
956                 if (curr == MAGAZINE_FREED) {
957                     // Allocation raced with a stripe-resize that freed this magazine.
958                     restoreMagazineFreed();
959                     return false;
960                 }
961 
962                 int remainingCapacity = curr.remainingCapacity();
963                 if (remainingCapacity > startingCapacity) {
964                     // We have a Chunk that has some space left.
965                     curr.readInitInto(buf, size, startingCapacity, maxCapacity);
966                     current = curr;
967                     return true;
968                 }
969 
970                 if (remainingCapacity >= size) {
971                     // At this point we know that this will be the last time curr will be used, so directly set it to
972                     // null and release it once we are done.
973                     try {
974                         curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
975                         return true;
976                     } finally {
977                         // Release in a finally block so even if readInitInto(...) would throw we would still correctly
978                         // release the current chunk before null it out.
979                         curr.releaseFromMagazine();
980                     }
981                 } else {
982                     // Release it as it's too small.
983                     curr.releaseFromMagazine();
984                 }
985             }
986 
987             // Now try to poll from the central queue first
988             curr = sharedChunkQueue.poll();
989             if (curr == null) {
990                 curr = chunkController.newChunkAllocation(size, this);
991             } else {
992                 curr.attachToMagazine(this);
993 
994                 int remainingCapacity = curr.remainingCapacity();
995                 if (remainingCapacity == 0 || remainingCapacity < size) {
996                     // Check if we either retain the chunk in the nextInLine cache or releasing it.
997                     if (remainingCapacity < RETIRE_CAPACITY) {
998                         curr.releaseFromMagazine();
999                     } else {
1000                         // See if it makes sense to transfer the Chunk to the nextInLine cache for later usage.
1001                         // This method will release curr if this is not the case
1002                         transferToNextInLineOrRelease(curr);
1003                     }
1004                     curr = chunkController.newChunkAllocation(size, this);
1005                 }
1006             }
1007 
1008             current = curr;
1009             try {
1010                 int remainingCapacity = curr.remainingCapacity();
1011                 assert remainingCapacity >= size;
1012                 if (remainingCapacity > startingCapacity) {
1013                     curr.readInitInto(buf, size, startingCapacity, maxCapacity);
1014                     curr = null;
1015                 } else {
1016                     curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
1017                 }
1018             } finally {
1019                 if (curr != null) {
1020                     // Release in a finally block so even if readInitInto(...) would throw we would still correctly
1021                     // release the current chunk before null it out.
1022                     curr.releaseFromMagazine();
1023                     current = null;
1024                 }
1025             }
1026             return true;
1027         }
1028 
1029         private void restoreMagazineFreed() {
1030             Chunk next = NEXT_IN_LINE.getAndSet(this, MAGAZINE_FREED);
1031             if (next != null && next != MAGAZINE_FREED) {
1032                 // A chunk snuck in through a race. Release it after restoring MAGAZINE_FREED state.
1033                 next.releaseFromMagazine();
1034             }
1035         }
1036 
1037         private void transferToNextInLineOrRelease(Chunk chunk) {
1038             if (NEXT_IN_LINE.compareAndSet(this, null, chunk)) {
1039                 return;
1040             }
1041 
1042             Chunk nextChunk = NEXT_IN_LINE.get(this);
1043             if (nextChunk != null && nextChunk != MAGAZINE_FREED
1044                     && chunk.remainingCapacity() > nextChunk.remainingCapacity()) {
1045                 if (NEXT_IN_LINE.compareAndSet(this, nextChunk, chunk)) {
1046                     nextChunk.releaseFromMagazine();
1047                     return;
1048                 }
1049             }
1050             // Next-in-line is occupied. We don't try to add it to the central queue yet as it might still be used
1051             // by some buffers and so is attached to a Magazine.
1052             // Once a Chunk is completely released by Chunk.release() it will try to move itself to the queue
1053             // as last resort.
1054             chunk.releaseFromMagazine();
1055         }
1056 
1057         boolean trySetNextInLine(Chunk chunk) {
1058             return NEXT_IN_LINE.compareAndSet(this, null, chunk);
1059         }
1060 
1061         void free() {
1062             // Release the current Chunk and the next that was stored for later usage.
1063             restoreMagazineFreed();
1064             long stamp = allocationLock != null ? allocationLock.writeLock() : 0;
1065             try {
1066                 if (current != null) {
1067                     current.releaseFromMagazine();
1068                     current = null;
1069                 }
1070             } finally {
1071                 if (allocationLock != null) {
1072                     allocationLock.unlockWrite(stamp);
1073                 }
1074             }
1075         }
1076 
1077         public AdaptiveByteBuf newBuffer() {
1078             AdaptiveByteBuf buf;
1079             if (handle == null) {
1080                 buf = EVENT_LOOP_LOCAL_BUFFER_POOL.get();
1081             } else {
1082                 buf = bufferQueue.poll();
1083                 if (buf == null) {
1084                     buf = new AdaptiveByteBuf(handle);
1085                 }
1086             }
1087             buf.resetRefCnt();
1088             buf.discardMarks();
1089             return buf;
1090         }
1091 
1092         boolean offerToQueue(Chunk chunk) {
1093             return group.offerToQueue(chunk);
1094         }
1095 
1096         public void initializeSharedStateIn(Magazine other) {
1097             chunkController.initializeSharedStateIn(other.chunkController);
1098         }
1099     }
1100 
1101     private static class Chunk implements ReferenceCounted, ChunkInfo {
1102         private static final long REFCNT_FIELD_OFFSET;
1103         private static final AtomicIntegerFieldUpdater<Chunk> AIF_UPDATER;
1104         private static final Object REFCNT_FIELD_VH;
1105         private static final ReferenceCountUpdater<Chunk> updater;
1106 
1107         static {
1108             switch (ReferenceCountUpdater.updaterTypeOf(Chunk.class, "refCnt")) {
1109                 case Atomic:
1110                     AIF_UPDATER = newUpdater(Chunk.class, "refCnt");
1111                     REFCNT_FIELD_OFFSET = -1;
1112                     REFCNT_FIELD_VH = null;
1113                     updater = new AtomicReferenceCountUpdater<Chunk>() {
1114                         @Override
1115                         protected AtomicIntegerFieldUpdater<Chunk> updater() {
1116                             return AIF_UPDATER;
1117                         }
1118                     };
1119                     break;
1120                 case Unsafe:
1121                     AIF_UPDATER = null;
1122                     REFCNT_FIELD_OFFSET = getUnsafeOffset(Chunk.class, "refCnt");
1123                     REFCNT_FIELD_VH = null;
1124                     updater = new UnsafeReferenceCountUpdater<Chunk>() {
1125                         @Override
1126                         protected long refCntFieldOffset() {
1127                             return REFCNT_FIELD_OFFSET;
1128                         }
1129                     };
1130                     break;
1131                 case VarHandle:
1132                     AIF_UPDATER = null;
1133                     REFCNT_FIELD_OFFSET = -1;
1134                     REFCNT_FIELD_VH = PlatformDependent.findVarHandleOfIntField(MethodHandles.lookup(),
1135                             Chunk.class, "refCnt");
1136                     updater = new VarHandleReferenceCountUpdater<Chunk>() {
1137                         @Override
1138                         protected VarHandle varHandle() {
1139                             return (VarHandle) REFCNT_FIELD_VH;
1140                         }
1141                     };
1142                     break;
1143                 default:
1144                     throw new Error("Unknown updater type for Chunk");
1145             }
1146         }
1147 
1148         protected final AbstractByteBuf delegate;
1149         protected Magazine magazine;
1150         private final AdaptivePoolingAllocator allocator;
1151         private final ChunkReleasePredicate chunkReleasePredicate;
1152         private final int capacity;
1153         private final boolean pooled;
1154         protected int allocatedBytes;
1155 
1156         // Value might not equal "real" reference count, all access should be via the updater
1157         @SuppressWarnings({"unused", "FieldMayBeFinal"})
1158         private volatile int refCnt;
1159 
1160         Chunk() {
1161             // Constructor only used by the MAGAZINE_FREED sentinel.
1162             delegate = null;
1163             magazine = null;
1164             allocator = null;
1165             chunkReleasePredicate = null;
1166             capacity = 0;
1167             pooled = false;
1168         }
1169 
1170         Chunk(AbstractByteBuf delegate, Magazine magazine, boolean pooled,
1171               ChunkReleasePredicate chunkReleasePredicate) {
1172             this.delegate = delegate;
1173             this.pooled = pooled;
1174             capacity = delegate.capacity();
1175             updater.setInitialValue(this);
1176             attachToMagazine(magazine);
1177 
1178             // We need the top-level allocator so ByteBuf.capacity(int) can call reallocate()
1179             allocator = magazine.group.allocator;
1180 
1181             this.chunkReleasePredicate = chunkReleasePredicate;
1182 
1183             if (PlatformDependent.isJfrEnabled() && AllocateChunkEvent.isEventEnabled()) {
1184                 AllocateChunkEvent event = new AllocateChunkEvent();
1185                 if (event.shouldCommit()) {
1186                     event.fill(this, AdaptiveByteBufAllocator.class);
1187                     event.pooled = pooled;
1188                     event.threadLocal = magazine.allocationLock == null;
1189                     event.commit();
1190                 }
1191             }
1192         }
1193 
1194         Magazine currentMagazine()  {
1195             return magazine;
1196         }
1197 
1198         void detachFromMagazine() {
1199             if (magazine != null) {
1200                 magazine.usedMemory.getAndAdd(-capacity);
1201                 magazine = null;
1202             }
1203         }
1204 
1205         void attachToMagazine(Magazine magazine) {
1206             assert this.magazine == null;
1207             this.magazine = magazine;
1208             magazine.usedMemory.getAndAdd(capacity);
1209         }
1210 
1211         @Override
1212         public Chunk touch(Object hint) {
1213             return this;
1214         }
1215 
1216         @Override
1217         public int refCnt() {
1218             return updater.refCnt(this);
1219         }
1220 
1221         @Override
1222         public Chunk retain() {
1223             return updater.retain(this);
1224         }
1225 
1226         @Override
1227         public Chunk retain(int increment) {
1228             return updater.retain(this, increment);
1229         }
1230 
1231         @Override
1232         public Chunk touch() {
1233             return this;
1234         }
1235 
1236         @Override
1237         public boolean release() {
1238             if (updater.release(this)) {
1239                 deallocate();
1240                 return true;
1241             }
1242             return false;
1243         }
1244 
1245         @Override
1246         public boolean release(int decrement) {
1247             if (updater.release(this, decrement)) {
1248                 deallocate();
1249                 return true;
1250             }
1251             return false;
1252         }
1253 
1254         /**
1255          * Called when a magazine is done using this chunk, probably because it was emptied.
1256          */
1257         boolean releaseFromMagazine() {
1258             return release();
1259         }
1260 
1261         /**
1262          * Called when a ByteBuf is done using its allocation in this chunk.
1263          */
1264         boolean releaseSegment(int ignoredSegmentId) {
1265             return release();
1266         }
1267 
1268         private void deallocate() {
1269             Magazine mag = magazine;
1270             int chunkSize = delegate.capacity();
1271             if (!pooled || chunkReleasePredicate.shouldReleaseChunk(chunkSize) || mag == null) {
1272                 // Drop the chunk if the parent allocator is closed,
1273                 // or if the chunk deviates too much from the preferred chunk size.
1274                 detachFromMagazine();
1275                 onRelease();
1276                 allocator.chunkRegistry.remove(this);
1277                 delegate.release();
1278             } else {
1279                 updater.resetRefCnt(this);
1280                 delegate.setIndex(0, 0);
1281                 allocatedBytes = 0;
1282                 if (!mag.trySetNextInLine(this)) {
1283                     // As this Chunk does not belong to the mag anymore we need to decrease the used memory .
1284                     detachFromMagazine();
1285                     if (!mag.offerToQueue(this)) {
1286                         // The central queue is full. Ensure we release again as we previously did use resetRefCnt()
1287                         // which did increase the reference count by 1.
1288                         boolean released = updater.release(this);
1289                         onRelease();
1290                         allocator.chunkRegistry.remove(this);
1291                         delegate.release();
1292                         assert released;
1293                     } else {
1294                         onReturn(false);
1295                     }
1296                 } else {
1297                     onReturn(true);
1298                 }
1299             }
1300         }
1301 
1302         private void onReturn(boolean returnedToMagazine) {
1303             if (PlatformDependent.isJfrEnabled() && ReturnChunkEvent.isEventEnabled()) {
1304                 ReturnChunkEvent event = new ReturnChunkEvent();
1305                 if (event.shouldCommit()) {
1306                     event.fill(this, AdaptiveByteBufAllocator.class);
1307                     event.returnedToMagazine = returnedToMagazine;
1308                     event.commit();
1309                 }
1310             }
1311         }
1312 
1313         private void onRelease() {
1314             if (PlatformDependent.isJfrEnabled() && FreeChunkEvent.isEventEnabled()) {
1315                 FreeChunkEvent event = new FreeChunkEvent();
1316                 if (event.shouldCommit()) {
1317                     event.fill(this, AdaptiveByteBufAllocator.class);
1318                     event.pooled = pooled;
1319                     event.commit();
1320                 }
1321             }
1322         }
1323 
1324         public void readInitInto(AdaptiveByteBuf buf, int size, int startingCapacity, int maxCapacity) {
1325             int startIndex = allocatedBytes;
1326             allocatedBytes = startIndex + startingCapacity;
1327             Chunk chunk = this;
1328             chunk.retain();
1329             try {
1330                 buf.init(delegate, chunk, 0, 0, startIndex, size, startingCapacity, maxCapacity);
1331                 chunk = null;
1332             } finally {
1333                 if (chunk != null) {
1334                     // If chunk is not null we know that buf.init(...) failed and so we need to manually release
1335                     // the chunk again as we retained it before calling buf.init(...). Beside this we also need to
1336                     // restore the old allocatedBytes value.
1337                     allocatedBytes = startIndex;
1338                     chunk.release();
1339                 }
1340             }
1341         }
1342 
1343         public int remainingCapacity() {
1344             return capacity - allocatedBytes;
1345         }
1346 
1347         @Override
1348         public int capacity() {
1349             return capacity;
1350         }
1351 
1352         @Override
1353         public boolean isDirect() {
1354             return delegate.isDirect();
1355         }
1356 
1357         @Override
1358         public long memoryAddress() {
1359             return delegate._memoryAddress();
1360         }
1361     }
1362 
1363     private static final class SizeClassedChunk extends Chunk {
1364         private static final int FREE_LIST_EMPTY = -1;
1365         private final int segmentSize;
1366         private final MpscIntQueue freeList;
1367 
1368         SizeClassedChunk(AbstractByteBuf delegate, Magazine magazine, boolean pooled, int segmentSize,
1369                          int[] segmentOffsets, ChunkReleasePredicate shouldReleaseChunk) {
1370             super(delegate, magazine, pooled, shouldReleaseChunk);
1371             this.segmentSize = segmentSize;
1372             int segmentCount = segmentOffsets.length;
1373             assert delegate.capacity() / segmentSize == segmentCount;
1374             assert segmentCount > 0: "Chunk must have a positive number of segments";
1375             freeList = MpscIntQueue.create(segmentCount, FREE_LIST_EMPTY);
1376             freeList.fill(segmentCount, new IntSupplier() {
1377                 int counter;
1378                 @Override
1379                 public int getAsInt() {
1380                     return segmentOffsets[counter++];
1381                 }
1382             });
1383         }
1384 
1385         @Override
1386         public void readInitInto(AdaptiveByteBuf buf, int size, int startingCapacity, int maxCapacity) {
1387             int startIndex = freeList.poll();
1388             if (startIndex == FREE_LIST_EMPTY) {
1389                 throw new IllegalStateException("Free list is empty");
1390             }
1391             allocatedBytes += segmentSize;
1392             Chunk chunk = this;
1393             chunk.retain();
1394             try {
1395                 buf.init(delegate, chunk, 0, 0, startIndex, size, startingCapacity, maxCapacity);
1396                 chunk = null;
1397             } finally {
1398                 if (chunk != null) {
1399                     // If chunk is not null we know that buf.init(...) failed and so we need to manually release
1400                     // the chunk again as we retained it before calling buf.init(...). Beside this we also need to
1401                     // restore the old allocatedBytes value.
1402                     allocatedBytes -= segmentSize;
1403                     chunk.releaseSegment(startIndex);
1404                 }
1405             }
1406         }
1407 
1408         @Override
1409         public int remainingCapacity() {
1410             int remainingCapacity = super.remainingCapacity();
1411             if (remainingCapacity > segmentSize) {
1412                 return remainingCapacity;
1413             }
1414             int updatedRemainingCapacity = freeList.size() * segmentSize;
1415             if (updatedRemainingCapacity == remainingCapacity) {
1416                 return remainingCapacity;
1417             }
1418             // update allocatedBytes based on what's available in the free list
1419             allocatedBytes = capacity() - updatedRemainingCapacity;
1420             return updatedRemainingCapacity;
1421         }
1422 
1423         @Override
1424         boolean releaseFromMagazine() {
1425             // Size-classed chunks can be reused before they become empty.
1426             // We can therefor put them in the shared queue as soon as the magazine is done with this chunk.
1427             Magazine mag = magazine;
1428             detachFromMagazine();
1429             if (!mag.offerToQueue(this)) {
1430                 return super.releaseFromMagazine();
1431             }
1432             return false;
1433         }
1434 
1435         @Override
1436         boolean releaseSegment(int startIndex) {
1437             boolean released = release();
1438             boolean segmentReturned = freeList.offer(startIndex);
1439             assert segmentReturned: "Unable to return segment " + startIndex + " to free list";
1440             return released;
1441         }
1442     }
1443 
1444     static final class AdaptiveByteBuf extends AbstractReferenceCountedByteBuf {
1445 
1446         private final ObjectPool.Handle<AdaptiveByteBuf> handle;
1447 
1448         // this both act as adjustment and the start index for a free list segment allocation
1449         private int startIndex;
1450         private AbstractByteBuf rootParent;
1451         Chunk chunk;
1452         private int length;
1453         private int maxFastCapacity;
1454         private ByteBuffer tmpNioBuf;
1455         private boolean hasArray;
1456         private boolean hasMemoryAddress;
1457 
1458         AdaptiveByteBuf(ObjectPool.Handle<AdaptiveByteBuf> recyclerHandle) {
1459             super(0);
1460             handle = ObjectUtil.checkNotNull(recyclerHandle, "recyclerHandle");
1461         }
1462 
1463         void init(AbstractByteBuf unwrapped, Chunk wrapped, int readerIndex, int writerIndex,
1464                   int startIndex, int size, int capacity, int maxCapacity) {
1465             this.startIndex = startIndex;
1466             chunk = wrapped;
1467             length = size;
1468             maxFastCapacity = capacity;
1469             maxCapacity(maxCapacity);
1470             setIndex0(readerIndex, writerIndex);
1471             hasArray = unwrapped.hasArray();
1472             hasMemoryAddress = unwrapped.hasMemoryAddress();
1473             rootParent = unwrapped;
1474             tmpNioBuf = null;
1475 
1476             if (PlatformDependent.isJfrEnabled() && AllocateBufferEvent.isEventEnabled()) {
1477                 AllocateBufferEvent event = new AllocateBufferEvent();
1478                 if (event.shouldCommit()) {
1479                     event.fill(this, AdaptiveByteBufAllocator.class);
1480                     event.chunkPooled = wrapped.pooled;
1481                     Magazine m = wrapped.magazine;
1482                     event.chunkThreadLocal = m != null && m.allocationLock == null;
1483                     event.commit();
1484                 }
1485             }
1486         }
1487 
1488         private AbstractByteBuf rootParent() {
1489             final AbstractByteBuf rootParent = this.rootParent;
1490             if (rootParent != null) {
1491                 return rootParent;
1492             }
1493             throw new IllegalReferenceCountException();
1494         }
1495 
1496         @Override
1497         public int capacity() {
1498             return length;
1499         }
1500 
1501         @Override
1502         public int maxFastWritableBytes() {
1503             return Math.min(maxFastCapacity, maxCapacity()) - writerIndex;
1504         }
1505 
1506         @Override
1507         public ByteBuf capacity(int newCapacity) {
1508             if (length <= newCapacity && newCapacity <= maxFastCapacity) {
1509                 ensureAccessible();
1510                 length = newCapacity;
1511                 return this;
1512             }
1513             checkNewCapacity(newCapacity);
1514             if (newCapacity < capacity()) {
1515                 length = newCapacity;
1516                 trimIndicesToCapacity(newCapacity);
1517                 return this;
1518             }
1519 
1520             if (PlatformDependent.isJfrEnabled() && ReallocateBufferEvent.isEventEnabled()) {
1521                 ReallocateBufferEvent event = new ReallocateBufferEvent();
1522                 if (event.shouldCommit()) {
1523                     event.fill(this, AdaptiveByteBufAllocator.class);
1524                     event.newCapacity = newCapacity;
1525                     event.commit();
1526                 }
1527             }
1528 
1529             // Reallocation required.
1530             Chunk chunk = this.chunk;
1531             AdaptivePoolingAllocator allocator = chunk.allocator;
1532             int readerIndex = this.readerIndex;
1533             int writerIndex = this.writerIndex;
1534             int baseOldRootIndex = startIndex;
1535             int oldCapacity = length;
1536             AbstractByteBuf oldRoot = rootParent();
1537             allocator.reallocate(newCapacity, maxCapacity(), this);
1538             oldRoot.getBytes(baseOldRootIndex, this, 0, oldCapacity);
1539             chunk.releaseSegment(baseOldRootIndex);
1540             this.readerIndex = readerIndex;
1541             this.writerIndex = writerIndex;
1542             return this;
1543         }
1544 
1545         @Override
1546         public ByteBufAllocator alloc() {
1547             return rootParent().alloc();
1548         }
1549 
1550         @Override
1551         public ByteOrder order() {
1552             return rootParent().order();
1553         }
1554 
1555         @Override
1556         public ByteBuf unwrap() {
1557             return null;
1558         }
1559 
1560         @Override
1561         public boolean isDirect() {
1562             return rootParent().isDirect();
1563         }
1564 
1565         @Override
1566         public int arrayOffset() {
1567             return idx(rootParent().arrayOffset());
1568         }
1569 
1570         @Override
1571         public boolean hasMemoryAddress() {
1572             return hasMemoryAddress;
1573         }
1574 
1575         @Override
1576         public long memoryAddress() {
1577             ensureAccessible();
1578             return _memoryAddress();
1579         }
1580 
1581         @Override
1582         long _memoryAddress() {
1583             AbstractByteBuf root = rootParent;
1584             return root != null ? root._memoryAddress() + startIndex : 0L;
1585         }
1586 
1587         @Override
1588         public ByteBuffer nioBuffer(int index, int length) {
1589             checkIndex(index, length);
1590             return rootParent().nioBuffer(idx(index), length);
1591         }
1592 
1593         @Override
1594         public ByteBuffer internalNioBuffer(int index, int length) {
1595             checkIndex(index, length);
1596             return (ByteBuffer) internalNioBuffer().position(index).limit(index + length);
1597         }
1598 
1599         private ByteBuffer internalNioBuffer() {
1600             if (tmpNioBuf == null) {
1601                 tmpNioBuf = rootParent().nioBuffer(startIndex, maxFastCapacity);
1602             }
1603             return (ByteBuffer) tmpNioBuf.clear();
1604         }
1605 
1606         @Override
1607         public ByteBuffer[] nioBuffers(int index, int length) {
1608             checkIndex(index, length);
1609             return rootParent().nioBuffers(idx(index), length);
1610         }
1611 
1612         @Override
1613         public boolean hasArray() {
1614             return hasArray;
1615         }
1616 
1617         @Override
1618         public byte[] array() {
1619             ensureAccessible();
1620             return rootParent().array();
1621         }
1622 
1623         @Override
1624         public ByteBuf copy(int index, int length) {
1625             checkIndex(index, length);
1626             return rootParent().copy(idx(index), length);
1627         }
1628 
1629         @Override
1630         public int nioBufferCount() {
1631             return rootParent().nioBufferCount();
1632         }
1633 
1634         @Override
1635         protected byte _getByte(int index) {
1636             return rootParent()._getByte(idx(index));
1637         }
1638 
1639         @Override
1640         protected short _getShort(int index) {
1641             return rootParent()._getShort(idx(index));
1642         }
1643 
1644         @Override
1645         protected short _getShortLE(int index) {
1646             return rootParent()._getShortLE(idx(index));
1647         }
1648 
1649         @Override
1650         protected int _getUnsignedMedium(int index) {
1651             return rootParent()._getUnsignedMedium(idx(index));
1652         }
1653 
1654         @Override
1655         protected int _getUnsignedMediumLE(int index) {
1656             return rootParent()._getUnsignedMediumLE(idx(index));
1657         }
1658 
1659         @Override
1660         protected int _getInt(int index) {
1661             return rootParent()._getInt(idx(index));
1662         }
1663 
1664         @Override
1665         protected int _getIntLE(int index) {
1666             return rootParent()._getIntLE(idx(index));
1667         }
1668 
1669         @Override
1670         protected long _getLong(int index) {
1671             return rootParent()._getLong(idx(index));
1672         }
1673 
1674         @Override
1675         protected long _getLongLE(int index) {
1676             return rootParent()._getLongLE(idx(index));
1677         }
1678 
1679         @Override
1680         public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
1681             checkIndex(index, length);
1682             rootParent().getBytes(idx(index), dst, dstIndex, length);
1683             return this;
1684         }
1685 
1686         @Override
1687         public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
1688             checkIndex(index, length);
1689             rootParent().getBytes(idx(index), dst, dstIndex, length);
1690             return this;
1691         }
1692 
1693         @Override
1694         public ByteBuf getBytes(int index, ByteBuffer dst) {
1695             checkIndex(index, dst.remaining());
1696             rootParent().getBytes(idx(index), dst);
1697             return this;
1698         }
1699 
1700         @Override
1701         protected void _setByte(int index, int value) {
1702             rootParent()._setByte(idx(index), value);
1703         }
1704 
1705         @Override
1706         protected void _setShort(int index, int value) {
1707             rootParent()._setShort(idx(index), value);
1708         }
1709 
1710         @Override
1711         protected void _setShortLE(int index, int value) {
1712             rootParent()._setShortLE(idx(index), value);
1713         }
1714 
1715         @Override
1716         protected void _setMedium(int index, int value) {
1717             rootParent()._setMedium(idx(index), value);
1718         }
1719 
1720         @Override
1721         protected void _setMediumLE(int index, int value) {
1722             rootParent()._setMediumLE(idx(index), value);
1723         }
1724 
1725         @Override
1726         protected void _setInt(int index, int value) {
1727             rootParent()._setInt(idx(index), value);
1728         }
1729 
1730         @Override
1731         protected void _setIntLE(int index, int value) {
1732             rootParent()._setIntLE(idx(index), value);
1733         }
1734 
1735         @Override
1736         protected void _setLong(int index, long value) {
1737             rootParent()._setLong(idx(index), value);
1738         }
1739 
1740         @Override
1741         protected void _setLongLE(int index, long value) {
1742             rootParent().setLongLE(idx(index), value);
1743         }
1744 
1745         @Override
1746         public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
1747             checkIndex(index, length);
1748             ByteBuffer tmp = (ByteBuffer) internalNioBuffer().clear().position(index);
1749             tmp.put(src, srcIndex, length);
1750             return this;
1751         }
1752 
1753         @Override
1754         public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
1755             checkIndex(index, length);
1756             ByteBuffer tmp = (ByteBuffer) internalNioBuffer().clear().position(index);
1757             tmp.put(src.nioBuffer(srcIndex, length));
1758             return this;
1759         }
1760 
1761         @Override
1762         public ByteBuf setBytes(int index, ByteBuffer src) {
1763             checkIndex(index, src.remaining());
1764             ByteBuffer tmp = (ByteBuffer) internalNioBuffer().clear().position(index);
1765             tmp.put(src);
1766             return this;
1767         }
1768 
1769         @Override
1770         public ByteBuf getBytes(int index, OutputStream out, int length)
1771                 throws IOException {
1772             checkIndex(index, length);
1773             if (length != 0) {
1774                 ByteBufUtil.readBytes(alloc(), internalNioBuffer().duplicate(), index, length, out);
1775             }
1776             return this;
1777         }
1778 
1779         @Override
1780         public int getBytes(int index, GatheringByteChannel out, int length)
1781                 throws IOException {
1782             ByteBuffer buf = internalNioBuffer().duplicate();
1783             buf.clear().position(index).limit(index + length);
1784             return out.write(buf);
1785         }
1786 
1787         @Override
1788         public int getBytes(int index, FileChannel out, long position, int length)
1789                 throws IOException {
1790             ByteBuffer buf = internalNioBuffer().duplicate();
1791             buf.clear().position(index).limit(index + length);
1792             return out.write(buf, position);
1793         }
1794 
1795         @Override
1796         public int setBytes(int index, InputStream in, int length)
1797                 throws IOException {
1798             checkIndex(index, length);
1799             final AbstractByteBuf rootParent = rootParent();
1800             if (rootParent.hasArray()) {
1801                 return rootParent.setBytes(idx(index), in, length);
1802             }
1803             byte[] tmp = ByteBufUtil.threadLocalTempArray(length);
1804             int readBytes = in.read(tmp, 0, length);
1805             if (readBytes <= 0) {
1806                 return readBytes;
1807             }
1808             setBytes(index, tmp, 0, readBytes);
1809             return readBytes;
1810         }
1811 
1812         @Override
1813         public int setBytes(int index, ScatteringByteChannel in, int length)
1814                 throws IOException {
1815             try {
1816                 return in.read(internalNioBuffer(index, length).duplicate());
1817             } catch (ClosedChannelException ignored) {
1818                 return -1;
1819             }
1820         }
1821 
1822         @Override
1823         public int setBytes(int index, FileChannel in, long position, int length)
1824                 throws IOException {
1825             try {
1826                 return in.read(internalNioBuffer(index, length).duplicate(), position);
1827             } catch (ClosedChannelException ignored) {
1828                 return -1;
1829             }
1830         }
1831 
1832         @Override
1833         public int setCharSequence(int index, CharSequence sequence, Charset charset) {
1834             return setCharSequence0(index, sequence, charset, false);
1835         }
1836 
1837         private int setCharSequence0(int index, CharSequence sequence, Charset charset, boolean expand) {
1838             if (charset.equals(CharsetUtil.UTF_8)) {
1839                 int length = ByteBufUtil.utf8MaxBytes(sequence);
1840                 if (expand) {
1841                     ensureWritable0(length);
1842                     checkIndex0(index, length);
1843                 } else {
1844                     checkIndex(index, length);
1845                 }
1846                 // Directly pass in the rootParent() with the adjusted index
1847                 return ByteBufUtil.writeUtf8(rootParent(), idx(index), length, sequence, sequence.length());
1848             }
1849             if (charset.equals(CharsetUtil.US_ASCII) || charset.equals(CharsetUtil.ISO_8859_1)) {
1850                 int length = sequence.length();
1851                 if (expand) {
1852                     ensureWritable0(length);
1853                     checkIndex0(index, length);
1854                 } else {
1855                     checkIndex(index, length);
1856                 }
1857                 // Directly pass in the rootParent() with the adjusted index
1858                 return ByteBufUtil.writeAscii(rootParent(), idx(index), sequence, length);
1859             }
1860             byte[] bytes = sequence.toString().getBytes(charset);
1861             if (expand) {
1862                 ensureWritable0(bytes.length);
1863                 // setBytes(...) will take care of checking the indices.
1864             }
1865             setBytes(index, bytes);
1866             return bytes.length;
1867         }
1868 
1869         @Override
1870         public int writeCharSequence(CharSequence sequence, Charset charset) {
1871             int written = setCharSequence0(writerIndex, sequence, charset, true);
1872             writerIndex += written;
1873             return written;
1874         }
1875 
1876         @Override
1877         public int forEachByte(int index, int length, ByteProcessor processor) {
1878             checkIndex(index, length);
1879             int ret = rootParent().forEachByte(idx(index), length, processor);
1880             return forEachResult(ret);
1881         }
1882 
1883         @Override
1884         public int forEachByteDesc(int index, int length, ByteProcessor processor) {
1885             checkIndex(index, length);
1886             int ret = rootParent().forEachByteDesc(idx(index), length, processor);
1887             return forEachResult(ret);
1888         }
1889 
1890         @Override
1891         public ByteBuf setZero(int index, int length) {
1892             checkIndex(index, length);
1893             rootParent().setZero(idx(index), length);
1894             return this;
1895         }
1896 
1897         @Override
1898         public ByteBuf writeZero(int length) {
1899             ensureWritable(length);
1900             rootParent().setZero(idx(writerIndex), length);
1901             writerIndex += length;
1902             return this;
1903         }
1904 
1905         private int forEachResult(int ret) {
1906             if (ret < startIndex) {
1907                 return -1;
1908             }
1909             return ret - startIndex;
1910         }
1911 
1912         @Override
1913         public boolean isContiguous() {
1914             return rootParent().isContiguous();
1915         }
1916 
1917         private int idx(int index) {
1918             return index + startIndex;
1919         }
1920 
1921         @Override
1922         protected void deallocate() {
1923             if (PlatformDependent.isJfrEnabled() && FreeBufferEvent.isEventEnabled()) {
1924                 FreeBufferEvent event = new FreeBufferEvent();
1925                 if (event.shouldCommit()) {
1926                     event.fill(this, AdaptiveByteBufAllocator.class);
1927                     event.commit();
1928                 }
1929             }
1930 
1931             if (chunk != null) {
1932                 chunk.releaseSegment(startIndex);
1933             }
1934             tmpNioBuf = null;
1935             chunk = null;
1936             rootParent = null;
1937             if (handle instanceof EnhancedHandle) {
1938                 EnhancedHandle<AdaptiveByteBuf>  enhancedHandle = (EnhancedHandle<AdaptiveByteBuf>) handle;
1939                 enhancedHandle.unguardedRecycle(this);
1940             } else {
1941                 handle.recycle(this);
1942             }
1943         }
1944     }
1945 
1946     /**
1947      * The strategy for how {@link AdaptivePoolingAllocator} should allocate chunk buffers.
1948      */
1949     interface ChunkAllocator {
1950         /**
1951          * Allocate a buffer for a chunk. This can be any kind of {@link AbstractByteBuf} implementation.
1952          * @param initialCapacity The initial capacity of the returned {@link AbstractByteBuf}.
1953          * @param maxCapacity The maximum capacity of the returned {@link AbstractByteBuf}.
1954          * @return The buffer that represents the chunk memory.
1955          */
1956         AbstractByteBuf allocate(int initialCapacity, int maxCapacity);
1957     }
1958 }