@purplefox FYI
 Fixed

Number of PoolArena's should be 2 * cores
We use 2 \* cores as the default number of EventLoop's for NIO and EPOLL, so we should do the same for the numbers of PoolArena's. This will reduce conditions.

jemalloc itself even use 4 \* cpus:
http://www.canonware.com/download/jemalloc/jemalloc-latest/doc/jemalloc.html

Related to:

https://groups.google.com/d/msg/netty/J78updTXPTU/T6-i_O6Hxu4J

buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java
buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java
diff --git a/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java b/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java
index 1ea167883ff..148e6ec0d22 100644
--- a/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java
+++ b/buffer/src/main/java/io/netty/buffer/PooledByteBufAllocator.java
@@ -69,18 +69,24 @@ public class PooledByteBufAllocator extends AbstractByteBufAllocator {
         // Determine reasonable default for nHeapArena and nDirectArena.
         // Assuming each arena has 3 chunks, the pool should not consume more than 50% of max memory.
         final Runtime runtime = Runtime.getRuntime();
+
+        // Use 2 * cores by default to reduce condition as we use 2 * cores for the number of EventLoops
+        // in NIO and EPOLL as well. If we choose a smaller number we will run into hotspots as allocation and
+        // deallocation needs to be synchronized on the PoolArena.
+        // See https://github.com/netty/netty/issues/3888
+        final int defaultMinNumArena = runtime.availableProcessors() * 2;
         final int defaultChunkSize = DEFAULT_PAGE_SIZE << DEFAULT_MAX_ORDER;
         DEFAULT_NUM_HEAP_ARENA = Math.max(0,
                 SystemPropertyUtil.getInt(
                         "io.netty.allocator.numHeapArenas",
                         (int) Math.min(
-                                runtime.availableProcessors(),
-                                Runtime.getRuntime().maxMemory() / defaultChunkSize / 2 / 3)));
+                                defaultMinNumArena,
+                                runtime.maxMemory() / defaultChunkSize / 2 / 3)));
         DEFAULT_NUM_DIRECT_ARENA = Math.max(0,
                 SystemPropertyUtil.getInt(
                         "io.netty.allocator.numDirectArenas",
                         (int) Math.min(
-                                runtime.availableProcessors(),
+                                defaultMinNumArena,
                                 PlatformDependent.maxDirectMemory() / defaultChunkSize / 2 / 3)));
 
         // cache sizes
@@ -202,7 +208,7 @@ public PooledByteBufAllocator(boolean preferDirect, int nHeapArena, int nDirectA
                                   int tinyCacheSize, int smallCacheSize, int normalCacheSize,
                                   long cacheThreadAliveCheckInterval) {
         this(preferDirect, nHeapArena, nDirectArena, pageSize, maxOrder,
-                tinyCacheSize, smallCacheSize, normalCacheSize);
+             tinyCacheSize, smallCacheSize, normalCacheSize);
     }
 
     @SuppressWarnings("unchecked")
