diff --git a/buffer/src/main/java/io/netty/buffer/PoolArena.java b/buffer/src/main/java/io/netty/buffer/PoolArena.java index e7c98d214b..33b6efc828 100644 --- a/buffer/src/main/java/io/netty/buffer/PoolArena.java +++ b/buffer/src/main/java/io/netty/buffer/PoolArena.java @@ -674,6 +674,10 @@ abstract class PoolArena implements PoolArenaMetric { directMemoryCacheAlignment); } + private static byte[] newByteArray(int size) { + return PlatformDependent.allocateUninitializedArray(size); + } + @Override boolean isDirect() { return false; @@ -681,12 +685,12 @@ abstract class PoolArena implements PoolArenaMetric { @Override protected PoolChunk newChunk(int pageSize, int maxOrder, int pageShifts, int chunkSize) { - return new PoolChunk(this, new byte[chunkSize], pageSize, maxOrder, pageShifts, chunkSize, 0); + return new PoolChunk(this, newByteArray(chunkSize), pageSize, maxOrder, pageShifts, chunkSize, 0); } @Override protected PoolChunk newUnpooledChunk(int capacity) { - return new PoolChunk(this, new byte[capacity], capacity, 0); + return new PoolChunk(this, newByteArray(capacity), capacity, 0); } @Override diff --git a/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeHeapByteBuf.java b/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeHeapByteBuf.java index 5af5e89350..4504f46a1f 100644 --- a/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeHeapByteBuf.java +++ b/buffer/src/main/java/io/netty/buffer/UnpooledUnsafeHeapByteBuf.java @@ -29,6 +29,11 @@ class UnpooledUnsafeHeapByteBuf extends UnpooledHeapByteBuf { super(alloc, initialCapacity, maxCapacity); } + @Override + byte[] allocateArray(int initialCapacity) { + return PlatformDependent.allocateUninitializedArray(initialCapacity); + } + @Override public byte getByte(int index) { checkIndex(index); 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 05ce40b817..7cae39c880 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent.java @@ -92,6 +92,7 @@ public final class PlatformDependent { private static final long DIRECT_MEMORY_LIMIT; private static final ThreadLocalRandomProvider RANDOM_PROVIDER; private static final Cleaner CLEANER; + private static final int UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD; public static final boolean BIG_ENDIAN_NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; @@ -155,7 +156,13 @@ public final class PlatformDependent { } } DIRECT_MEMORY_LIMIT = maxDirectMemory; - logger.debug("io.netty.maxDirectMemory: {} bytes", maxDirectMemory); + logger.debug("-Dio.netty.maxDirectMemory: {} bytes", maxDirectMemory); + + int tryAllocateUninitializedArray = + SystemPropertyUtil.getInt("io.netty.uninitializedArrayAllocationThreshold", 1024); + UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD = javaVersion() >= 9 && PlatformDependent0.hasAllocateArrayMethod() ? + tryAllocateUninitializedArray : -1; + logger.debug("-Dio.netty.uninitializedArrayAllocationThreshold: {}", UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD); MAYBE_SUPER_USER = maybeSuperUser0(); @@ -172,6 +179,11 @@ public final class PlatformDependent { } } + public static byte[] allocateUninitializedArray(int size) { + return UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD < 0 || UNINITIALIZED_ARRAY_ALLOCATION_THRESHOLD > size ? + new byte[size] : PlatformDependent0.allocateUninitializedArray(size); + } + /** * Returns {@code true} if and only if the current platform is Android */ 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 bdd29a87ca..ab1b741c71 100644 --- a/common/src/main/java/io/netty/util/internal/PlatformDependent0.java +++ b/common/src/main/java/io/netty/util/internal/PlatformDependent0.java @@ -40,7 +40,9 @@ final class PlatformDependent0 { private static final long BYTE_ARRAY_BASE_OFFSET; private static final Constructor DIRECT_BUFFER_CONSTRUCTOR; private static final boolean IS_EXPLICIT_NO_UNSAFE = explicitNoUnsafe0(); + private static final Method ALLOCATE_ARRAY_METHOD; + private static final Object INTERNAL_UNSAFE; static final Unsafe UNSAFE; /** @@ -54,12 +56,15 @@ final class PlatformDependent0 { static { final ByteBuffer direct; Field addressField = null; + Method allocateArrayMethod = null; Unsafe unsafe; + Object internalUnsafe = null; if (isExplicitNoUnsafe()) { direct = null; addressField = null; unsafe = null; + internalUnsafe = null; } else { direct = ByteBuffer.allocateDirect(1); @@ -182,6 +187,7 @@ final class PlatformDependent0 { ADDRESS_FIELD_OFFSET = -1; UNALIGNED = false; DIRECT_BUFFER_CONSTRUCTOR = null; + ALLOCATE_ARRAY_METHOD = null; } else { Constructor directBufferConstructor; long address = -1; @@ -232,7 +238,6 @@ final class PlatformDependent0 { } } DIRECT_BUFFER_CONSTRUCTOR = directBufferConstructor; - ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField); boolean unaligned; Object maybeUnaligned = AccessController.doPrivileged(new PrivilegedAction() { @@ -274,8 +279,65 @@ final class PlatformDependent0 { UNALIGNED = unaligned; BYTE_ARRAY_BASE_OFFSET = arrayBaseOffset(); + + Object maybeException = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + try { + // Java9 has jdk.internal.misc.Unsafe and not all methods are propergated to + // sun.misc.Unsafe + Class internalUnsafeClass = getClassLoader(PlatformDependent0.class) + .loadClass("jdk.internal.misc.Unsafe"); + Method method = internalUnsafeClass.getDeclaredMethod("getUnsafe"); + return method.invoke(null); + } catch (Throwable e) { + return e; + } + } + }); + + if (!(maybeException instanceof Throwable)) { + internalUnsafe = maybeException; + final Object finalInternalUnsafe = internalUnsafe; + maybeException = AccessController.doPrivileged(new PrivilegedAction() { + @Override + public Object run() { + try { + return finalInternalUnsafe.getClass().getDeclaredMethod( + "allocateUninitializedArray", Class.class, int.class); + } catch (NoSuchMethodException e) { + return e; + } catch (SecurityException e) { + return e; + } + } + }); + + if (maybeException instanceof Method) { + try { + Method m = (Method) maybeException; + byte[] bytes = (byte[]) m.invoke(finalInternalUnsafe, byte.class, 8); + assert bytes.length == 8; + allocateArrayMethod = m; + } catch (IllegalAccessException e) { + maybeException = e; + } catch (InvocationTargetException e) { + maybeException = e; + } + } + } + ALLOCATE_ARRAY_METHOD = allocateArrayMethod; + + if (maybeException instanceof Throwable) { + logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): unavailable", + (Throwable) maybeException); + } else { + logger.debug("jdk.internal.misc.Unsafe.allocateUninitializedArray(int): available"); + } } + INTERNAL_UNSAFE = internalUnsafe; + logger.debug("java.nio.DirectByteBuffer.(long, int): {}", DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable"); } @@ -334,6 +396,20 @@ final class PlatformDependent0 { return newDirectBuffer(UNSAFE.allocateMemory(capacity), capacity); } + static boolean hasAllocateArrayMethod() { + return ALLOCATE_ARRAY_METHOD != null; + } + + static byte[] allocateUninitializedArray(int size) { + try { + return (byte[]) ALLOCATE_ARRAY_METHOD.invoke(INTERNAL_UNSAFE, byte.class, size); + } catch (IllegalAccessException e) { + throw new Error(e); + } catch (InvocationTargetException e) { + throw new Error(e); + } + } + static ByteBuffer newDirectBuffer(long address, int capacity) { ObjectUtil.checkPositiveOrZero(capacity, "capacity"); diff --git a/microbench/src/main/java/io/netty/microbench/internal/UnitializedArrayBenchmark.java b/microbench/src/main/java/io/netty/microbench/internal/UnitializedArrayBenchmark.java new file mode 100644 index 0000000000..03085689fe --- /dev/null +++ b/microbench/src/main/java/io/netty/microbench/internal/UnitializedArrayBenchmark.java @@ -0,0 +1,73 @@ +/* + * Copyright 2017 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: + * + * http://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.microbench.internal; + +import io.netty.microbench.util.AbstractMicrobenchmark; +import io.netty.util.internal.PlatformDependent; +import org.openjdk.jmh.annotations.Benchmark; +import org.openjdk.jmh.annotations.BenchmarkMode; +import org.openjdk.jmh.annotations.Fork; +import org.openjdk.jmh.annotations.Level; +import org.openjdk.jmh.annotations.Measurement; +import org.openjdk.jmh.annotations.Mode; +import org.openjdk.jmh.annotations.OutputTimeUnit; +import org.openjdk.jmh.annotations.Param; +import org.openjdk.jmh.annotations.Scope; +import org.openjdk.jmh.annotations.Setup; +import org.openjdk.jmh.annotations.State; +import org.openjdk.jmh.annotations.Warmup; + +import java.util.concurrent.TimeUnit; + +@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Measurement(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS) +@Fork(2) +@BenchmarkMode(Mode.AverageTime) +@OutputTimeUnit(TimeUnit.NANOSECONDS) +@State(Scope.Benchmark) +public class UnitializedArrayBenchmark extends AbstractMicrobenchmark { + + @Param({ "1", "10", "100", "1000", "10000", "100000" }) + private int size; + + @Setup(Level.Trial) + public void setupTrial() { + if (PlatformDependent.javaVersion() < 9) { + throw new IllegalStateException("Needs Java9"); + } + if (!PlatformDependent.hasUnsafe()) { + throw new IllegalStateException("Needs Unsafe"); + } + } + + @Override + protected String[] jvmArgs() { + // Ensure we minimize the GC overhead for this benchmark and also open up required package. + // See also https://shipilev.net/jvm-anatomy-park/7-initialization-costs/ + return new String[] { "-XX:+UseParallelOldGC", "-Xmx8g", "-Xms8g", + "-Xmn6g", "--add-opens", "java.base/jdk.internal.misc=ALL-UNNAMED" }; + } + + @Benchmark + public byte[] allocateInitializedByteArray() { + return new byte[size]; + } + + @Benchmark + public byte[] allocateUninitializedByteArray() { + return PlatformDependent.allocateUninitializedArray(size); + } +}