Skip to content

Commit

Permalink
Remove some reflection usage (#14032)
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.

Also remove the animal sniffer maven plug-in, because animal sniffer
does not support signature polymorphic method calls.
See mojohaus/animal-sniffer#67

Result:
Cleaner, and perhaps slightly faster, code.

Fixes #14009
  • Loading branch information
chrisvest committed May 7, 2024
1 parent dacd5c0 commit 9daa15f
Show file tree
Hide file tree
Showing 11 changed files with 200 additions and 492 deletions.
43 changes: 0 additions & 43 deletions codec/src/test/resources/io/netty/handler/codec/xml/sample-04.xml
Original file line number Diff line number Diff line change
Expand Up @@ -287,49 +287,6 @@
<maxmem>1024m</maxmem>
</configuration>
</plugin>
<plugin>
<!-- ensure that only methods available in java 1.6 can
be used even when compiling with java 1.7+ -->
<groupId>org.codehaus.mojo</groupId>
<artifactId>animal-sniffer-maven-plugin</artifactId>
<version>1.15</version>
<configuration>
<signature>
<groupId>org.codehaus.mojo.signature</groupId>
<artifactId>java16</artifactId>
<version>1.1</version>
</signature>
<ignores>
<ignore>sun.misc.Unsafe</ignore>
<ignore>sun.misc.Cleaner</ignore>

<ignore>java.util.zip.Deflater</ignore>

<!-- Used for NIO UDP multicast -->
<ignore>java.nio.channels.DatagramChannel</ignore>
<ignore>java.nio.channels.MembershipKey</ignore>
<ignore>java.net.StandardProtocolFamily</ignore>

<!-- Used for NIO. 2 -->
<ignore>java.nio.channels.AsynchronousChannel</ignore>
<ignore>java.nio.channels.AsynchronousSocketChannel</ignore>
<ignore>java.nio.channels.AsynchronousServerSocketChannel</ignore>
<ignore>java.nio.channels.AsynchronousChannelGroup</ignore>
<ignore>java.nio.channels.NetworkChannel</ignore>
<ignore>java.nio.channels.InterruptedByTimeoutException</ignore>
<ignore>java.net.StandardSocketOptions</ignore>
<ignore>java.net.SocketOption</ignore>
</ignores>
</configuration>
<executions>
<execution>
<phase>process-classes</phase>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-checkstyle-plugin</artifactId>
<version>2.12.1</version>
Expand Down
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

0 comments on commit 9daa15f

Please sign in to comment.