1
2
3
4
5
6
7
8
9
10
11
12
13
14
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.concurrent.ConcurrentSkipListIntObjMultimap;
25 import io.netty.util.concurrent.ConcurrentSkipListIntObjMultimap.IntEntry;
26 import io.netty.util.concurrent.FastThreadLocal;
27 import io.netty.util.concurrent.FastThreadLocalThread;
28 import io.netty.util.concurrent.MpscIntQueue;
29 import io.netty.util.internal.MathUtil;
30 import io.netty.util.internal.ObjectUtil;
31 import io.netty.util.internal.PlatformDependent;
32 import io.netty.util.internal.RefCnt;
33 import io.netty.util.internal.SystemPropertyUtil;
34 import io.netty.util.internal.ThreadExecutorMap;
35 import io.netty.util.internal.UnstableApi;
36
37 import java.io.IOException;
38 import java.io.InputStream;
39 import java.io.OutputStream;
40 import java.nio.ByteBuffer;
41 import java.nio.ByteOrder;
42 import java.nio.channels.ClosedChannelException;
43 import java.nio.channels.FileChannel;
44 import java.nio.channels.GatheringByteChannel;
45 import java.nio.channels.ScatteringByteChannel;
46 import java.nio.charset.Charset;
47 import java.util.ArrayList;
48 import java.util.Arrays;
49 import java.util.Iterator;
50 import java.util.Queue;
51 import java.util.concurrent.atomic.AtomicInteger;
52 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
53 import java.util.concurrent.atomic.AtomicLong;
54 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
55 import java.util.concurrent.atomic.LongAdder;
56 import java.util.concurrent.locks.StampedLock;
57 import java.util.function.IntConsumer;
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84 @UnstableApi
85 final class AdaptivePoolingAllocator {
86 private static final int LOW_MEM_THRESHOLD = 512 * 1024 * 1024;
87 private static final boolean IS_LOW_MEM = SystemPropertyUtil.getBoolean(
88 "io.netty.allocator.lowMemory",
89 Runtime.getRuntime().maxMemory() <= LOW_MEM_THRESHOLD);
90
91
92
93
94
95 private static final boolean DISABLE_THREAD_LOCAL_MAGAZINES_ON_LOW_MEM = SystemPropertyUtil.getBoolean(
96 "io.netty.allocator.disableThreadLocalMagazinesOnLowMemory", true);
97
98
99
100
101
102
103
104
105 static final int MIN_CHUNK_SIZE = 128 * 1024;
106 private static final int EXPANSION_ATTEMPTS = 3;
107 private static final int INITIAL_MAGAZINES = 1;
108 private static final int RETIRE_CAPACITY = 256;
109 private static final int MAX_STRIPES = IS_LOW_MEM ? 1 : NettyRuntime.availableProcessors() * 2;
110 private static final int BUFS_PER_CHUNK = 8;
111
112
113
114
115
116
117 private static final int MAX_CHUNK_SIZE = IS_LOW_MEM ?
118 2 * 1024 * 1024 :
119 8 * 1024 * 1024;
120 private static final int MAX_POOLED_BUF_SIZE = MAX_CHUNK_SIZE / BUFS_PER_CHUNK;
121
122
123
124
125
126
127 static final int CHUNK_REUSE_QUEUE = Math.max(2, SystemPropertyUtil.getInt(
128 "io.netty.allocator.chunkReuseQueueCapacity", NettyRuntime.availableProcessors() * 2));
129
130 static final long CHUNK_PURGE_POLLS_THREAD_LOCAL = Math.max(1, SystemPropertyUtil.getLong(
131 "io.netty.allocator.chunkPurgePollsThreadLocal", 16L));
132
133 static final long CHUNK_PURGE_POLLS_SHARED = Math.max(1, SystemPropertyUtil.getLong(
134 "io.netty.allocator.chunkPurgePollsShared", 128L));
135
136 static final int CHUNK_PURGE_THRESHOLD = Math.max(1, SystemPropertyUtil.getInt(
137 "io.netty.allocator.chunkPurgeThreshold", 3));
138
139
140
141
142
143
144
145
146 static final int THREAD_LOCAL_CACHE_MAX_BYTES = Math.max(1, SystemPropertyUtil.getInt(
147 "io.netty.allocator.threadLocalChunkCacheMaxBytes", 8 * 1024 * 1024));
148
149
150
151
152
153
154
155 static final int THREAD_LOCAL_CACHE_MIN_BYTES = Math.min(THREAD_LOCAL_CACHE_MAX_BYTES,
156 Math.max(1, SystemPropertyUtil.getInt(
157 "io.netty.allocator.threadLocalChunkCacheMinBytes",
158 THREAD_LOCAL_CACHE_MAX_BYTES / 2)));
159
160
161
162
163
164 private static final int MAGAZINE_BUFFER_QUEUE_CAPACITY = SystemPropertyUtil.getInt(
165 "io.netty.allocator.magazineBufferQueueCapacity", 1024);
166
167
168
169
170
171
172
173
174
175
176
177
178
179 private static final int[] SIZE_CLASSES = {
180 32,
181 64,
182 128,
183 256,
184 512,
185 640,
186 1024,
187 1152,
188 2048,
189 2304,
190 4096,
191 4352,
192 8192,
193 8704,
194 16384,
195 16896,
196 };
197
198 private static final int SIZE_CLASSES_COUNT = SIZE_CLASSES.length;
199 private static final byte[] SIZE_INDEXES = new byte[SIZE_CLASSES[SIZE_CLASSES_COUNT - 1] / 32 + 1];
200
201 static {
202 if (MAGAZINE_BUFFER_QUEUE_CAPACITY < 2) {
203 throw new IllegalArgumentException("MAGAZINE_BUFFER_QUEUE_CAPACITY: " + MAGAZINE_BUFFER_QUEUE_CAPACITY
204 + " (expected: >= " + 2 + ')');
205 }
206 int lastIndex = 0;
207 for (int i = 0; i < SIZE_CLASSES_COUNT; i++) {
208 int sizeClass = SIZE_CLASSES[i];
209
210 assert (sizeClass & 31) == 0 : "Size class must be a multiple of 32";
211 int sizeIndex = sizeIndexOf(sizeClass);
212 Arrays.fill(SIZE_INDEXES, lastIndex + 1, sizeIndex + 1, (byte) i);
213 lastIndex = sizeIndex;
214 }
215 }
216
217 private final ChunkAllocator chunkAllocator;
218 private final ChunkRegistry chunkRegistry;
219 private final MagazineGroup[] sizeClassedMagazineGroups;
220 private final MagazineGroup largeBufferMagazineGroup;
221 private final FastThreadLocal<MagazineGroup[]> threadLocalGroup;
222
223 AdaptivePoolingAllocator(ChunkAllocator chunkAllocator, boolean useCacheForNonEventLoopThreads) {
224 if (PlatformDependent.isJfrEnabled()) {
225 JfrEventRegistration.init();
226 }
227 this.chunkAllocator = ObjectUtil.checkNotNull(chunkAllocator, "chunkAllocator");
228 chunkRegistry = new ChunkRegistry();
229 sizeClassedMagazineGroups = createMagazineGroupSizeClasses(this, false);
230 largeBufferMagazineGroup = new MagazineGroup(
231 this, chunkAllocator, new BuddyChunkManagementStrategy(), false);
232
233 boolean disableThreadLocalGroups = IS_LOW_MEM && DISABLE_THREAD_LOCAL_MAGAZINES_ON_LOW_MEM;
234 threadLocalGroup = disableThreadLocalGroups ? null : new FastThreadLocal<MagazineGroup[]>() {
235 @Override
236 protected MagazineGroup[] initialValue() {
237 if (useCacheForNonEventLoopThreads || ThreadExecutorMap.currentExecutor() != null) {
238 return createMagazineGroupSizeClasses(AdaptivePoolingAllocator.this, true);
239 }
240 return null;
241 }
242
243 @Override
244 protected void onRemoval(final MagazineGroup[] groups) throws Exception {
245 if (groups != null) {
246 for (MagazineGroup group : groups) {
247 group.free();
248 }
249 }
250 }
251 };
252 }
253
254 private static MagazineGroup[] createMagazineGroupSizeClasses(
255 AdaptivePoolingAllocator allocator, boolean isThreadLocal) {
256 MagazineGroup[] groups = new MagazineGroup[SIZE_CLASSES.length];
257 for (int i = 0; i < SIZE_CLASSES.length; i++) {
258 int segmentSize = SIZE_CLASSES[i];
259 groups[i] = new MagazineGroup(allocator, allocator.chunkAllocator,
260 new SizeClassChunkManagementStrategy(segmentSize), isThreadLocal);
261 }
262 return groups;
263 }
264
265 ByteBuf allocate(int size, int maxCapacity) {
266 return allocate(size, maxCapacity, Thread.currentThread(), null);
267 }
268
269 private AdaptiveByteBuf allocate(int size, int maxCapacity, Thread currentThread, AdaptiveByteBuf buf) {
270 AdaptiveByteBuf allocated = null;
271 if (size <= MAX_POOLED_BUF_SIZE) {
272 final int index = sizeClassIndexOf(size);
273 MagazineGroup[] magazineGroups;
274 if (!FastThreadLocalThread.currentThreadWillCleanupFastThreadLocals() ||
275 IS_LOW_MEM ||
276 (magazineGroups = threadLocalGroup.get()) == null) {
277 magazineGroups = sizeClassedMagazineGroups;
278 }
279 if (index < magazineGroups.length) {
280 allocated = magazineGroups[index].allocate(size, maxCapacity, currentThread, buf);
281 } else if (!IS_LOW_MEM) {
282 allocated = largeBufferMagazineGroup.allocate(size, maxCapacity, currentThread, buf);
283 }
284 }
285 if (allocated == null) {
286 allocated = allocateFallback(size, maxCapacity, currentThread, buf);
287 }
288 return allocated;
289 }
290
291 private static int sizeIndexOf(final int size) {
292
293 return size + 31 >> 5;
294 }
295
296 static int sizeClassIndexOf(int size) {
297 int sizeIndex = sizeIndexOf(size);
298 if (sizeIndex < SIZE_INDEXES.length) {
299 return SIZE_INDEXES[sizeIndex];
300 }
301 return SIZE_CLASSES_COUNT;
302 }
303
304 static int[] getSizeClasses() {
305 return SIZE_CLASSES.clone();
306 }
307
308 private AdaptiveByteBuf allocateFallback(int size, int maxCapacity, Thread currentThread, AdaptiveByteBuf buf) {
309
310 Magazine magazine;
311 if (buf != null) {
312 Chunk chunk = buf.chunk;
313 if (chunk == null || chunk == Magazine.MAGAZINE_FREED || (magazine = chunk.currentMagazine()) == null) {
314 magazine = getFallbackMagazine(currentThread);
315 }
316 } else {
317 magazine = getFallbackMagazine(currentThread);
318 buf = magazine.newBuffer();
319 }
320
321 AbstractByteBuf innerChunk = chunkAllocator.allocate(size, maxCapacity);
322 Chunk chunk = new Chunk(innerChunk, magazine, false);
323 chunkRegistry.add(chunk);
324 try {
325 boolean success = chunk.readInitInto(buf, size, size, maxCapacity);
326 assert success : "Failed to initialize ByteBuf with dedicated chunk";
327 } finally {
328
329
330
331 chunk.release();
332 }
333 return buf;
334 }
335
336 private Magazine getFallbackMagazine(Thread currentThread) {
337 Magazine[] mags = largeBufferMagazineGroup.magazines;
338 return mags[(int) currentThread.getId() & mags.length - 1];
339 }
340
341
342
343
344 void reallocate(int size, int maxCapacity, AdaptiveByteBuf into) {
345 AdaptiveByteBuf result = allocate(size, maxCapacity, Thread.currentThread(), into);
346 assert result == into : "Re-allocation created separate buffer instance";
347 }
348
349 long usedMemory() {
350 return chunkRegistry.totalCapacity();
351 }
352
353
354
355
356 @SuppressWarnings({"FinalizeDeclaration", "deprecation"})
357 @Override
358 protected void finalize() throws Throwable {
359 try {
360 free();
361 } finally {
362 super.finalize();
363 }
364 }
365
366 private void free() {
367 largeBufferMagazineGroup.free();
368 }
369
370 private static final class MagazineGroup {
371 private final AdaptivePoolingAllocator allocator;
372 private final ChunkAllocator chunkAllocator;
373 private final ChunkManagementStrategy chunkManagementStrategy;
374 private final ChunkCache chunkCache;
375 private final StampedLock magazineExpandLock;
376 private final Magazine threadLocalMagazine;
377 private Thread ownerThread;
378 private volatile Magazine[] magazines;
379 private volatile boolean freed;
380
381 MagazineGroup(AdaptivePoolingAllocator allocator,
382 ChunkAllocator chunkAllocator,
383 ChunkManagementStrategy chunkManagementStrategy,
384 boolean isThreadLocal) {
385 this.allocator = allocator;
386 this.chunkAllocator = chunkAllocator;
387 this.chunkManagementStrategy = chunkManagementStrategy;
388 chunkCache = chunkManagementStrategy.createChunkCache(isThreadLocal);
389 if (isThreadLocal) {
390 ownerThread = Thread.currentThread();
391 magazineExpandLock = null;
392 threadLocalMagazine = new Magazine(this, false, chunkManagementStrategy.createController(this));
393 } else {
394 ownerThread = null;
395 magazineExpandLock = new StampedLock();
396 threadLocalMagazine = null;
397 Magazine[] mags = new Magazine[INITIAL_MAGAZINES];
398 for (int i = 0; i < mags.length; i++) {
399 mags[i] = new Magazine(this, true, chunkManagementStrategy.createController(this));
400 }
401 magazines = mags;
402 }
403 }
404
405 public AdaptiveByteBuf allocate(int size, int maxCapacity, Thread currentThread, AdaptiveByteBuf buf) {
406 boolean reallocate = buf != null;
407
408
409 Magazine tlMag = threadLocalMagazine;
410 if (tlMag != null) {
411 if (buf == null) {
412 buf = tlMag.newBuffer();
413 }
414 boolean allocated = tlMag.tryAllocate(size, maxCapacity, buf, reallocate);
415 assert allocated : "Allocation of threadLocalMagazine must always succeed";
416 return buf;
417 }
418
419
420 long threadId = currentThread.getId();
421 Magazine[] mags;
422 int expansions = 0;
423 do {
424 mags = magazines;
425 int mask = mags.length - 1;
426 int index = (int) (threadId & mask);
427 for (int i = 0, m = mags.length << 1; i < m; i++) {
428 Magazine mag = mags[index + i & mask];
429 if (buf == null) {
430 buf = mag.newBuffer();
431 }
432 if (mag.tryAllocate(size, maxCapacity, buf, reallocate)) {
433
434 return buf;
435 }
436 }
437 expansions++;
438 } while (expansions <= EXPANSION_ATTEMPTS && tryExpandMagazines(mags.length));
439
440
441 if (!reallocate && buf != null) {
442 buf.release();
443 }
444 return null;
445 }
446
447 private boolean tryExpandMagazines(int currentLength) {
448 if (currentLength >= MAX_STRIPES) {
449 return true;
450 }
451 final Magazine[] mags;
452 long writeLock = magazineExpandLock.tryWriteLock();
453 if (writeLock != 0) {
454 try {
455 mags = magazines;
456 if (mags.length >= MAX_STRIPES || mags.length > currentLength || freed) {
457 return true;
458 }
459 Magazine[] expanded = new Magazine[mags.length * 2];
460 for (int i = 0, l = expanded.length; i < l; i++) {
461 expanded[i] = new Magazine(this, true, chunkManagementStrategy.createController(this));
462 }
463 magazines = expanded;
464 } finally {
465 magazineExpandLock.unlockWrite(writeLock);
466 }
467 for (Magazine magazine : mags) {
468 magazine.free();
469 }
470 }
471 return true;
472 }
473
474 Chunk pollChunk(int size) {
475 return chunkCache.pollChunk(size);
476 }
477
478 boolean offerChunk(Chunk chunk) {
479 if (freed) {
480 return false;
481 }
482
483 if (chunk.hasUnprocessedFreelistEntries()) {
484 chunk.processFreelistEntries();
485 }
486 boolean isAdded = chunkCache.offerChunk(chunk);
487
488 if (freed && isAdded) {
489
490 freeChunkReuseQueue(ownerThread);
491 }
492 return isAdded;
493 }
494
495 private void free() {
496 freed = true;
497 Thread ownerThread = this.ownerThread;
498 if (threadLocalMagazine != null) {
499 this.ownerThread = null;
500 threadLocalMagazine.free();
501 } else {
502 long stamp = magazineExpandLock.writeLock();
503 try {
504 Magazine[] mags = magazines;
505 for (Magazine magazine : mags) {
506 magazine.free();
507 }
508 } finally {
509 magazineExpandLock.unlockWrite(stamp);
510 }
511 }
512 freeChunkReuseQueue(ownerThread);
513 }
514
515 private void freeChunkReuseQueue(Thread ownerThread) {
516 if (ownerThread != null && chunkCache instanceof ThreadLocalSizeClassedChunkCache) {
517 ThreadLocalSizeClassedChunkCache tlCache = (ThreadLocalSizeClassedChunkCache) chunkCache;
518 int mask = tlCache.chunks.length - 1;
519 for (int i = 0; i < tlCache.count; i++) {
520 SizeClassedChunk chunk = tlCache.chunks[(tlCache.head + i) & mask];
521 assert ownerThread == chunk.ownerThread;
522 chunk.ownerThread = null;
523 }
524 }
525 chunkCache.free();
526 }
527 }
528
529 interface ChunkCache {
530 Chunk pollChunk(int size);
531
532 boolean offerChunk(Chunk chunk);
533
534 void free();
535
536 boolean isEmpty();
537 }
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567 abstract static class SizeClassedChunkCache implements ChunkCache {
568 static SizeClassedChunkCache create(boolean isThreadLocal, int chunkSize) {
569 return isThreadLocal ? new ThreadLocalSizeClassedChunkCache(chunkSize) :
570 new SharedSizeClassedChunkCache();
571 }
572
573 @Override
574 public abstract SizeClassedChunk pollChunk(int size);
575
576
577 abstract SizeClassedChunk forcePurge();
578 }
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669 static final class ThreadLocalSizeClassedChunkCache extends SizeClassedChunkCache {
670 SizeClassedChunk[] chunks;
671 int head;
672 int tail;
673 int count;
674 int notEmptyCount;
675 private long purgeBudget;
676 final int maxCachedChunks;
677 final int purgeRetentionFloor;
678
679 ThreadLocalSizeClassedChunkCache(int chunkSize) {
680 chunks = new SizeClassedChunk[8];
681 purgeBudget = CHUNK_PURGE_POLLS_THREAD_LOCAL;
682 maxCachedChunks = Math.max(1, THREAD_LOCAL_CACHE_MAX_BYTES / chunkSize);
683 purgeRetentionFloor = Math.min(maxCachedChunks,
684 Math.max(1, THREAD_LOCAL_CACHE_MIN_BYTES / chunkSize));
685 }
686
687 @Override
688 SizeClassedChunk forcePurge() {
689 purgeBudget = 1;
690 return pollChunk(0);
691 }
692
693 @Override
694 public SizeClassedChunk pollChunk(int size) {
695 if (--purgeBudget == 0) {
696 runPurgeScan();
697 }
698 return scanForCapacity();
699 }
700
701 private SizeClassedChunk scanForCapacity() {
702 if (notEmptyCount > 0) {
703 SizeClassedChunk chunk = chunks[head];
704 assert chunk.hasRemainingCapacity();
705 chunk.purgeEpoch = 0;
706 chunks[head] = null;
707 head = (head + 1) & (chunks.length - 1);
708 count--;
709 notEmptyCount--;
710 return chunk;
711 }
712 return scanForCapacityFallback();
713 }
714
715 private SizeClassedChunk scanForCapacityFallback() {
716 int mask = chunks.length - 1;
717 int emptyCount = count - notEmptyCount;
718 int pos = (head + notEmptyCount) & mask;
719 for (int i = 0; i < emptyCount; i++) {
720 SizeClassedChunk chunk = chunks[pos];
721 if (chunk.hasRemainingCapacity()) {
722 chunk.purgeEpoch = 0;
723 int lastIdx = (tail - 1) & mask;
724 chunks[pos] = chunks[lastIdx];
725 chunks[lastIdx] = null;
726 tail = lastIdx;
727 count--;
728 return chunk;
729 }
730 pos = (pos + 1) & mask;
731 }
732 return null;
733 }
734
735 private void runPurgeScan() {
736 int mask = chunks.length - 1;
737 int kept = 0;
738 int survivors = count;
739 for (int i = 0; i < count; i++) {
740 int readIdx = (head + i) & mask;
741 SizeClassedChunk chunk = chunks[readIdx];
742 if (chunk.purgeEpoch > 0) {
743 assert chunk.hasFullCapacity();
744 chunk.purgeEpoch++;
745 if (chunk.purgeEpoch > CHUNK_PURGE_THRESHOLD && survivors > purgeRetentionFloor) {
746 chunk.markToDeallocate();
747 chunks[readIdx] = null;
748 survivors--;
749 continue;
750 }
751 } else if (chunk.hasFullCapacity()) {
752 chunk.purgeEpoch = 1;
753 }
754 int writeIdx = (head + kept) & mask;
755 if (writeIdx != readIdx) {
756 chunks[writeIdx] = chunk;
757 chunks[readIdx] = null;
758 }
759 kept++;
760 }
761 tail = (head + kept) & mask;
762 count = kept;
763 partition(kept);
764 purgeBudget = CHUNK_PURGE_POLLS_THREAD_LOCAL;
765 }
766
767 private void partition(int size) {
768 int mask = chunks.length - 1;
769
770 int lo = 0;
771 int hi = size - 1;
772 while (lo <= hi) {
773 int loIdx = (head + lo) & mask;
774 if (chunks[loIdx].hasRemainingCapacity()) {
775 lo++;
776 } else {
777 int hiIdx = (head + hi) & mask;
778 SizeClassedChunk tmp = chunks[loIdx];
779 chunks[loIdx] = chunks[hiIdx];
780 chunks[hiIdx] = tmp;
781 hi--;
782 }
783 }
784 notEmptyCount = lo;
785
786
787
788
789
790
791
792
793
794
795
796 int elo = 0;
797 int emid = 0;
798 int ehi = lo - 1;
799 while (emid <= ehi) {
800 int emidIdx = (head + emid) & mask;
801 SizeClassedChunk c = chunks[emidIdx];
802 if (c.purgeEpoch == 0) {
803 if (elo != emid) {
804 int eloIdx = (head + elo) & mask;
805 chunks[emidIdx] = chunks[eloIdx];
806 chunks[eloIdx] = c;
807 }
808 elo++;
809 emid++;
810 } else if (c.purgeEpoch < CHUNK_PURGE_THRESHOLD) {
811 emid++;
812 } else {
813 int ehiIdx = (head + ehi) & mask;
814 chunks[emidIdx] = chunks[ehiIdx];
815 chunks[ehiIdx] = c;
816 ehi--;
817 }
818 }
819 }
820
821 @Override
822 public boolean offerChunk(Chunk chunk) {
823 if (count >= maxCachedChunks) {
824 return false;
825 }
826 if (count == chunks.length) {
827 SizeClassedChunk[] newChunks = new SizeClassedChunk[chunks.length * 2];
828 for (int i = 0; i < count; i++) {
829 newChunks[i] = chunks[(head + i) & (chunks.length - 1)];
830 }
831 chunks = newChunks;
832 head = 0;
833 tail = count;
834 }
835 chunks[tail] = (SizeClassedChunk) chunk;
836 tail = (tail + 1) & (chunks.length - 1);
837 count++;
838 return true;
839 }
840
841 @Override
842 public String toString() {
843 int mask = chunks.length - 1;
844 StringBuilder sb = new StringBuilder();
845 sb.append("ThreadLocalCache[head=").append(head)
846 .append(", tail=").append(tail)
847 .append(", count=").append(count)
848 .append(", notEmpty=").append(notEmptyCount)
849 .append(", length=").append(chunks.length)
850 .append("]\n ");
851 for (int i = 0; i < count; i++) {
852 if (i > 0) {
853 sb.append(", ");
854 }
855 if (i == notEmptyCount) {
856 sb.append("| ");
857 }
858 SizeClassedChunk c = chunks[(head + i) & mask];
859 String region = i < notEmptyCount ? "notEmpty" : "empty";
860 String actual = c == null ? "null" :
861 c.hasRemainingCapacity() ? "hasCap" : "noCap";
862 sb.append('[').append(region).append(':').append(actual)
863 .append(",ep=").append(c == null ? -1 : c.purgeEpoch).append(']');
864 }
865 return sb.toString();
866 }
867
868 @Override
869 public void free() {
870 int mask = chunks.length - 1;
871 for (int i = 0; i < count; i++) {
872 int idx = (head + i) & mask;
873 chunks[idx].markToDeallocate();
874 chunks[idx] = null;
875 }
876 head = 0;
877 tail = 0;
878 count = 0;
879 notEmptyCount = 0;
880 }
881
882 @Override
883 public boolean isEmpty() {
884 return count == 0;
885 }
886 }
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920 static final class SharedSizeClassedChunkCache extends SizeClassedChunkCache {
921
922
923 private static final int SHARED_CACHE_CAPACITY = Math.max(16, CHUNK_REUSE_QUEUE * 2);
924 private final Queue<SizeClassedChunk> queue;
925 private final AtomicLong purgeBudget;
926 private final ArrayList<SizeClassedChunk> deferredBuffer = new ArrayList<>();
927 private long purgeGeneration;
928 private final AtomicLong scanGeneration = new AtomicLong();
929
930 SharedSizeClassedChunkCache() {
931 queue = PlatformDependent.newFixedMpmcQueue(SHARED_CACHE_CAPACITY);
932 purgeBudget = new AtomicLong(CHUNK_PURGE_POLLS_SHARED);
933 }
934
935 @Override
936 SizeClassedChunk forcePurge() {
937 purgeBudget.set(1);
938 return pollChunk(0);
939 }
940
941 @Override
942 public SizeClassedChunk pollChunk(int size) {
943 long budget = purgeBudget.decrementAndGet();
944 if (budget == 0) {
945 runPurgeScan();
946 }
947 return scanForCapacity();
948 }
949
950 private SizeClassedChunk scanForCapacity() {
951 SizeClassedChunk first = queue.poll();
952 if (first == null) {
953 return null;
954 }
955 if (first.purgeEpoch == 0 && first.hasRemainingCapacity()) {
956 return first;
957 }
958 long generation = scanGeneration.incrementAndGet();
959 first.lastScanGeneration = generation;
960 if (first.hasRemainingCapacity()) {
961 return scanForCapacitySlow(generation, first);
962 }
963 offerOrDeallocate(first);
964 return scanForCapacitySlow(generation, null);
965 }
966
967 private SizeClassedChunk scanForCapacitySlow(long generation, SizeClassedChunk fallback) {
968 SizeClassedChunk chunk;
969 while ((chunk = queue.poll()) != null) {
970 if (chunk.lastScanGeneration >= generation) {
971 offerOrDeallocate(chunk);
972 break;
973 }
974 if (chunk.hasRemainingCapacity()) {
975 if (chunk.purgeEpoch == 0) {
976 if (fallback != null) {
977 offerOrDeallocate(fallback);
978 }
979 return chunk;
980 }
981 if (fallback == null) {
982 fallback = chunk;
983 continue;
984 }
985 }
986 chunk.lastScanGeneration = generation;
987 offerOrDeallocate(chunk);
988 }
989 if (fallback != null) {
990 fallback.purgeEpoch = 0;
991 return fallback;
992 }
993 return null;
994 }
995
996 private boolean offerOrDeallocate(SizeClassedChunk chunk) {
997 if (!queue.offer(chunk)) {
998 chunk.markToDeallocate();
999 return false;
1000 }
1001 return true;
1002 }
1003
1004 private boolean offerOrDeallocate(SizeClassedChunk chunk, long generation) {
1005 chunk.lastPurgeGeneration = generation;
1006 return offerOrDeallocate(chunk);
1007 }
1008
1009 private void runPurgeScan() {
1010 long generation = ++purgeGeneration;
1011 int retained = 0;
1012 ArrayList<SizeClassedChunk> deferred = deferredBuffer;
1013 SizeClassedChunk chunk;
1014 while ((chunk = queue.poll()) != null) {
1015 if (chunk.lastPurgeGeneration == generation) {
1016 offerOrDeallocate(chunk, generation);
1017 break;
1018 }
1019 retained++;
1020 if (chunk.hasFullCapacity()) {
1021 chunk.purgeEpoch++;
1022 if (chunk.purgeEpoch > CHUNK_PURGE_THRESHOLD) {
1023 deferred.add(chunk);
1024 continue;
1025 }
1026 } else {
1027 chunk.purgeEpoch = 0;
1028 }
1029 int remaining = chunk.remainingCapacity();
1030 if (remaining > 0) {
1031 if (!offerOrDeallocate(chunk, generation)) {
1032 retained--;
1033 }
1034 } else {
1035 deferred.add(chunk);
1036 }
1037 }
1038 for (int i = 0, size = deferred.size(); i < size; i++) {
1039 chunk = deferred.get(i);
1040 if (chunk.purgeEpoch > CHUNK_PURGE_THRESHOLD && retained > CHUNK_REUSE_QUEUE) {
1041 chunk.markToDeallocate();
1042 retained--;
1043 } else {
1044 if (!offerOrDeallocate(chunk, generation)) {
1045 retained--;
1046 }
1047 }
1048 }
1049 deferred.clear();
1050 purgeBudget.lazySet(CHUNK_PURGE_POLLS_SHARED);
1051 }
1052
1053 @Override
1054 public boolean offerChunk(Chunk chunk) {
1055 return queue.offer((SizeClassedChunk) chunk);
1056 }
1057
1058 @Override
1059 public void free() {
1060 SizeClassedChunk chunk;
1061 while ((chunk = queue.poll()) != null) {
1062 chunk.markToDeallocate();
1063 }
1064 }
1065
1066 @Override
1067 public boolean isEmpty() {
1068 return queue.isEmpty();
1069 }
1070 }
1071
1072 private static final class ConcurrentSkipListChunkCache implements ChunkCache {
1073 private final ConcurrentSkipListIntObjMultimap<Chunk> chunks;
1074
1075 private ConcurrentSkipListChunkCache() {
1076 chunks = new ConcurrentSkipListIntObjMultimap<>(-1);
1077 }
1078
1079 @Override
1080 public Chunk pollChunk(int size) {
1081 if (chunks.isEmpty()) {
1082 return null;
1083 }
1084 IntEntry<Chunk> entry = chunks.pollCeilingEntry(size);
1085 if (entry != null) {
1086 Chunk chunk = entry.getValue();
1087 if (chunk.hasUnprocessedFreelistEntries()) {
1088 chunk.processFreelistEntries();
1089 }
1090 return chunk;
1091 }
1092
1093 Chunk bestChunk = null;
1094 int bestRemainingCapacity = 0;
1095 Iterator<IntEntry<Chunk>> itr = chunks.iterator();
1096 while (itr.hasNext()) {
1097 entry = itr.next();
1098 final Chunk chunk;
1099 if (entry != null && (chunk = entry.getValue()).hasUnprocessedFreelistEntries()) {
1100 if (!chunks.remove(entry.getKey(), entry.getValue())) {
1101 continue;
1102 }
1103 chunk.processFreelistEntries();
1104 int remainingCapacity = chunk.remainingCapacity();
1105 if (remainingCapacity >= size &&
1106 (bestChunk == null || remainingCapacity > bestRemainingCapacity)) {
1107 if (bestChunk != null) {
1108 chunks.put(bestRemainingCapacity, bestChunk);
1109 }
1110 bestChunk = chunk;
1111 bestRemainingCapacity = remainingCapacity;
1112 } else {
1113 chunks.put(remainingCapacity, chunk);
1114 }
1115 }
1116 }
1117
1118 return bestChunk;
1119 }
1120
1121 @Override
1122 public boolean offerChunk(Chunk chunk) {
1123 chunks.put(chunk.remainingCapacity(), chunk);
1124
1125 int size = chunks.size();
1126 while (size > CHUNK_REUSE_QUEUE) {
1127 int key = -1;
1128 Chunk toDeallocate = null;
1129 for (IntEntry<Chunk> entry : chunks) {
1130 Chunk candidate = entry.getValue();
1131 if (candidate != null && RefCnt.refCnt(candidate.refCnt) == 1) {
1132 toDeallocate = candidate;
1133 key = entry.getKey();
1134 break;
1135 }
1136 }
1137 if (toDeallocate == null) {
1138 break;
1139 }
1140 if (chunks.remove(key, toDeallocate)) {
1141 toDeallocate.markToDeallocate();
1142 }
1143 size = chunks.size();
1144 }
1145 return true;
1146 }
1147
1148 @Override
1149 public void free() {
1150 for (IntEntry<Chunk> entry : chunks) {
1151 Chunk chunk = entry.getValue();
1152 if (chunk != null && chunks.remove(entry.getKey(), chunk)) {
1153 chunk.markToDeallocate();
1154 }
1155 }
1156 }
1157
1158 @Override
1159 public boolean isEmpty() {
1160 return chunks.isEmpty();
1161 }
1162 }
1163
1164 private interface ChunkManagementStrategy {
1165 ChunkController createController(MagazineGroup group);
1166
1167 ChunkCache createChunkCache(boolean isThreadLocal);
1168 }
1169
1170 private interface ChunkController {
1171
1172
1173
1174 int computeBufferCapacity(int requestedSize, int maxCapacity, boolean isReallocation);
1175
1176
1177
1178
1179 Chunk newChunkAllocation(int promptingSize, Magazine magazine);
1180 }
1181
1182 private static final class SizeClassChunkManagementStrategy implements ChunkManagementStrategy {
1183
1184
1185
1186 private static final int MIN_SEGMENTS_PER_CHUNK = 32;
1187 private final int segmentSize;
1188 private final int chunkSize;
1189
1190 private SizeClassChunkManagementStrategy(int segmentSize) {
1191 this.segmentSize = ObjectUtil.checkPositive(segmentSize, "segmentSize");
1192 chunkSize = Math.max(MIN_CHUNK_SIZE, segmentSize * MIN_SEGMENTS_PER_CHUNK);
1193 }
1194
1195 @Override
1196 public ChunkController createController(MagazineGroup group) {
1197 return new SizeClassChunkController(group, segmentSize, chunkSize);
1198 }
1199
1200 @Override
1201 public ChunkCache createChunkCache(boolean isThreadLocal) {
1202 return SizeClassedChunkCache.create(isThreadLocal, chunkSize);
1203 }
1204 }
1205
1206 private static final class SizeClassChunkController implements ChunkController {
1207
1208 private final ChunkAllocator chunkAllocator;
1209 private final int segmentSize;
1210 private final int chunkSize;
1211 private final ChunkRegistry chunkRegistry;
1212
1213 private SizeClassChunkController(MagazineGroup group, int segmentSize, int chunkSize) {
1214 chunkAllocator = group.chunkAllocator;
1215 this.segmentSize = segmentSize;
1216 this.chunkSize = chunkSize;
1217 chunkRegistry = group.allocator.chunkRegistry;
1218 }
1219
1220 private MpscIntQueue createEmptyFreeList() {
1221 return MpscIntQueue.create(chunkSize / segmentSize, SizeClassedChunk.FREE_LIST_EMPTY);
1222 }
1223
1224 private MpscIntQueue createFreeList() {
1225 final int segmentsCount = chunkSize / segmentSize;
1226 final MpscIntQueue freeList = MpscIntQueue.create(segmentsCount, SizeClassedChunk.FREE_LIST_EMPTY);
1227 int segmentOffset = 0;
1228 for (int i = 0; i < segmentsCount; i++) {
1229 freeList.offer(segmentOffset);
1230 segmentOffset += segmentSize;
1231 }
1232 return freeList;
1233 }
1234
1235 private IntStack createLocalFreeList() {
1236 final int segmentsCount = chunkSize / segmentSize;
1237 int segmentOffset = chunkSize;
1238 int[] offsets = new int[segmentsCount];
1239 for (int i = 0; i < segmentsCount; i++) {
1240 segmentOffset -= segmentSize;
1241 offsets[i] = segmentOffset;
1242 }
1243 return new IntStack(offsets);
1244 }
1245
1246 @Override
1247 public int computeBufferCapacity(
1248 int requestedSize, int maxCapacity, boolean isReallocation) {
1249 return Math.min(segmentSize, maxCapacity);
1250 }
1251
1252 @Override
1253 public Chunk newChunkAllocation(int promptingSize, Magazine magazine) {
1254 AbstractByteBuf chunkBuffer = chunkAllocator.allocate(chunkSize, chunkSize);
1255 assert chunkBuffer.capacity() == chunkSize;
1256 SizeClassedChunk chunk = new SizeClassedChunk(chunkBuffer, magazine, this);
1257 chunkRegistry.add(chunk);
1258 return chunk;
1259 }
1260 }
1261
1262 private static final class BuddyChunkManagementStrategy implements ChunkManagementStrategy {
1263 private final AtomicInteger maxChunkSize = new AtomicInteger();
1264
1265 @Override
1266 public ChunkController createController(MagazineGroup group) {
1267 return new BuddyChunkController(group, maxChunkSize);
1268 }
1269
1270 @Override
1271 public ChunkCache createChunkCache(boolean isThreadLocal) {
1272 return new ConcurrentSkipListChunkCache();
1273 }
1274 }
1275
1276 private static final class BuddyChunkController implements ChunkController {
1277 private final ChunkAllocator chunkAllocator;
1278 private final ChunkRegistry chunkRegistry;
1279 private final AtomicInteger maxChunkSize;
1280
1281 BuddyChunkController(MagazineGroup group, AtomicInteger maxChunkSize) {
1282 chunkAllocator = group.chunkAllocator;
1283 chunkRegistry = group.allocator.chunkRegistry;
1284 this.maxChunkSize = maxChunkSize;
1285 }
1286
1287 @Override
1288 public int computeBufferCapacity(int requestedSize, int maxCapacity, boolean isReallocation) {
1289 return MathUtil.safeFindNextPositivePowerOfTwo(requestedSize);
1290 }
1291
1292 @Override
1293 public Chunk newChunkAllocation(int promptingSize, Magazine magazine) {
1294 int maxChunkSize = this.maxChunkSize.get();
1295 int proposedChunkSize = MathUtil.safeFindNextPositivePowerOfTwo(BUFS_PER_CHUNK * promptingSize);
1296 int chunkSize = Math.min(MAX_CHUNK_SIZE, Math.max(maxChunkSize, proposedChunkSize));
1297 if (chunkSize > maxChunkSize) {
1298
1299 this.maxChunkSize.set(chunkSize);
1300 }
1301 BuddyChunk chunk = new BuddyChunk(chunkAllocator.allocate(chunkSize, chunkSize), magazine);
1302 chunkRegistry.add(chunk);
1303 return chunk;
1304 }
1305 }
1306
1307 private static final class Magazine {
1308 private static final AtomicReferenceFieldUpdater<Magazine, Chunk> NEXT_IN_LINE;
1309
1310 static {
1311 NEXT_IN_LINE = AtomicReferenceFieldUpdater.newUpdater(Magazine.class, Chunk.class, "nextInLine");
1312 }
1313
1314 private static final Chunk MAGAZINE_FREED = new Chunk();
1315
1316 private static final class AdaptiveRecycler extends Recycler<AdaptiveByteBuf> {
1317
1318 private AdaptiveRecycler(boolean unguarded) {
1319
1320 super(unguarded);
1321 }
1322
1323 private AdaptiveRecycler(int maxCapacity, boolean unguarded) {
1324
1325 super(maxCapacity, unguarded);
1326 }
1327
1328 @Override
1329 protected AdaptiveByteBuf newObject(final Handle<AdaptiveByteBuf> handle) {
1330 return new AdaptiveByteBuf((EnhancedHandle<AdaptiveByteBuf>) handle);
1331 }
1332
1333 public static AdaptiveRecycler threadLocal() {
1334 return new AdaptiveRecycler(true);
1335 }
1336
1337 public static AdaptiveRecycler sharedWith(int maxCapacity) {
1338 return new AdaptiveRecycler(maxCapacity, true);
1339 }
1340 }
1341
1342 private static final AdaptiveRecycler EVENT_LOOP_LOCAL_BUFFER_POOL = AdaptiveRecycler.threadLocal();
1343
1344 private Chunk current;
1345 @SuppressWarnings("unused")
1346 private volatile Chunk nextInLine;
1347 private final MagazineGroup group;
1348 private final ChunkController chunkController;
1349 private final StampedLock allocationLock;
1350 private final AdaptiveRecycler recycler;
1351
1352 Magazine(MagazineGroup group, boolean shareable, ChunkController chunkController) {
1353 this.group = group;
1354 this.chunkController = chunkController;
1355
1356 if (shareable) {
1357
1358 allocationLock = new StampedLock();
1359 recycler = AdaptiveRecycler.sharedWith(MAGAZINE_BUFFER_QUEUE_CAPACITY);
1360 } else {
1361 allocationLock = null;
1362 recycler = null;
1363 }
1364 }
1365
1366 public boolean tryAllocate(int size, int maxCapacity, AdaptiveByteBuf buf, boolean reallocate) {
1367 if (allocationLock == null) {
1368
1369 return allocate(size, maxCapacity, buf, reallocate);
1370 }
1371
1372
1373 long writeLock = allocationLock.tryWriteLock();
1374 if (writeLock != 0) {
1375 try {
1376 return allocate(size, maxCapacity, buf, reallocate);
1377 } finally {
1378 allocationLock.unlockWrite(writeLock);
1379 }
1380 }
1381 return allocateWithoutLock(size, maxCapacity, buf);
1382 }
1383
1384 private boolean allocateWithoutLock(int size, int maxCapacity, AdaptiveByteBuf buf) {
1385 Chunk curr = NEXT_IN_LINE.getAndSet(this, null);
1386 if (curr == MAGAZINE_FREED) {
1387
1388 restoreMagazineFreed();
1389 return false;
1390 }
1391 if (curr == null) {
1392 curr = group.pollChunk(size);
1393 if (curr == null) {
1394 return false;
1395 }
1396 curr.attachToMagazine(this);
1397 }
1398 boolean allocated = false;
1399 int remainingCapacity = curr.remainingCapacity();
1400 int startingCapacity = chunkController.computeBufferCapacity(
1401 size, maxCapacity, true );
1402 if (remainingCapacity >= size &&
1403 curr.readInitInto(buf, size, Math.min(remainingCapacity, startingCapacity), maxCapacity)) {
1404 allocated = true;
1405 remainingCapacity = curr.remainingCapacity();
1406 }
1407 try {
1408 if (remainingCapacity >= RETIRE_CAPACITY) {
1409 transferToNextInLineOrRelease(curr);
1410 curr = null;
1411 }
1412 } finally {
1413 if (curr != null) {
1414 curr.releaseFromMagazine();
1415 }
1416 }
1417 return allocated;
1418 }
1419
1420 private boolean allocate(int size, int maxCapacity, AdaptiveByteBuf buf, boolean reallocate) {
1421 int startingCapacity = chunkController.computeBufferCapacity(size, maxCapacity, reallocate);
1422 Chunk curr = current;
1423 if (curr != null) {
1424 boolean success = curr.readInitInto(buf, size, startingCapacity, maxCapacity);
1425 int remainingCapacity = curr.remainingCapacity();
1426 if (!success && remainingCapacity > 0) {
1427 current = null;
1428 transferToNextInLineOrRelease(curr);
1429 } else if (remainingCapacity == 0) {
1430 current = null;
1431 curr.releaseFromMagazine();
1432 }
1433 if (success) {
1434 return true;
1435 }
1436 }
1437
1438 assert current == null;
1439
1440
1441
1442
1443
1444
1445
1446
1447 curr = NEXT_IN_LINE.getAndSet(this, null);
1448 if (curr != null) {
1449 if (curr == MAGAZINE_FREED) {
1450
1451 restoreMagazineFreed();
1452 return false;
1453 }
1454
1455 int remainingCapacity = curr.remainingCapacity();
1456 if (remainingCapacity > startingCapacity &&
1457 curr.readInitInto(buf, size, startingCapacity, maxCapacity)) {
1458
1459 current = curr;
1460 return true;
1461 }
1462
1463 try {
1464 if (remainingCapacity >= size) {
1465
1466
1467 return curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
1468 }
1469 } finally {
1470
1471
1472 curr.releaseFromMagazine();
1473 }
1474 }
1475
1476
1477 curr = group.pollChunk(size);
1478 if (curr == null) {
1479 curr = chunkController.newChunkAllocation(size, this);
1480 } else {
1481 curr.attachToMagazine(this);
1482
1483 int remainingCapacity = curr.remainingCapacity();
1484 if (remainingCapacity == 0 || remainingCapacity < size) {
1485
1486 if (remainingCapacity < RETIRE_CAPACITY) {
1487 curr.releaseFromMagazine();
1488 } else {
1489
1490
1491 transferToNextInLineOrRelease(curr);
1492 }
1493 curr = chunkController.newChunkAllocation(size, this);
1494 }
1495 }
1496
1497 current = curr;
1498 boolean success;
1499 try {
1500 int remainingCapacity = curr.remainingCapacity();
1501 assert remainingCapacity >= size;
1502 if (remainingCapacity > startingCapacity) {
1503 success = curr.readInitInto(buf, size, startingCapacity, maxCapacity);
1504 curr = null;
1505 } else {
1506 success = curr.readInitInto(buf, size, remainingCapacity, maxCapacity);
1507 }
1508 } finally {
1509 if (curr != null) {
1510
1511
1512 curr.releaseFromMagazine();
1513 current = null;
1514 }
1515 }
1516 return success;
1517 }
1518
1519 private void restoreMagazineFreed() {
1520 Chunk next = NEXT_IN_LINE.getAndSet(this, MAGAZINE_FREED);
1521 if (next != null && next != MAGAZINE_FREED) {
1522
1523 next.releaseFromMagazine();
1524 }
1525 }
1526
1527 private void transferToNextInLineOrRelease(Chunk chunk) {
1528 if (NEXT_IN_LINE.compareAndSet(this, null, chunk)) {
1529 return;
1530 }
1531
1532 Chunk nextChunk = NEXT_IN_LINE.get(this);
1533 if (nextChunk != null && nextChunk != MAGAZINE_FREED
1534 && chunk.remainingCapacity() > nextChunk.remainingCapacity()) {
1535 if (NEXT_IN_LINE.compareAndSet(this, nextChunk, chunk)) {
1536 nextChunk.releaseFromMagazine();
1537 return;
1538 }
1539 }
1540
1541
1542
1543
1544 chunk.releaseFromMagazine();
1545 }
1546
1547 void free() {
1548
1549 restoreMagazineFreed();
1550 long stamp = allocationLock != null ? allocationLock.writeLock() : 0;
1551 try {
1552 if (current != null) {
1553 current.releaseFromMagazine();
1554 current = null;
1555 }
1556 } finally {
1557 if (allocationLock != null) {
1558 allocationLock.unlockWrite(stamp);
1559 }
1560 }
1561 }
1562
1563 public AdaptiveByteBuf newBuffer() {
1564 AdaptiveRecycler recycler = this.recycler;
1565 AdaptiveByteBuf buf = recycler == null ? EVENT_LOOP_LOCAL_BUFFER_POOL.get() : recycler.get();
1566 buf.resetRefCnt();
1567 buf.discardMarks();
1568 return buf;
1569 }
1570
1571 boolean offerToQueue(Chunk chunk) {
1572 return group.offerChunk(chunk);
1573 }
1574 }
1575
1576 private static final class ChunkRegistry {
1577 private final LongAdder totalCapacity = new LongAdder();
1578
1579 public long totalCapacity() {
1580 return totalCapacity.sum();
1581 }
1582
1583 public void add(Chunk chunk) {
1584 totalCapacity.add(chunk.capacity());
1585 }
1586
1587 public void remove(Chunk chunk) {
1588 totalCapacity.add(-chunk.capacity());
1589 }
1590 }
1591
1592 static class Chunk implements ChunkInfo {
1593 protected final AbstractByteBuf delegate;
1594
1595 final long delegateMemoryAddress;
1596 protected Magazine magazine;
1597 private final AdaptivePoolingAllocator allocator;
1598
1599
1600 private final RefCnt refCnt = new RefCnt();
1601 private final int capacity;
1602 private final boolean pooled;
1603 protected int allocatedBytes;
1604
1605 Chunk() {
1606
1607 delegate = null;
1608 delegateMemoryAddress = 0L;
1609 magazine = null;
1610 allocator = null;
1611 capacity = 0;
1612 pooled = false;
1613 }
1614
1615 Chunk(AbstractByteBuf delegate, Magazine magazine, boolean pooled) {
1616 this.delegate = delegate;
1617 delegateMemoryAddress = delegate.hasMemoryAddress() ? delegate.memoryAddress() : 0L;
1618 this.pooled = pooled;
1619 capacity = delegate.capacity();
1620 attachToMagazine(magazine);
1621
1622
1623 allocator = magazine.group.allocator;
1624
1625 if (PlatformDependent.isJfrEnabled() && AllocateChunkEvent.isEventEnabled()) {
1626 AllocateChunkEvent event = new AllocateChunkEvent();
1627 if (event.shouldCommit()) {
1628 event.fill(this, AdaptiveByteBufAllocator.class);
1629 event.pooled = pooled;
1630 event.threadLocal = magazine.allocationLock == null;
1631 event.commit();
1632 }
1633 }
1634 }
1635
1636 Magazine currentMagazine() {
1637 return magazine;
1638 }
1639
1640 void detachFromMagazine() {
1641 if (magazine != null) {
1642 magazine = null;
1643 }
1644 }
1645
1646 void attachToMagazine(Magazine magazine) {
1647 assert this.magazine == null;
1648 this.magazine = magazine;
1649 }
1650
1651
1652
1653
1654 void releaseFromMagazine() {
1655
1656
1657 Magazine mag = magazine;
1658 detachFromMagazine();
1659 if (!mag.offerToQueue(this)) {
1660 markToDeallocate();
1661 }
1662 }
1663
1664
1665
1666
1667 void releaseSegment(int ignoredSegmentId, int size) {
1668 release();
1669 }
1670
1671 void markToDeallocate() {
1672 release();
1673 }
1674
1675 private void retain() {
1676 RefCnt.retain(refCnt);
1677 }
1678
1679 protected boolean release() {
1680 boolean deallocate = RefCnt.release(refCnt);
1681 if (deallocate) {
1682 deallocate();
1683 }
1684 return deallocate;
1685 }
1686
1687 protected void deallocate() {
1688 onRelease();
1689 allocator.chunkRegistry.remove(this);
1690 delegate.release();
1691 }
1692
1693 private void onRelease() {
1694 if (PlatformDependent.isJfrEnabled() && FreeChunkEvent.isEventEnabled()) {
1695 FreeChunkEvent event = new FreeChunkEvent();
1696 if (event.shouldCommit()) {
1697 event.fill(this, AdaptiveByteBufAllocator.class);
1698 event.pooled = pooled;
1699 event.commit();
1700 }
1701 }
1702 }
1703
1704 public boolean readInitInto(AdaptiveByteBuf buf, int size, int startingCapacity, int maxCapacity) {
1705 int startIndex = allocatedBytes;
1706 allocatedBytes = startIndex + startingCapacity;
1707 Chunk chunk = this;
1708 chunk.retain();
1709 try {
1710 buf.init(delegate, chunk, 0, 0, startIndex, size, startingCapacity, maxCapacity);
1711 chunk = null;
1712 } finally {
1713 if (chunk != null) {
1714
1715
1716
1717 allocatedBytes = startIndex;
1718 chunk.release();
1719 }
1720 }
1721 return true;
1722 }
1723
1724 public int remainingCapacity() {
1725 return capacity - allocatedBytes;
1726 }
1727
1728 public boolean hasUnprocessedFreelistEntries() {
1729 return false;
1730 }
1731
1732 public void processFreelistEntries() {
1733 }
1734
1735 @Override
1736 public int capacity() {
1737 return capacity;
1738 }
1739
1740 @Override
1741 public boolean isDirect() {
1742 return delegate.isDirect();
1743 }
1744
1745 @Override
1746 public long memoryAddress() {
1747 return delegate._memoryAddress();
1748 }
1749 }
1750
1751 private static final class IntStack {
1752
1753 private final int[] stack;
1754 private int top;
1755
1756 IntStack(int[] initialValues) {
1757 stack = initialValues;
1758 top = initialValues.length - 1;
1759 }
1760
1761 public boolean isEmpty() {
1762 return top == -1;
1763 }
1764
1765 public int pop() {
1766 final int last = stack[top];
1767 top--;
1768 return last;
1769 }
1770
1771 public void push(int value) {
1772 stack[top + 1] = value;
1773 top++;
1774 }
1775
1776 public int size() {
1777 return top + 1;
1778 }
1779 }
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800 static class SizeClassedChunk extends Chunk {
1801 private static final int FREE_LIST_EMPTY = -1;
1802 private static final int AVAILABLE = -1;
1803
1804
1805 private static final int DEALLOCATED = Integer.MIN_VALUE;
1806 private static final AtomicIntegerFieldUpdater<SizeClassedChunk> STATE =
1807 AtomicIntegerFieldUpdater.newUpdater(SizeClassedChunk.class, "state");
1808 private volatile int state;
1809 private final int segments;
1810 private final int segmentSize;
1811 private final MpscIntQueue externalFreeList;
1812 private final IntStack localFreeList;
1813 private Thread ownerThread;
1814 int purgeEpoch;
1815 long lastPurgeGeneration;
1816 long lastScanGeneration;
1817
1818 SizeClassedChunk(AbstractByteBuf delegate, Magazine magazine,
1819 SizeClassChunkController controller) {
1820 super(delegate, magazine, true);
1821 segmentSize = controller.segmentSize;
1822 segments = controller.chunkSize / segmentSize;
1823 STATE.lazySet(this, AVAILABLE);
1824 ownerThread = magazine.group.ownerThread;
1825 if (ownerThread == null) {
1826 externalFreeList = controller.createFreeList();
1827 localFreeList = null;
1828 } else {
1829 externalFreeList = controller.createEmptyFreeList();
1830 localFreeList = controller.createLocalFreeList();
1831 }
1832 }
1833
1834 @Override
1835 public boolean readInitInto(AdaptiveByteBuf buf, int size, int startingCapacity, int maxCapacity) {
1836 assert state == AVAILABLE;
1837 final int startIndex = nextAvailableSegmentOffset();
1838 if (startIndex == FREE_LIST_EMPTY) {
1839 return false;
1840 }
1841 allocatedBytes += segmentSize;
1842 try {
1843 buf.init(delegate, this, 0, 0, startIndex, size, startingCapacity, maxCapacity);
1844 } catch (Throwable t) {
1845 allocatedBytes -= segmentSize;
1846 releaseSegmentOffsetIntoFreeList(startIndex);
1847 throw t;
1848 }
1849 return true;
1850 }
1851
1852 private int nextAvailableSegmentOffset() {
1853 final int startIndex;
1854 IntStack localFreeList = this.localFreeList;
1855 if (localFreeList != null) {
1856 assert Thread.currentThread() == ownerThread;
1857 if (localFreeList.isEmpty()) {
1858 startIndex = externalFreeList.poll();
1859 } else {
1860 startIndex = localFreeList.pop();
1861 }
1862 } else {
1863 startIndex = externalFreeList.poll();
1864 }
1865 return startIndex;
1866 }
1867
1868
1869
1870
1871 public boolean hasRemainingCapacity() {
1872 int remaining = super.remainingCapacity();
1873 if (remaining > 0) {
1874 return true;
1875 }
1876 if (localFreeList != null) {
1877 return !localFreeList.isEmpty();
1878 }
1879 return !externalFreeList.isEmpty();
1880 }
1881
1882 boolean hasFullCapacity() {
1883 int free = externalFreeList.size();
1884 IntStack local = localFreeList;
1885 if (local != null) {
1886 free += local.size();
1887 }
1888 return free == segments;
1889 }
1890
1891 @Override
1892 public int remainingCapacity() {
1893 int remaining = super.remainingCapacity();
1894 return remaining > segmentSize ? remaining : updateRemainingCapacity(remaining);
1895 }
1896
1897 private int updateRemainingCapacity(int snapshotted) {
1898 int freeSegments = externalFreeList.size();
1899 IntStack localFreeList = this.localFreeList;
1900 if (localFreeList != null) {
1901 freeSegments += localFreeList.size();
1902 }
1903 int updated = freeSegments * segmentSize;
1904 if (updated != snapshotted) {
1905 allocatedBytes = capacity() - updated;
1906 }
1907 return updated;
1908 }
1909
1910 private void releaseSegmentOffsetIntoFreeList(int startIndex) {
1911 IntStack localFreeList = this.localFreeList;
1912 if (localFreeList != null && Thread.currentThread() == ownerThread) {
1913 localFreeList.push(startIndex);
1914 } else {
1915 boolean segmentReturned = externalFreeList.offer(startIndex);
1916 assert segmentReturned : "Unable to return segment " + startIndex + " to free list";
1917 }
1918 }
1919
1920 @Override
1921 void releaseSegment(int startIndex, int size) {
1922 IntStack localFreeList = this.localFreeList;
1923 if (localFreeList != null && Thread.currentThread() == ownerThread) {
1924 localFreeList.push(startIndex);
1925 int state = this.state;
1926 if (state != AVAILABLE) {
1927 updateStateOnLocalReleaseSegment(state, localFreeList);
1928 }
1929 } else {
1930 boolean segmentReturned = externalFreeList.offer(startIndex);
1931 assert segmentReturned;
1932
1933 int state = this.state;
1934 if (state != AVAILABLE) {
1935 deallocateIfNeeded(state);
1936 }
1937 }
1938 }
1939
1940 private void updateStateOnLocalReleaseSegment(int previousLocalSize, IntStack localFreeList) {
1941 int newLocalSize = localFreeList.size();
1942 boolean alwaysTrue = STATE.compareAndSet(this, previousLocalSize, newLocalSize);
1943 assert alwaysTrue : "this shouldn't happen unless double release in the local free list";
1944 deallocateIfNeeded(newLocalSize);
1945 }
1946
1947 private void deallocateIfNeeded(int localSize) {
1948
1949 int totalFreeSegments = localSize + externalFreeList.size();
1950 if (totalFreeSegments == segments && STATE.compareAndSet(this, localSize, DEALLOCATED)) {
1951 deallocate();
1952 }
1953 }
1954
1955 @Override
1956 void markToDeallocate() {
1957 IntStack localFreeList = this.localFreeList;
1958 int localSize = localFreeList != null ? localFreeList.size() : 0;
1959 STATE.set(this, localSize);
1960 deallocateIfNeeded(localSize);
1961 }
1962 }
1963
1964 private static final class BuddyChunk extends Chunk implements IntConsumer {
1965 private static final int MIN_BUDDY_SIZE = 32768;
1966 private static final byte IS_CLAIMED = (byte) (1 << 7);
1967 private static final byte HAS_CLAIMED_CHILDREN = 1 << 6;
1968 private static final byte SHIFT_MASK = ~(IS_CLAIMED | HAS_CLAIMED_CHILDREN);
1969 private static final int PACK_OFFSET_MASK = 0xFFFF;
1970 private static final int PACK_SIZE_SHIFT = Integer.SIZE - Integer.numberOfLeadingZeros(PACK_OFFSET_MASK);
1971
1972 private final MpscIntQueue freeList;
1973
1974 private final byte[] buddies;
1975 private final int freeListCapacity;
1976
1977 BuddyChunk(AbstractByteBuf delegate, Magazine magazine) {
1978 super(delegate, magazine, true);
1979 freeListCapacity = delegate.capacity() / MIN_BUDDY_SIZE;
1980 int maxShift = Integer.numberOfTrailingZeros(freeListCapacity);
1981 assert maxShift <= 30;
1982 freeList = MpscIntQueue.create(freeListCapacity, -1);
1983 buddies = new byte[freeListCapacity << 1];
1984
1985
1986 int index = 1;
1987 int runLength = 1;
1988 int currentRun = 0;
1989 while (maxShift > 0) {
1990 buddies[index++] = (byte) maxShift;
1991 if (++currentRun == runLength) {
1992 currentRun = 0;
1993 runLength <<= 1;
1994 maxShift--;
1995 }
1996 }
1997 }
1998
1999 @Override
2000 public boolean readInitInto(AdaptiveByteBuf buf, int size, int startingCapacity, int maxCapacity) {
2001 if (!freeList.isEmpty()) {
2002 freeList.drain(freeListCapacity, this);
2003 }
2004 int startIndex = chooseFirstFreeBuddy(1, startingCapacity, 0);
2005 if (startIndex == -1) {
2006 return false;
2007 }
2008 Chunk chunk = this;
2009 chunk.retain();
2010 try {
2011 buf.init(delegate, this, 0, 0, startIndex, size, startingCapacity, maxCapacity);
2012 allocatedBytes += startingCapacity;
2013 chunk = null;
2014 } finally {
2015 if (chunk != null) {
2016 unreserveMatchingBuddy(1, startingCapacity, startIndex, 0);
2017
2018
2019 chunk.release();
2020 }
2021 }
2022 return true;
2023 }
2024
2025 @Override
2026 public void accept(int packed) {
2027
2028 int size = unpackSize(packed);
2029 int offset = unpackOffset(packed);
2030 unreserveMatchingBuddy(1, size, offset, 0);
2031 allocatedBytes -= size;
2032 }
2033
2034 private static int unpackSize(int packed) {
2035 return MIN_BUDDY_SIZE << (packed >> PACK_SIZE_SHIFT);
2036 }
2037
2038 private static int unpackOffset(int packed) {
2039 return (packed & PACK_OFFSET_MASK) * MIN_BUDDY_SIZE;
2040 }
2041
2042 @Override
2043 void releaseSegment(int startingIndex, int size) {
2044 int packedOffset = startingIndex / MIN_BUDDY_SIZE;
2045 int packedSize = Integer.numberOfTrailingZeros(size / MIN_BUDDY_SIZE) << PACK_SIZE_SHIFT;
2046 int packed = packedOffset | packedSize;
2047 freeList.offer(packed);
2048 release();
2049 }
2050
2051 @Override
2052 public int remainingCapacity() {
2053 int capacityInFreeList = 0;
2054 if (!freeList.isEmpty()) {
2055 capacityInFreeList = freeList.weakPeekReduce(freeListCapacity, 0,
2056 (sum, entry) -> sum + unpackSize(entry));
2057 }
2058 return super.remainingCapacity() + capacityInFreeList;
2059 }
2060
2061 @Override
2062 public boolean hasUnprocessedFreelistEntries() {
2063 return !freeList.isEmpty();
2064 }
2065
2066 @Override
2067 public void processFreelistEntries() {
2068 freeList.drain(freeListCapacity, this);
2069 }
2070
2071
2072
2073
2074 private int chooseFirstFreeBuddy(int index, int size, int currOffset) {
2075 byte[] buddies = this.buddies;
2076 while (index < buddies.length) {
2077 byte buddy = buddies[index];
2078 int currValue = MIN_BUDDY_SIZE << (buddy & SHIFT_MASK);
2079 if (currValue < size || (buddy & IS_CLAIMED) == IS_CLAIMED) {
2080 return -1;
2081 }
2082 if (currValue == size && (buddy & HAS_CLAIMED_CHILDREN) == 0) {
2083 buddies[index] |= IS_CLAIMED;
2084 return currOffset;
2085 }
2086 int found = chooseFirstFreeBuddy(index << 1, size, currOffset);
2087 if (found != -1) {
2088 buddies[index] |= HAS_CLAIMED_CHILDREN;
2089 return found;
2090 }
2091 index = (index << 1) + 1;
2092 currOffset += currValue >> 1;
2093 }
2094 return -1;
2095 }
2096
2097
2098
2099
2100 private boolean unreserveMatchingBuddy(int index, int size, int offset, int currOffset) {
2101 byte[] buddies = this.buddies;
2102 if (buddies.length <= index) {
2103 return false;
2104 }
2105 byte buddy = buddies[index];
2106 int currSize = MIN_BUDDY_SIZE << (buddy & SHIFT_MASK);
2107
2108 if (currSize == size) {
2109
2110 if (currOffset == offset) {
2111 buddies[index] &= SHIFT_MASK;
2112 return false;
2113 }
2114 throw new IllegalStateException("The intended segment was not found at index " +
2115 index + ", for size " + size + " and offset " + offset);
2116 }
2117
2118
2119 boolean claims;
2120 int siblingIndex;
2121 if (offset < currOffset + (currSize >> 1)) {
2122
2123 claims = unreserveMatchingBuddy(index << 1, size, offset, currOffset);
2124 siblingIndex = (index << 1) + 1;
2125 } else {
2126
2127 claims = unreserveMatchingBuddy((index << 1) + 1, size, offset, currOffset + (currSize >> 1));
2128 siblingIndex = index << 1;
2129 }
2130 if (!claims) {
2131
2132 byte sibling = buddies[siblingIndex];
2133 if ((sibling & SHIFT_MASK) == sibling) {
2134
2135 buddies[index] &= SHIFT_MASK;
2136 return false;
2137 }
2138 }
2139 return true;
2140 }
2141
2142 @Override
2143 public String toString() {
2144 int capacity = delegate.capacity();
2145 int remaining = capacity - allocatedBytes;
2146 return "BuddyChunk[capacity: " + capacity +
2147 ", remaining: " + remaining +
2148 ", free list: " + freeList.size() + ']';
2149 }
2150 }
2151
2152 static final class AdaptiveByteBuf extends AbstractReferenceCountedByteBuf {
2153
2154 private final EnhancedHandle<AdaptiveByteBuf> handle;
2155
2156
2157 private int startIndex;
2158 private AbstractByteBuf rootParent;
2159
2160
2161 private long memoryAddress;
2162 Chunk chunk;
2163 private int length;
2164 private int maxFastCapacity;
2165 private ByteBuffer tmpNioBuf;
2166 private boolean hasArray;
2167 private boolean hasMemoryAddress;
2168
2169 AdaptiveByteBuf(EnhancedHandle<AdaptiveByteBuf> recyclerHandle) {
2170 super(0);
2171 handle = ObjectUtil.checkNotNull(recyclerHandle, "recyclerHandle");
2172 }
2173
2174 void init(AbstractByteBuf unwrapped, Chunk wrapped, int readerIndex, int writerIndex,
2175 int startIndex, int size, int capacity, int maxCapacity) {
2176 this.startIndex = startIndex;
2177 chunk = wrapped;
2178 length = size;
2179 maxFastCapacity = capacity;
2180 maxCapacity(maxCapacity);
2181 setIndex0(readerIndex, writerIndex);
2182 hasArray = unwrapped.hasArray();
2183 hasMemoryAddress = unwrapped.hasMemoryAddress();
2184
2185 memoryAddress = hasMemoryAddress ? wrapped.delegateMemoryAddress + startIndex : 0L;
2186 rootParent = unwrapped;
2187 tmpNioBuf = null;
2188
2189 if (PlatformDependent.isJfrEnabled() && AllocateBufferEvent.isEventEnabled()) {
2190 AllocateBufferEvent event = new AllocateBufferEvent();
2191 if (event.shouldCommit()) {
2192 event.fill(this, AdaptiveByteBufAllocator.class);
2193 event.chunkPooled = wrapped.pooled;
2194 Magazine m = wrapped.magazine;
2195 event.chunkThreadLocal = m != null && m.allocationLock == null;
2196 event.commit();
2197 }
2198 }
2199 }
2200
2201 private AbstractByteBuf rootParent() {
2202 final AbstractByteBuf rootParent = this.rootParent;
2203 if (rootParent != null) {
2204 return rootParent;
2205 }
2206 throw new IllegalReferenceCountException();
2207 }
2208
2209 @Override
2210 public int capacity() {
2211 return length;
2212 }
2213
2214 @Override
2215 public int maxFastWritableBytes() {
2216 return Math.min(maxFastCapacity, maxCapacity()) - writerIndex;
2217 }
2218
2219 @Override
2220 public ByteBuf capacity(int newCapacity) {
2221 checkNewCapacity(newCapacity);
2222 if (length <= newCapacity && newCapacity <= maxFastCapacity) {
2223 length = newCapacity;
2224 return this;
2225 }
2226 if (newCapacity < capacity()) {
2227 length = newCapacity;
2228 trimIndicesToCapacity(newCapacity);
2229 return this;
2230 }
2231
2232 if (PlatformDependent.isJfrEnabled() && ReallocateBufferEvent.isEventEnabled()) {
2233 ReallocateBufferEvent event = new ReallocateBufferEvent();
2234 if (event.shouldCommit()) {
2235 event.fill(this, AdaptiveByteBufAllocator.class);
2236 event.newCapacity = newCapacity;
2237 event.commit();
2238 }
2239 }
2240
2241
2242 Chunk chunk = this.chunk;
2243 AdaptivePoolingAllocator allocator = chunk.allocator;
2244 int readerIndex = this.readerIndex;
2245 int writerIndex = this.writerIndex;
2246 int baseOldRootIndex = startIndex;
2247 int oldLength = length;
2248 int oldCapacity = maxFastCapacity;
2249 AbstractByteBuf oldRoot = rootParent();
2250 allocator.reallocate(newCapacity, maxCapacity(), this);
2251 oldRoot.getBytes(baseOldRootIndex, this, 0, oldLength);
2252 chunk.releaseSegment(baseOldRootIndex, oldCapacity);
2253 assert oldCapacity < maxFastCapacity && newCapacity <= maxFastCapacity :
2254 "Capacity increase failed";
2255 this.readerIndex = readerIndex;
2256 this.writerIndex = writerIndex;
2257 return this;
2258 }
2259
2260 @Override
2261 public ByteBufAllocator alloc() {
2262 return rootParent().alloc();
2263 }
2264
2265 @SuppressWarnings("deprecation")
2266 @Override
2267 public ByteOrder order() {
2268 return rootParent().order();
2269 }
2270
2271 @Override
2272 public ByteBuf unwrap() {
2273 return null;
2274 }
2275
2276 @Override
2277 public boolean isDirect() {
2278 return rootParent().isDirect();
2279 }
2280
2281 @Override
2282 public int arrayOffset() {
2283 return idx(rootParent().arrayOffset());
2284 }
2285
2286 @Override
2287 public boolean hasMemoryAddress() {
2288 return hasMemoryAddress;
2289 }
2290
2291 @Override
2292 public long memoryAddress() {
2293 ensureAccessible();
2294 if (!hasMemoryAddress) {
2295 throw new UnsupportedOperationException();
2296 }
2297 return _memoryAddress();
2298 }
2299
2300 @Override
2301 long _memoryAddress() {
2302
2303
2304
2305 return memoryAddress;
2306 }
2307
2308 @Override
2309 boolean _isDirect() {
2310 AbstractByteBuf root = rootParent;
2311 return root != null && root.isDirect();
2312 }
2313
2314 @Override
2315 public ByteBuffer nioBuffer(int index, int length) {
2316 checkIndex(index, length);
2317 return rootParent().nioBuffer(idx(index), length);
2318 }
2319
2320 @Override
2321 public ByteBuffer internalNioBuffer(int index, int length) {
2322 checkIndex(index, length);
2323 return (ByteBuffer) internalNioBuffer().position(index).limit(index + length);
2324 }
2325
2326 private ByteBuffer internalNioBuffer() {
2327 if (tmpNioBuf == null) {
2328 tmpNioBuf = rootParent().nioBuffer(startIndex, maxFastCapacity);
2329 }
2330 return (ByteBuffer) tmpNioBuf.clear();
2331 }
2332
2333 @Override
2334 public ByteBuffer[] nioBuffers(int index, int length) {
2335 checkIndex(index, length);
2336 return rootParent().nioBuffers(idx(index), length);
2337 }
2338
2339 @Override
2340 public boolean hasArray() {
2341 return hasArray;
2342 }
2343
2344 @Override
2345 public byte[] array() {
2346 ensureAccessible();
2347 return rootParent().array();
2348 }
2349
2350 @Override
2351 public ByteBuf copy(int index, int length) {
2352 checkIndex(index, length);
2353 return rootParent().copy(idx(index), length);
2354 }
2355
2356 @Override
2357 public int nioBufferCount() {
2358 return rootParent().nioBufferCount();
2359 }
2360
2361 @Override
2362 protected byte _getByte(int index) {
2363 return rootParent()._getByte(idx(index));
2364 }
2365
2366 @Override
2367 protected short _getShort(int index) {
2368 return rootParent()._getShort(idx(index));
2369 }
2370
2371 @Override
2372 protected short _getShortLE(int index) {
2373 return rootParent()._getShortLE(idx(index));
2374 }
2375
2376 @Override
2377 protected int _getUnsignedMedium(int index) {
2378 return rootParent()._getUnsignedMedium(idx(index));
2379 }
2380
2381 @Override
2382 protected int _getUnsignedMediumLE(int index) {
2383 return rootParent()._getUnsignedMediumLE(idx(index));
2384 }
2385
2386 @Override
2387 protected int _getInt(int index) {
2388 return rootParent()._getInt(idx(index));
2389 }
2390
2391 @Override
2392 protected int _getIntLE(int index) {
2393 return rootParent()._getIntLE(idx(index));
2394 }
2395
2396 @Override
2397 protected long _getLong(int index) {
2398 return rootParent()._getLong(idx(index));
2399 }
2400
2401 @Override
2402 protected long _getLongLE(int index) {
2403 return rootParent()._getLongLE(idx(index));
2404 }
2405
2406 @Override
2407 public ByteBuf getBytes(int index, ByteBuf dst, int dstIndex, int length) {
2408 checkIndex(index, length);
2409 rootParent().getBytes(idx(index), dst, dstIndex, length);
2410 return this;
2411 }
2412
2413 @Override
2414 public ByteBuf getBytes(int index, byte[] dst, int dstIndex, int length) {
2415 checkIndex(index, length);
2416 rootParent().getBytes(idx(index), dst, dstIndex, length);
2417 return this;
2418 }
2419
2420 @Override
2421 public ByteBuf getBytes(int index, ByteBuffer dst) {
2422 checkIndex(index, dst.remaining());
2423 rootParent().getBytes(idx(index), dst);
2424 return this;
2425 }
2426
2427 @Override
2428 protected void _setByte(int index, int value) {
2429 rootParent()._setByte(idx(index), value);
2430 }
2431
2432 @Override
2433 protected void _setShort(int index, int value) {
2434 rootParent()._setShort(idx(index), value);
2435 }
2436
2437 @Override
2438 protected void _setShortLE(int index, int value) {
2439 rootParent()._setShortLE(idx(index), value);
2440 }
2441
2442 @Override
2443 protected void _setMedium(int index, int value) {
2444 rootParent()._setMedium(idx(index), value);
2445 }
2446
2447 @Override
2448 protected void _setMediumLE(int index, int value) {
2449 rootParent()._setMediumLE(idx(index), value);
2450 }
2451
2452 @Override
2453 protected void _setInt(int index, int value) {
2454 rootParent()._setInt(idx(index), value);
2455 }
2456
2457 @Override
2458 protected void _setIntLE(int index, int value) {
2459 rootParent()._setIntLE(idx(index), value);
2460 }
2461
2462 @Override
2463 protected void _setLong(int index, long value) {
2464 rootParent()._setLong(idx(index), value);
2465 }
2466
2467 @Override
2468 protected void _setLongLE(int index, long value) {
2469 rootParent()._setLongLE(idx(index), value);
2470 }
2471
2472 @Override
2473 public ByteBuf setBytes(int index, byte[] src, int srcIndex, int length) {
2474 checkIndex(index, length);
2475 if (tmpNioBuf == null && PlatformDependent.javaVersion() >= 13) {
2476 ByteBuffer dstBuffer = rootParent()._internalNioBuffer();
2477 PlatformDependent.absolutePut(dstBuffer, idx(index), src, srcIndex, length);
2478 } else {
2479 ByteBuffer tmp = (ByteBuffer) internalNioBuffer().position(index);
2480 tmp.put(src, srcIndex, length);
2481 }
2482 return this;
2483 }
2484
2485 @Override
2486 public ByteBuf setBytes(int index, ByteBuf src, int srcIndex, int length) {
2487 if (PlatformDependent.hasUnsafe() && hasMemoryAddress) {
2488
2489
2490
2491
2492
2493 UnsafeByteBufUtil.setBytes(this, _memoryAddress() + index, index, src, srcIndex, length);
2494 return this;
2495 }
2496 checkIndex(index, length);
2497 if (src instanceof AdaptiveByteBuf && PlatformDependent.javaVersion() >= 16) {
2498 AdaptiveByteBuf srcBuf = (AdaptiveByteBuf) src;
2499 srcBuf.checkIndex(srcIndex, length);
2500 ByteBuffer dstBuffer = rootParent()._internalNioBuffer();
2501 ByteBuffer srcBuffer = srcBuf.rootParent()._internalNioBuffer();
2502 PlatformDependent.absolutePut(dstBuffer, idx(index), srcBuffer, srcBuf.idx(srcIndex), length);
2503 } else {
2504 ByteBuffer tmp = internalNioBuffer();
2505 tmp.position(index);
2506 tmp.put(src.nioBuffer(srcIndex, length));
2507 }
2508 return this;
2509 }
2510
2511 @Override
2512 public ByteBuf setBytes(int index, ByteBuffer src) {
2513 int length = src.remaining();
2514 checkIndex(index, length);
2515 if (src == tmpNioBuf) {
2516 src = src.duplicate();
2517 }
2518 ByteBuffer tmp = internalNioBuffer();
2519 if (PlatformDependent.javaVersion() >= 16) {
2520 int offset = src.position();
2521 PlatformDependent.absolutePut(tmp, index, src, offset, length);
2522 src.position(offset + length);
2523 } else {
2524 tmp.position(index);
2525 tmp.put(src);
2526 }
2527 return this;
2528 }
2529
2530 @Override
2531 public ByteBuf getBytes(int index, OutputStream out, int length)
2532 throws IOException {
2533 checkIndex(index, length);
2534 if (length != 0) {
2535 ByteBuffer tmp = internalNioBuffer();
2536 ByteBufUtil.readBytes(alloc(), tmp.hasArray() ? tmp : tmp.duplicate(), index, length, out);
2537 }
2538 return this;
2539 }
2540
2541 @Override
2542 public int getBytes(int index, GatheringByteChannel out, int length)
2543 throws IOException {
2544 checkIndex(index, length);
2545 ByteBuffer buf = internalNioBuffer().duplicate();
2546 buf.position(index).limit(index + length);
2547 return out.write(buf);
2548 }
2549
2550 @Override
2551 public int getBytes(int index, FileChannel out, long position, int length)
2552 throws IOException {
2553 checkIndex(index, length);
2554 ByteBuffer buf = internalNioBuffer().duplicate();
2555 buf.position(index).limit(index + length);
2556 return out.write(buf, position);
2557 }
2558
2559 @Override
2560 public int setBytes(int index, InputStream in, int length)
2561 throws IOException {
2562 checkIndex(index, length);
2563 final AbstractByteBuf rootParent = rootParent();
2564 if (rootParent.hasArray()) {
2565 return rootParent.setBytes(idx(index), in, length);
2566 }
2567 byte[] tmp = ByteBufUtil.threadLocalTempArray(length);
2568 int readBytes = in.read(tmp, 0, length);
2569 if (readBytes <= 0) {
2570 return readBytes;
2571 }
2572 setBytes(index, tmp, 0, readBytes);
2573 return readBytes;
2574 }
2575
2576 @Override
2577 public int setBytes(int index, ScatteringByteChannel in, int length)
2578 throws IOException {
2579 try {
2580 return in.read(internalNioBuffer(index, length));
2581 } catch (ClosedChannelException ignored) {
2582 return -1;
2583 }
2584 }
2585
2586 @Override
2587 public int setBytes(int index, FileChannel in, long position, int length)
2588 throws IOException {
2589 try {
2590 return in.read(internalNioBuffer(index, length), position);
2591 } catch (ClosedChannelException ignored) {
2592 return -1;
2593 }
2594 }
2595
2596 @Override
2597 public int setCharSequence(int index, CharSequence sequence, Charset charset) {
2598 return setCharSequence0(index, sequence, charset, false);
2599 }
2600
2601 private int setCharSequence0(int index, CharSequence sequence, Charset charset, boolean expand) {
2602 if (charset.equals(CharsetUtil.UTF_8)) {
2603 int length = ByteBufUtil.utf8MaxBytes(sequence);
2604 if (expand) {
2605 ensureWritable0(length);
2606 checkIndex0(index, length);
2607 } else {
2608 checkIndex(index, length);
2609 }
2610 return ByteBufUtil.writeUtf8(this, index, length, sequence, sequence.length());
2611 }
2612 if (charset.equals(CharsetUtil.US_ASCII) || charset.equals(CharsetUtil.ISO_8859_1)) {
2613 int length = sequence.length();
2614 if (expand) {
2615 ensureWritable0(length);
2616 checkIndex0(index, length);
2617 } else {
2618 checkIndex(index, length);
2619 }
2620 return ByteBufUtil.writeAscii(this, index, sequence, length);
2621 }
2622 byte[] bytes = sequence.toString().getBytes(charset);
2623 if (expand) {
2624 ensureWritable0(bytes.length);
2625
2626 }
2627 setBytes(index, bytes);
2628 return bytes.length;
2629 }
2630
2631 @Override
2632 public int writeCharSequence(CharSequence sequence, Charset charset) {
2633 int written = setCharSequence0(writerIndex, sequence, charset, true);
2634 writerIndex += written;
2635 return written;
2636 }
2637
2638 @Override
2639 public int forEachByte(int index, int length, ByteProcessor processor) {
2640 checkIndex(index, length);
2641 int ret = rootParent().forEachByte(idx(index), length, processor);
2642 return forEachResult(ret);
2643 }
2644
2645 @Override
2646 public int forEachByteDesc(int index, int length, ByteProcessor processor) {
2647 checkIndex(index, length);
2648 int ret = rootParent().forEachByteDesc(idx(index), length, processor);
2649 return forEachResult(ret);
2650 }
2651
2652 @Override
2653 public ByteBuf setZero(int index, int length) {
2654 checkIndex(index, length);
2655 rootParent().setZero(idx(index), length);
2656 return this;
2657 }
2658
2659 @Override
2660 public ByteBuf writeZero(int length) {
2661 ensureWritable(length);
2662 rootParent().setZero(idx(writerIndex), length);
2663 writerIndex += length;
2664 return this;
2665 }
2666
2667 private int forEachResult(int ret) {
2668 if (ret < startIndex) {
2669 return -1;
2670 }
2671 return ret - startIndex;
2672 }
2673
2674 @Override
2675 public boolean isContiguous() {
2676 return rootParent().isContiguous();
2677 }
2678
2679 private int idx(int index) {
2680 return index + startIndex;
2681 }
2682
2683 @Override
2684 protected void deallocate() {
2685 if (PlatformDependent.isJfrEnabled() && FreeBufferEvent.isEventEnabled()) {
2686 FreeBufferEvent event = new FreeBufferEvent();
2687 if (event.shouldCommit()) {
2688 event.fill(this, AdaptiveByteBufAllocator.class);
2689 event.commit();
2690 }
2691 }
2692
2693 if (chunk != null) {
2694 chunk.releaseSegment(startIndex, maxFastCapacity);
2695 }
2696 tmpNioBuf = null;
2697 chunk = null;
2698 rootParent = null;
2699 memoryAddress = 0L;
2700 hasMemoryAddress = false;
2701 handle.unguardedRecycle(this);
2702 }
2703 }
2704
2705
2706
2707
2708 interface ChunkAllocator {
2709
2710
2711
2712
2713
2714
2715
2716 AbstractByteBuf allocate(int initialCapacity, int maxCapacity);
2717 }
2718 }