Repository: netty/netty
Issue: direct memory allocation error in PooledByteBufAllocator when setting directMemoryCacheAlignment to "1"

Define a `PooledByteBufAllocator` and set ·directMemoryCacheAlignment· to "1" and try to allocate some `ByteBuf` like
```java
PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(
        PlatformDependent.directBufferPreferred(),
        PooledByteBufAllocator.defaultNumHeapArena(),
        PooledByteBufAllocator.defaultNumDirectArena(),
        PooledByteBufAllocator.defaultPageSize(),
        11,
        PooledByteBufAllocator.defaultSmallCacheSize(),
        64,
        PooledByteBufAllocator.defaultUseCacheForAllThreads(),
        1
);

ByteBuf buf1 = pooledByteBufAllocator.directBuffer(16384);
for (int i = 0; i < 16384; i++) {
    buf1.writeByte(1);
}
ByteBuf buf2 = pooledByteBufAllocator.directBuffer(65536);
buf2.writeByte(2);
System.out.println("last byte of buf1: " + buf1.getByte(buf1.capacity() - 1));
```

### Expected behavior
`last byte of the buf1: 1`
### Actual behavior
`last byte of the buf1: 2`
### Steps to reproduce
```java
PooledByteBufAllocator pooledByteBufAllocator = new PooledByteBufAllocator(
        PlatformDependent.directBufferPreferred(),
        PooledByteBufAllocator.defaultNumHeapArena(),
        PooledByteBufAllocator.defaultNumDirectArena(),
        PooledByteBufAllocator.defaultPageSize(),
        11,
        PooledByteBufAllocator.defaultSmallCacheSize(),
        64,
        PooledByteBufAllocator.defaultUseCacheForAllThreads(),
        1
);

ByteBuf buf1 = pooledByteBufAllocator.directBuffer(16384);
ByteBuf buf2 = pooledByteBufAllocator.directBuffer(65536);
System.out.println("memory address of buf1: [" + buf1.memoryAddress() + ", " + (buf1.memoryAddress() + buf1.capacity()) + ")");
System.out.println("memory address of buf2: [" + buf2.memoryAddress() + ", " + (buf2.memoryAddress() + buf2.capacity()) + ")");
```

Got 2 overlapped ranges(on my pc)
memory address of buf1: [2047716884545, 2047716900929)
memory address of buf2: [2047716900928, 2047716966464)



### Netty version
4.1.60.Final
### JVM version (e.g. `java -version`)
1.8.0_271
### OS version (e.g. `uname -a`)
win10

Issue comments:
@alalag1 Oof! Nice catch. We'll take a look.

Pull request review comments:
So this means we may waste `directMemoryCacheAlignment` bytes in the worse case, correct ? 
Should we better not make this static but create a new one in the test ? Otherwise we may end up with using more memory then we really need (due the fact that the ALLOCATOR will not be collected until the JVM dies).
should we require that `alignment` is a power of two and so we can use `&` as a replacement for `%` for perf reasons ?
Yes, that was always the case when alignment was requested.
I switched to using JCTools Pow2 for this, which do these checks.

Patch that resolved the issue:
diff --git a/buffer/src/main/java/io/netty/buffer/PoolArena.java b/buffer/src/main/java/io/netty/buffer/PoolArena.java
index cab3224760f..fba1127b5d7 100644
--- a/buffer/src/main/java/io/netty/buffer/PoolArena.java
+++ b/buffer/src/main/java/io/netty/buffer/PoolArena.java
@@ -41,7 +41,6 @@ enum SizeClass {
 
     final int numSmallSubpagePools;
     final int directMemoryCacheAlignment;
-    final int directMemoryCacheAlignmentMask;
     private final PoolSubpage<T>[] smallSubpagePools;
 
     private final PoolChunkList<T> q050;
@@ -77,7 +76,6 @@ protected PoolArena(PooledByteBufAllocator parent, int pageSize,
         super(pageSize, pageShifts, chunkSize, cacheAlignment);
         this.parent = parent;
         directMemoryCacheAlignment = cacheAlignment;
-        directMemoryCacheAlignmentMask = cacheAlignment - 1;
 
         numSmallSubpagePools = nSubpages;
         smallSubpagePools = newSubpagePoolArray(numSmallSubpagePools);
@@ -152,7 +150,7 @@ private void tcacheAllocateSmall(PoolThreadCache cache, PooledByteBuf<T> buf, fi
             return;
         }
 
-        /**
+        /*
          * Synchronize on the head. This is needed as {@link PoolChunk#allocateSubpage(int)} and
          * {@link PoolChunk#free(long)} may modify the doubly linked list as well.
          */
@@ -235,7 +233,7 @@ void free(PoolChunk<T> chunk, ByteBuffer nioBuffer, long handle, int normCapacit
         }
     }
 
-    private SizeClass sizeClass(long handle) {
+    private static SizeClass sizeClass(long handle) {
         return isSubpage(handle) ? SizeClass.Small : SizeClass.Normal;
     }
 
@@ -559,12 +557,13 @@ boolean isDirect() {
 
         @Override
         protected PoolChunk<byte[]> newChunk(int pageSize, int maxPageIdx, int pageShifts, int chunkSize) {
-            return new PoolChunk<byte[]>(this, newByteArray(chunkSize), pageSize, pageShifts, chunkSize, maxPageIdx, 0);
+            return new PoolChunk<byte[]>(
+                    this, null, newByteArray(chunkSize), pageSize, pageShifts, chunkSize, maxPageIdx);
         }
 
         @Override
         protected PoolChunk<byte[]> newUnpooledChunk(int capacity) {
-            return new PoolChunk<byte[]>(this, newByteArray(capacity), capacity, 0);
+            return new PoolChunk<byte[]>(this, null, newByteArray(capacity), capacity);
         }
 
         @Override
@@ -601,43 +600,31 @@ boolean isDirect() {
             return true;
         }
 
-        // mark as package-private, only for unit test
-        int offsetCacheLine(ByteBuffer memory) {
-            // We can only calculate the offset if Unsafe is present as otherwise directBufferAddress(...) will
-            // throw an NPE.
-            int remainder = HAS_UNSAFE
-                    ? (int) (PlatformDependent.directBufferAddress(memory) & directMemoryCacheAlignmentMask)
-                    : 0;
-
-            // offset = alignment - address & (alignment - 1)
-            return directMemoryCacheAlignment - remainder;
-        }
-
         @Override
         protected PoolChunk<ByteBuffer> newChunk(int pageSize, int maxPageIdx,
             int pageShifts, int chunkSize) {
             if (directMemoryCacheAlignment == 0) {
-                return new PoolChunk<ByteBuffer>(this,
-                        allocateDirect(chunkSize), pageSize, pageShifts,
-                        chunkSize, maxPageIdx, 0);
+                ByteBuffer memory = allocateDirect(chunkSize);
+                return new PoolChunk<ByteBuffer>(this, memory, memory, pageSize, pageShifts,
+                        chunkSize, maxPageIdx);
             }
-            final ByteBuffer memory = allocateDirect(chunkSize
-                    + directMemoryCacheAlignment);
-            return new PoolChunk<ByteBuffer>(this, memory, pageSize,
-                    pageShifts, chunkSize, maxPageIdx,
-                    offsetCacheLine(memory));
+
+            final ByteBuffer base = allocateDirect(chunkSize + directMemoryCacheAlignment);
+            final ByteBuffer memory = PlatformDependent.alignDirectBuffer(base, directMemoryCacheAlignment);
+            return new PoolChunk<ByteBuffer>(this, base, memory, pageSize,
+                    pageShifts, chunkSize, maxPageIdx);
         }
 
         @Override
         protected PoolChunk<ByteBuffer> newUnpooledChunk(int capacity) {
             if (directMemoryCacheAlignment == 0) {
-                return new PoolChunk<ByteBuffer>(this,
-                        allocateDirect(capacity), capacity, 0);
+                ByteBuffer memory = allocateDirect(capacity);
+                return new PoolChunk<ByteBuffer>(this, memory, memory, capacity);
             }
-            final ByteBuffer memory = allocateDirect(capacity
-                    + directMemoryCacheAlignment);
-            return new PoolChunk<ByteBuffer>(this, memory, capacity,
-                    offsetCacheLine(memory));
+
+            final ByteBuffer base = allocateDirect(capacity + directMemoryCacheAlignment);
+            final ByteBuffer memory = PlatformDependent.alignDirectBuffer(base, directMemoryCacheAlignment);
+            return new PoolChunk<ByteBuffer>(this, base, memory, capacity);
         }
 
         private static ByteBuffer allocateDirect(int capacity) {
@@ -648,9 +635,9 @@ private static ByteBuffer allocateDirect(int capacity) {
         @Override
         protected void destroyChunk(PoolChunk<ByteBuffer> chunk) {
             if (PlatformDependent.useDirectBufferNoCleaner()) {
-                PlatformDependent.freeDirectNoCleaner(chunk.memory);
+                PlatformDependent.freeDirectNoCleaner((ByteBuffer) chunk.base);
             } else {
-                PlatformDependent.freeDirectBuffer(chunk.memory);
+                PlatformDependent.freeDirectBuffer((ByteBuffer) chunk.base);
             }
         }
 
diff --git a/buffer/src/main/java/io/netty/buffer/PoolChunk.java b/buffer/src/main/java/io/netty/buffer/PoolChunk.java
index b9604b31302..b9a1b4cb49b 100644
--- a/buffer/src/main/java/io/netty/buffer/PoolChunk.java
+++ b/buffer/src/main/java/io/netty/buffer/PoolChunk.java
@@ -141,9 +141,9 @@ final class PoolChunk<T> implements PoolChunkMetric {
     static final int RUN_OFFSET_SHIFT = SIZE_BIT_LENGTH + SIZE_SHIFT;
 
     final PoolArena<T> arena;
+    final Object base;
     final T memory;
     final boolean unpooled;
-    final int offset;
 
     /**
      * store the first page and last page of each avail run
@@ -181,14 +181,14 @@ final class PoolChunk<T> implements PoolChunkMetric {
     //private long pad0, pad1, pad2, pad3, pad4, pad5, pad6, pad7;
 
     @SuppressWarnings("unchecked")
-    PoolChunk(PoolArena<T> arena, T memory, int pageSize, int pageShifts, int chunkSize, int maxPageIdx, int offset) {
+    PoolChunk(PoolArena<T> arena, Object base, T memory, int pageSize, int pageShifts, int chunkSize, int maxPageIdx) {
         unpooled = false;
         this.arena = arena;
+        this.base = base;
         this.memory = memory;
         this.pageSize = pageSize;
         this.pageShifts = pageShifts;
         this.chunkSize = chunkSize;
-        this.offset = offset;
         freeBytes = chunkSize;
 
         runsAvail = newRunsAvailqueueArray(maxPageIdx);
@@ -204,11 +204,11 @@ final class PoolChunk<T> implements PoolChunkMetric {
     }
 
     /** Creates a special chunk that is not pooled. */
-    PoolChunk(PoolArena<T> arena, T memory, int size, int offset) {
+    PoolChunk(PoolArena<T> arena, Object base, T memory, int size) {
         unpooled = true;
         this.arena = arena;
+        this.base = base;
         this.memory = memory;
-        this.offset = offset;
         pageSize = 0;
         pageShifts = 0;
         runsAvailMap = null;
@@ -569,9 +569,8 @@ void initBufWithSubpage(PooledByteBuf<T> buf, ByteBuffer nioBuffer, long handle,
         assert s.doNotDestroy;
         assert reqCapacity <= s.elemSize;
 
-        buf.init(this, nioBuffer, handle,
-                 (runOffset << pageShifts) + bitmapIdx * s.elemSize + offset,
-                 reqCapacity, s.elemSize, threadCache);
+        int offset = (runOffset << pageShifts) + bitmapIdx * s.elemSize;
+        buf.init(this, nioBuffer, handle, offset, reqCapacity, s.elemSize, threadCache);
     }
 
     @Override
diff --git a/buffer/src/main/java/io/netty/buffer/PooledByteBuf.java b/buffer/src/main/java/io/netty/buffer/PooledByteBuf.java
index 2c3aef6fc4b..f0190850734 100644
--- a/buffer/src/main/java/io/netty/buffer/PooledByteBuf.java
+++ b/buffer/src/main/java/io/netty/buffer/PooledByteBuf.java
@@ -52,7 +52,7 @@ void init(PoolChunk<T> chunk, ByteBuffer nioBuffer,
     }
 
     void initUnpooled(PoolChunk<T> chunk, int length) {
-        init0(chunk, null, 0, chunk.offset, length, length, null);
+        init0(chunk, null, 0, 0, length, length, null);
     }
 
     private void init0(PoolChunk<T> chunk, ByteBuffer nioBuffer,
diff --git a/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java b/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java
index a3c8c605f41..dd9e36f55b7 100644
--- a/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java
+++ b/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java
@@ -63,15 +63,19 @@ public void run() {
     };
 
     static {
+        int defaultAlignment = SystemPropertyUtil.getInt(
+                "io.netty.allocator.directMemoryCacheAlignment", 0);
         int defaultPageSize = SystemPropertyUtil.getInt("io.netty.allocator.pageSize", 8192);
         Throwable pageSizeFallbackCause = null;
         try {
-            validateAndCalculatePageShifts(defaultPageSize);
+            validateAndCalculatePageShifts(defaultPageSize, defaultAlignment);
         } catch (Throwable t) {
             pageSizeFallbackCause = t;
             defaultPageSize = 8192;
+            defaultAlignment = 0;
         }
         DEFAULT_PAGE_SIZE = defaultPageSize;
+        DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT = defaultAlignment;
 
         int defaultMaxOrder = SystemPropertyUtil.getInt("io.netty.allocator.maxOrder", 11);
         Throwable maxOrderFallbackCause = null;
@@ -142,9 +146,6 @@ public void run() {
         DEFAULT_USE_CACHE_FOR_ALL_THREADS = SystemPropertyUtil.getBoolean(
                 "io.netty.allocator.useCacheForAllThreads", true);
 
-        DEFAULT_DIRECT_MEMORY_CACHE_ALIGNMENT = SystemPropertyUtil.getInt(
-                "io.netty.allocator.directMemoryCacheAlignment", 0);
-
         // Use 1023 by default as we use an ArrayDeque as backing storage which will then allocate an internal array
         // of 1024 elements. Otherwise we would allocate 2048 and only use 1024 which is wasteful.
         DEFAULT_MAX_CACHED_BYTEBUFFERS_PER_CHUNK = SystemPropertyUtil.getInt(
@@ -266,6 +267,17 @@ public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectA
         threadCache = new PoolThreadLocalCache(useCacheForAllThreads);
         this.smallCacheSize = smallCacheSize;
         this.normalCacheSize = normalCacheSize;
+
+        if (directMemoryCacheAlignment != 0) {
+            if (!PlatformDependent.hasAlignDirectByteBuffer()) {
+                throw new UnsupportedOperationException("Buffer alignment is not supported. " +
+                        "Either Unsafe or ByteBuffer.alignSlice() must be available.");
+            }
+
+            // Ensure page size is a whole multiple of the alignment, or bump it to the next whole multiple.
+            pageSize = (int) PlatformDependent.align(pageSize, directMemoryCacheAlignment);
+        }
+
         chunkSize = validateAndCalculateChunkSize(pageSize, maxOrder);
 
         checkPositiveOrZero(nHeapArena, "nHeapArena");
@@ -281,7 +293,7 @@ public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectA
                     + directMemoryCacheAlignment + " (expected: power of two)");
         }
 
-        int pageShifts = validateAndCalculatePageShifts(pageSize);
+        int pageShifts = validateAndCalculatePageShifts(pageSize, directMemoryCacheAlignment);
 
         if (nHeapArena > 0) {
             heapArenas = newArenaArray(nHeapArena);
@@ -321,15 +333,20 @@ private static <T> PoolArena<T>[] newArenaArray(int size) {
         return new PoolArena[size];
     }
 
-    private static int validateAndCalculatePageShifts(int pageSize) {
+    private static int validateAndCalculatePageShifts(int pageSize, int alignment) {
         if (pageSize < MIN_PAGE_SIZE) {
-            throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + ")");
+            throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: " + MIN_PAGE_SIZE + ')');
         }
 
         if ((pageSize & pageSize - 1) != 0) {
             throw new IllegalArgumentException("pageSize: " + pageSize + " (expected: power of 2)");
         }
 
+        if (pageSize < alignment) {
+            throw new IllegalArgumentException("Alignment cannot be greater than page size. " +
+                    "Alignment: " + alignment + ", page size: " + pageSize + '.');
+        }
+
         // Logarithm base 2. At this point we know that pageSize is a power of two.
         return Integer.SIZE - 1 - Integer.numberOfLeadingZeros(pageSize);
     }
diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent.java b/common/src/main/java/io/netty/util/internal/PlatformDependent.java
index 89daf214b00..3e752ad0434 100644
--- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java
+++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java
@@ -768,6 +768,32 @@ public static void freeDirectNoCleaner(ByteBuffer buffer) {
         decrementMemoryCounter(capacity);
     }
 
+    public static boolean hasAlignDirectByteBuffer() {
+        return hasUnsafe() || PlatformDependent0.hasAlignSliceMethod();
+    }
+
+    public static ByteBuffer alignDirectBuffer(ByteBuffer buffer, int alignment) {
+        if (!buffer.isDirect()) {
+            throw new IllegalArgumentException("Cannot get aligned slice of non-direct byte buffer.");
+        }
+        if (PlatformDependent0.hasAlignSliceMethod()) {
+            return PlatformDependent0.alignSlice(buffer, alignment);
+        }
+        if (hasUnsafe()) {
+            long address = directBufferAddress(buffer);
+            long aligned = align(address, alignment);
+            buffer.position((int) (aligned - address));
+            return buffer.slice();
+        }
+        // We don't have enough information to be able to align any buffers.
+        throw new UnsupportedOperationException("Cannot align direct buffer. " +
+                "Needs either Unsafe or ByteBuffer.alignSlice method available.");
+    }
+
+    public static long align(long value, int alignment) {
+        return Pow2.align(value, alignment);
+    }
+
     private static void incrementMemoryCounter(int capacity) {
         if (DIRECT_MEMORY_COUNTER != null) {
             long newUsedMemory = DIRECT_MEMORY_COUNTER.addAndGet(capacity);
diff --git a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
index e5eb2f5b109..0609d0090e6 100644
--- a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
+++ b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java
@@ -46,6 +46,7 @@ final class PlatformDependent0 {
     private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
     private static final Throwable EXPLICIT_NO_UNSAFE_CAUSE = explicitNoUnsafeCause0();
     private static final Method ALLOCATE_ARRAY_METHOD;
+    private static final Method ALIGN_SLICE;
     private static final int JAVA_VERSION = javaVersion0();
     private static final boolean IS_ANDROID = isAndroid0();
 
@@ -398,6 +399,21 @@ public Object run() {
             ALLOCATE_ARRAY_METHOD = allocateArrayMethod;
         }
 
+        if (javaVersion() > 9) {
+            ALIGN_SLICE = (Method) AccessController.doPrivileged(new PrivilegedAction<Object>() {
+                @Override
+                public Object run() {
+                    try {
+                        return ByteBuffer.class.getDeclaredMethod("alignedSlice", int.class);
+                    } catch (Exception e) {
+                        return null;
+                    }
+                }
+            });
+        } else {
+            ALIGN_SLICE = null;
+        }
+
         INTERNAL_UNSAFE = internalUnsafe;
 
         logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}",
@@ -474,6 +490,20 @@ static ByteBuffer allocateDirectNoCleaner(int capacity) {
         return newDirectBuffer(UNSAFE.allocateMemory(Math.max(1, capacity)), capacity);
     }
 
+    static boolean hasAlignSliceMethod() {
+        return ALIGN_SLICE != null;
+    }
+
+    static ByteBuffer alignSlice(ByteBuffer buffer, int alignment) {
+        try {
+            return (ByteBuffer) ALIGN_SLICE.invoke(buffer, alignment);
+        } catch (IllegalAccessException e) {
+            throw new Error(e);
+        } catch (InvocationTargetException e) {
+            throw new Error(e);
+        }
+    }
+
     static boolean hasAllocateArrayMethod() {
         return ALLOCATE_ARRAY_METHOD != null;
     }


Test patch:
diff --git a/buffer/src/test/java/io/netty/buffer/AbstractPooledByteBufTest.java b/buffer/src/test/java/io/netty/buffer/AbstractPooledByteBufTest.java
index f7c16661a67..54797b25647 100644
--- a/buffer/src/test/java/io/netty/buffer/AbstractPooledByteBufTest.java
+++ b/buffer/src/test/java/io/netty/buffer/AbstractPooledByteBufTest.java
@@ -125,4 +125,18 @@ public void testIsContiguous() {
         assertTrue(buf.isContiguous());
         buf.release();
     }
+
+    @Test
+    public void distinctBuffersMustNotOverlap() {
+        ByteBuf a = newBuffer(16384);
+        ByteBuf b = newBuffer(65536);
+        a.setByte(a.capacity() - 1, 1);
+        b.setByte(0, 2);
+        try {
+            assertEquals(1, a.getByte(a.capacity() - 1));
+        } finally {
+            a.release();
+            b.release();
+        }
+    }
 }
diff --git a/buffer/src/test/java/io/netty/buffer/AlignedPooledByteBufAllocatorTest.java b/buffer/src/test/java/io/netty/buffer/AlignedPooledByteBufAllocatorTest.java
new file mode 100644
index 00000000000..59fcf77c0e0
--- /dev/null
+++ b/buffer/src/test/java/io/netty/buffer/AlignedPooledByteBufAllocatorTest.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2021 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.buffer;
+
+public class AlignedPooledByteBufAllocatorTest extends PooledByteBufAllocatorTest {
+    @Override
+    protected PooledByteBufAllocator newAllocator(boolean preferDirect) {
+        int directMemoryCacheAlignment = 1;
+        return new PooledByteBufAllocator(
+                preferDirect,
+                PooledByteBufAllocator.defaultNumHeapArena(),
+                PooledByteBufAllocator.defaultNumDirectArena(),
+                PooledByteBufAllocator.defaultPageSize(),
+                11,
+                PooledByteBufAllocator.defaultSmallCacheSize(),
+                64,
+                PooledByteBufAllocator.defaultUseCacheForAllThreads(),
+                directMemoryCacheAlignment);
+    }
+}
diff --git a/buffer/src/test/java/io/netty/buffer/PoolArenaTest.java b/buffer/src/test/java/io/netty/buffer/PoolArenaTest.java
index 590dce11666..a598c910f38 100644
--- a/buffer/src/test/java/io/netty/buffer/PoolArenaTest.java
+++ b/buffer/src/test/java/io/netty/buffer/PoolArenaTest.java
@@ -103,26 +103,6 @@ public void testPageIdx2size() {
         }
     }
 
-    @Test
-    public void testDirectArenaOffsetCacheLine() throws Exception {
-        assumeTrue(PlatformDependent.hasUnsafe());
-        int capacity = 5;
-        int alignment = 128;
-
-        for (int i = 0; i < 1000; i++) {
-            ByteBuffer bb = PlatformDependent.useDirectBufferNoCleaner()
-                    ? PlatformDependent.allocateDirectNoCleaner(capacity + alignment)
-                    : ByteBuffer.allocateDirect(capacity + alignment);
-
-            PoolArena.DirectArena arena = new PoolArena.DirectArena(null, 512, 9, 512, alignment);
-            int offset = arena.offsetCacheLine(bb);
-            long address = PlatformDependent.directBufferAddress(bb);
-
-            Assert.assertEquals(0, (offset + address) & (alignment - 1));
-            PlatformDependent.freeDirectBuffer(bb);
-        }
-    }
-
     @Test
     public void testAllocationCounter() {
         final PooledByteBufAllocator allocator = new PooledByteBufAllocator(
diff --git a/buffer/src/test/java/io/netty/buffer/PooledAlignedBigEndianDirectByteBufTest.java b/buffer/src/test/java/io/netty/buffer/PooledAlignedBigEndianDirectByteBufTest.java
new file mode 100644
index 00000000000..6ac7740a190
--- /dev/null
+++ b/buffer/src/test/java/io/netty/buffer/PooledAlignedBigEndianDirectByteBufTest.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2021 The Netty Project
+ *
+ * The Netty Project licenses this file to you under the Apache License,
+ * version 2.0 (the "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at:
+ *
+ *   https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
+ * under the License.
+ */
+package io.netty.buffer;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+
+import java.nio.ByteOrder;
+
+import static org.junit.Assert.assertSame;
+
+public class PooledAlignedBigEndianDirectByteBufTest extends PooledBigEndianDirectByteBufTest {
+    private static final int directMemoryCacheAlignment = 1;
+    private static PooledByteBufAllocator allocator;
+
+    @BeforeClass
+    public static void setUpAllocator() {
+        allocator = new PooledByteBufAllocator(
+                true,
+                PooledByteBufAllocator.defaultNumHeapArena(),
+                PooledByteBufAllocator.defaultNumDirectArena(),
+                PooledByteBufAllocator.defaultPageSize(),
+                11,
+                PooledByteBufAllocator.defaultSmallCacheSize(),
+                64,
+                PooledByteBufAllocator.defaultUseCacheForAllThreads(),
+                directMemoryCacheAlignment);
+    }
+
+    @AfterClass
+    public static void releaseAllocator() {
+        allocator = null;
+    }
+
+    @Override
+    protected ByteBuf alloc(int length, int maxCapacity) {
+        ByteBuf buffer = allocator.directBuffer(length, maxCapacity);
+        assertSame(ByteOrder.BIG_ENDIAN, buffer.order());
+        return buffer;
+    }
+}