Skip to content

Commit

Permalink
Remove some reflection usage
Browse files Browse the repository at this point in the history
Motivation:
We've been using reflection as a way to abstract over Java API versions.
Now that we're based on Java 8, some of this reflection usage is no longer necessary.
In some cases, the APIs we were reflecting on are now directly available.
In other cases, we'd like a little more performance and can call through method handles instead.

Modification:
Remove some reflection usage that was necessary when running on Java 6 or 7, and replace it with direct calls if the code is still needed.
Replace the more performance sensitive reflection usage with MethodHandles.

Result:
Cleaner, and perhaps slightly faster, code.
  • Loading branch information
chrisvest committed May 3, 2024
1 parent dacd5c0 commit fa54995
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 397 deletions.
84 changes: 36 additions & 48 deletions common/src/main/java/io/netty/util/internal/CleanerJava6.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,44 +18,59 @@
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Objects;

import static java.lang.invoke.MethodType.methodType;


/**
* Allows to free direct {@link ByteBuffer} by using Cleaner. This is encapsulated in an extra class to be able
* to use {@link PlatformDependent0} on Android without problems.
*
* <p>
* For more details see <a href="https://github.com/netty/netty/issues/2604">#2604</a>.
*/
final class CleanerJava6 implements Cleaner {
private static final long CLEANER_FIELD_OFFSET;
private static final Method CLEAN_METHOD;
private static final Field CLEANER_FIELD;
private static final MethodHandle CLEAN_METHOD;

private static final InternalLogger logger = InternalLoggerFactory.getInstance(CleanerJava6.class);

static {
long fieldOffset;
Method clean;
Field cleanerField;
MethodHandle clean;
Throwable error = null;
final ByteBuffer direct = ByteBuffer.allocateDirect(1);
try {
Object mayBeCleanerField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try {
Field cleanerField = direct.getClass().getDeclaredField("cleaner");
if (!PlatformDependent.hasUnsafe()) {
// We need to make it accessible if we do not use Unsafe as we will access it via
// reflection.
cleanerField.setAccessible(true);
}
return cleanerField;
Class<?> cleanerClass = Class.forName("sun.misc.Cleaner");
Class<?> directBufClass = Class.forName("sun.nio.ch.DirectBuffer");
MethodHandles.Lookup lookup = MethodHandles.lookup();

// Call clean() on the cleaner
MethodHandle clean = lookup.findVirtual(
cleanerClass, "clean", methodType(void.class));
// But only if the cleaner is non-null
MethodHandle nullTest = lookup.findStatic(
Objects.class, "nonNull", methodType(boolean.class, Object.class));
clean = MethodHandles.guardWithTest(
nullTest.asType(methodType(boolean.class, cleanerClass)),
clean,
nullTest.asType(methodType(void.class, cleanerClass)));
// Change receiver to DirectBuffer, convert DirectBuffer to Cleaner by calling cleaner()
clean = MethodHandles.filterArguments(clean, 0, lookup.findVirtual(
directBufClass,
"cleaner",
methodType(cleanerClass)));
// Change receiver to ByteBuffer, convert using explicit cast to DirectBuffer
clean = MethodHandles.explicitCastArguments(clean,
methodType(void.class, ByteBuffer.class));
return clean;
} catch (Throwable cause) {
return cause;
}
Expand All @@ -65,41 +80,24 @@ public Object run() {
throw (Throwable) mayBeCleanerField;
}

cleanerField = (Field) mayBeCleanerField;

final Object cleaner;

// If we have sun.misc.Unsafe we will use it as its faster then using reflection,
// otherwise let us try reflection as last resort.
if (PlatformDependent.hasUnsafe()) {
fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField);
cleaner = PlatformDependent0.getObject(direct, fieldOffset);
} else {
fieldOffset = -1;
cleaner = cleanerField.get(direct);
}
clean = cleaner.getClass().getDeclaredMethod("clean");
clean.invoke(cleaner);
clean = (MethodHandle) mayBeCleanerField;
clean.invokeExact(direct);
} catch (Throwable t) {
// We don't have ByteBuffer.cleaner().
fieldOffset = -1;
clean = null;
error = t;
cleanerField = null;
}

if (error == null) {
logger.debug("java.nio.ByteBuffer.cleaner(): available");
} else {
logger.debug("java.nio.ByteBuffer.cleaner(): unavailable", error);
}
CLEANER_FIELD = cleanerField;
CLEANER_FIELD_OFFSET = fieldOffset;
CLEAN_METHOD = clean;
}

static boolean isSupported() {
return CLEANER_FIELD_OFFSET != -1 || CLEANER_FIELD != null;
return CLEAN_METHOD != null;
}

@Override
Expand Down Expand Up @@ -135,17 +133,7 @@ public Throwable run() {
}
}

private static void freeDirectBuffer0(ByteBuffer buffer) throws Exception {
final Object cleaner;
// If CLEANER_FIELD_OFFSET == -1 we need to use reflection to access the cleaner, otherwise we can use
// sun.misc.Unsafe.
if (CLEANER_FIELD_OFFSET == -1) {
cleaner = CLEANER_FIELD.get(buffer);
} else {
cleaner = PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET);
}
if (cleaner != null) {
CLEAN_METHOD.invoke(cleaner);
}
private static void freeDirectBuffer0(ByteBuffer buffer) throws Throwable {
CLEAN_METHOD.invokeExact(buffer);
}
}
42 changes: 21 additions & 21 deletions common/src/main/java/io/netty/util/internal/CleanerJava9.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,23 +17,26 @@

import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import sun.misc.Unsafe;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;

import static java.lang.invoke.MethodType.methodType;

/**
* Provide a way to clean a ByteBuffer on Java9+.
*/
final class CleanerJava9 implements Cleaner {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(CleanerJava9.class);

private static final Method INVOKE_CLEANER;
private static final MethodHandle INVOKE_CLEANER;

static {
final Method method;
final MethodHandle method;
final Throwable error;
if (PlatformDependent0.hasUnsafe()) {
final ByteBuffer buffer = ByteBuffer.allocateDirect(1);
Expand All @@ -42,15 +45,14 @@ final class CleanerJava9 implements Cleaner {
public Object run() {
try {
// See https://bugs.openjdk.java.net/browse/JDK-8171377
Method m = PlatformDependent0.UNSAFE.getClass().getDeclaredMethod(
"invokeCleaner", ByteBuffer.class);
m.invoke(PlatformDependent0.UNSAFE, buffer);
return m;
} catch (NoSuchMethodException e) {
return e;
} catch (InvocationTargetException e) {
return e;
} catch (IllegalAccessException e) {
Class<? extends Unsafe> unsafeClass = PlatformDependent0.UNSAFE.getClass();
MethodHandles.Lookup lookup = MethodHandles.lookup();
MethodHandle invokeCleaner = lookup.findVirtual(
unsafeClass, "invokeCleaner", methodType(void.class, ByteBuffer.class));
invokeCleaner = invokeCleaner.bindTo(PlatformDependent0.UNSAFE);
invokeCleaner.invokeExact(buffer);
return invokeCleaner;
} catch (Throwable e) {
return e;
}
}
Expand All @@ -60,7 +62,7 @@ public Object run() {
method = null;
error = (Throwable) maybeInvokeMethod;
} else {
method = (Method) maybeInvokeMethod;
method = (MethodHandle) maybeInvokeMethod;
error = null;
}
} else {
Expand All @@ -85,7 +87,7 @@ public void freeDirectBuffer(ByteBuffer buffer) {
// See https://bugs.openjdk.java.net/browse/JDK-8191053.
if (System.getSecurityManager() == null) {
try {
INVOKE_CLEANER.invoke(PlatformDependent0.UNSAFE, buffer);
INVOKE_CLEANER.invokeExact(buffer);
} catch (Throwable cause) {
PlatformDependent0.throwException(cause);
}
Expand All @@ -95,14 +97,12 @@ public void freeDirectBuffer(ByteBuffer buffer) {
}

private static void freeDirectBufferPrivileged(final ByteBuffer buffer) {
Exception error = AccessController.doPrivileged(new PrivilegedAction<Exception>() {
Throwable error = AccessController.doPrivileged(new PrivilegedAction<Throwable>() {
@Override
public Exception run() {
public Throwable run() {
try {
INVOKE_CLEANER.invoke(PlatformDependent0.UNSAFE, buffer);
} catch (InvocationTargetException e) {
return e;
} catch (IllegalAccessException e) {
INVOKE_CLEANER.invokeExact(buffer);
} catch (Throwable e) {
return e;
}
return null;
Expand Down
78 changes: 24 additions & 54 deletions common/src/main/java/io/netty/util/internal/PlatformDependent.java
Original file line number Diff line number Diff line change
Expand Up @@ -31,16 +31,17 @@
import org.jctools.util.Pow2;
import org.jctools.util.UnsafeAccess;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.Arrays;
Expand All @@ -61,6 +62,7 @@
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Stream;

import static io.netty.util.internal.PlatformDependent0.HASH_CODE_ASCII_SEED;
import static io.netty.util.internal.PlatformDependent0.HASH_CODE_C1;
Expand All @@ -69,6 +71,7 @@
import static io.netty.util.internal.PlatformDependent0.unalignedAccess;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.invoke.MethodType.methodType;

/**
* Utility that detects various properties specific to the current runtime
Expand Down Expand Up @@ -211,42 +214,30 @@ public void freeDirectBuffer(ByteBuffer buffer) {
static void addFilesystemOsClassifiers(final Set<String> allowedClassifiers,
final Set<String> availableClassifiers) {
for (final String osReleaseFileName : OS_RELEASE_FILES) {
final File file = new File(osReleaseFileName);
final Path file = Paths.get(osReleaseFileName);
boolean found = AccessController.doPrivileged(new PrivilegedAction<Boolean>() {
@Override
public Boolean run() {
Pattern lineSplitPattern = Pattern.compile("[ ]+");
try {
if (file.exists()) {
BufferedReader reader = null;
try {
reader = new BufferedReader(
new InputStreamReader(
new FileInputStream(file), CharsetUtil.UTF_8));

String line;
while ((line = reader.readLine()) != null) {
if (Files.exists(file)) {
try (Stream<String> lines = Files.lines(file, StandardCharsets.UTF_8)) {
lines.forEach(line -> {
if (line.startsWith(LINUX_ID_PREFIX)) {
String id = normalizeOsReleaseVariableValue(
line.substring(LINUX_ID_PREFIX.length()));
addClassifier(allowedClassifiers, availableClassifiers, id);
} else if (line.startsWith(LINUX_ID_LIKE_PREFIX)) {
line = normalizeOsReleaseVariableValue(
line.substring(LINUX_ID_LIKE_PREFIX.length()));
addClassifier(allowedClassifiers, availableClassifiers, line.split("[ ]+"));
addClassifier(allowedClassifiers, availableClassifiers,
lineSplitPattern.split(line));
}
}
});
} catch (SecurityException e) {
logger.debug("Unable to read {}", osReleaseFileName, e);
} catch (IOException e) {
logger.debug("Error while reading content of {}", osReleaseFileName, e);
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException ignored) {
// Ignore
}
}
}
// specification states we should only fall back if /etc/os-release does not exist
return true;
Expand Down Expand Up @@ -441,7 +432,7 @@ public static void throwException(Throwable t) {
if (hasUnsafe()) {
PlatformDependent0.throwException(t);
} else {
PlatformDependent.<RuntimeException>throwException0(t);
throwException0(t);
}
}

Expand Down Expand Up @@ -1193,49 +1184,28 @@ private static Pattern getMaxDirectMemorySizeArgPattern() {
*
* @return The estimated max direct memory, in bytes.
*/
@SuppressWarnings("unchecked")
public static long estimateMaxDirectMemory() {
long maxDirectMemory = PlatformDependent0.bitsMaxDirectMemory();
if (maxDirectMemory > 0) {
return maxDirectMemory;
}

ClassLoader systemClassLoader = null;
try {
systemClassLoader = getSystemClassLoader();

// When using IBM J9 / Eclipse OpenJ9 we should not use VM.maxDirectMemory() as it not reflects the
// correct value.
// See:
// - https://github.com/netty/netty/issues/7654
String vmName = SystemPropertyUtil.get("java.vm.name", "").toLowerCase();
if (!vmName.startsWith("ibm j9") &&
// https://github.com/eclipse/openj9/blob/openj9-0.8.0/runtime/include/vendor_version.h#L53
!vmName.startsWith("eclipse openj9")) {
// Try to get from sun.misc.VM.maxDirectMemory() which should be most accurate.
Class<?> vmClass = Class.forName("sun.misc.VM", true, systemClassLoader);
Method m = vmClass.getDeclaredMethod("maxDirectMemory");
maxDirectMemory = ((Number) m.invoke(null)).longValue();
}
} catch (Throwable ignored) {
// Ignore
}

if (maxDirectMemory > 0) {
return maxDirectMemory;
}

try {
// Now try to get the JVM option (-XX:MaxDirectMemorySize) and parse it.
// Note that we are using reflection because Android doesn't have these classes.
ClassLoader systemClassLoader = getSystemClassLoader();
Class<?> mgmtFactoryClass = Class.forName(
"java.lang.management.ManagementFactory", true, systemClassLoader);
Class<?> runtimeClass = Class.forName(
"java.lang.management.RuntimeMXBean", true, systemClassLoader);

Object runtime = mgmtFactoryClass.getDeclaredMethod("getRuntimeMXBean").invoke(null);

@SuppressWarnings("unchecked")
List<String> vmArgs = (List<String>) runtimeClass.getDeclaredMethod("getInputArguments").invoke(runtime);
MethodHandles.Lookup lookup = MethodHandles.publicLookup();
MethodHandle getRuntime = lookup.findStatic(
mgmtFactoryClass, "getRuntimeMXBean", methodType(runtimeClass));
MethodHandle getInputArguments = lookup.findVirtual(
runtimeClass, "getInputArguments", methodType(List.class));
List<String> vmArgs = (List<String>) getInputArguments.invoke(getRuntime.invoke());

Pattern maxDirectMemorySizeArgPattern = getMaxDirectMemorySizeArgPattern();

Expand Down
Loading

0 comments on commit fa54995

Please sign in to comment.