We should be able to use the ByteBuffer cleaner on java8 (and earlier… (#8234)

* We should be able to use the ByteBuffer cleaner on java8 (and earlier versions) even if sun.misc.Unsafe is not present.

Motivation:

At the moment we have a hard dependency on sun.misc.Unsafe to use the Cleaner on Java8 and earlier. This is not really needed as we can still use pure reflection if sun.misc.Unsafe is not present.

Modifications:

Refactor Cleaner6 to fallback to pure reflection if sun.misc.Unsafe is not present on system.

Result:

More timely releasing of direct memory on Java8 and earlier when sun.misc.Unsafe is not present.
This commit is contained in:
Norman Maurer 2018-08-30 07:43:10 +02:00 committed by GitHub
parent 4a5b61fc13
commit 38eee409c8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 89 additions and 24 deletions

View File

@ -21,6 +21,8 @@ import io.netty.util.internal.logging.InternalLoggerFactory;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.security.AccessController;
import java.security.PrivilegedAction;
/** /**
@ -32,19 +34,50 @@ import java.nio.ByteBuffer;
final class CleanerJava6 implements Cleaner { final class CleanerJava6 implements Cleaner {
private static final long CLEANER_FIELD_OFFSET; private static final long CLEANER_FIELD_OFFSET;
private static final Method CLEAN_METHOD; private static final Method CLEAN_METHOD;
private static final Field CLEANER_FIELD;
private static final InternalLogger logger = InternalLoggerFactory.getInstance(CleanerJava6.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(CleanerJava6.class);
static { static {
long fieldOffset = -1; long fieldOffset;
Method clean = null; Method clean;
Field cleanerField;
Throwable error = null; Throwable error = null;
if (PlatformDependent0.hasUnsafe()) { final ByteBuffer direct = ByteBuffer.allocateDirect(1);
ByteBuffer direct = ByteBuffer.allocateDirect(1); try {
Object mayBeCleanerField = AccessController.doPrivileged(new PrivilegedAction<Object>() {
@Override
public Object run() {
try { try {
Field cleanerField = direct.getClass().getDeclaredField("cleaner"); 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;
} catch (Throwable cause) {
return cause;
}
}
});
if (mayBeCleanerField instanceof Throwable) {
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); fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField);
Object cleaner = PlatformDependent0.getObject(direct, fieldOffset); cleaner = PlatformDependent0.getObject(direct, fieldOffset);
} else {
fieldOffset = -1;
cleaner = cleanerField.get(direct);
}
clean = cleaner.getClass().getDeclaredMethod("clean"); clean = cleaner.getClass().getDeclaredMethod("clean");
clean.invoke(cleaner); clean.invoke(cleaner);
} catch (Throwable t) { } catch (Throwable t) {
@ -52,21 +85,21 @@ final class CleanerJava6 implements Cleaner {
fieldOffset = -1; fieldOffset = -1;
clean = null; clean = null;
error = t; error = t;
cleanerField = null;
} }
} else {
error = new UnsupportedOperationException("sun.misc.Unsafe unavailable");
}
if (error == null) { if (error == null) {
logger.debug("java.nio.ByteBuffer.cleaner(): available"); logger.debug("java.nio.ByteBuffer.cleaner(): available");
} else { } else {
logger.debug("java.nio.ByteBuffer.cleaner(): unavailable", error); logger.debug("java.nio.ByteBuffer.cleaner(): unavailable", error);
} }
CLEANER_FIELD = cleanerField;
CLEANER_FIELD_OFFSET = fieldOffset; CLEANER_FIELD_OFFSET = fieldOffset;
CLEAN_METHOD = clean; CLEAN_METHOD = clean;
} }
static boolean isSupported() { static boolean isSupported() {
return CLEANER_FIELD_OFFSET != -1; return CLEANER_FIELD_OFFSET != -1 || CLEANER_FIELD != null;
} }
@Override @Override
@ -74,13 +107,45 @@ final class CleanerJava6 implements Cleaner {
if (!buffer.isDirect()) { if (!buffer.isDirect()) {
return; return;
} }
if (System.getSecurityManager() == null) {
try { try {
Object cleaner = PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET); freeDirectBuffer0(buffer);
if (cleaner != null) {
CLEAN_METHOD.invoke(cleaner);
}
} catch (Throwable cause) { } catch (Throwable cause) {
PlatformDependent0.throwException(cause); PlatformDependent0.throwException(cause);
} }
} else {
freeDirectBufferPrivileged(buffer);
}
}
private static void freeDirectBufferPrivileged(final ByteBuffer buffer) {
Throwable cause = AccessController.doPrivileged(new PrivilegedAction<Throwable>() {
@Override
public Throwable run() {
try {
freeDirectBuffer0(buffer);
return null;
} catch (Throwable cause) {
return cause;
}
}
});
if (cause != null) {
PlatformDependent0.throwException(cause);
}
}
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);
}
} }
} }

View File

@ -164,7 +164,7 @@ public final class PlatformDependent {
MAYBE_SUPER_USER = maybeSuperUser0(); MAYBE_SUPER_USER = maybeSuperUser0();
if (!isAndroid() && hasUnsafe()) { if (!isAndroid()) {
// only direct to method if we are not running on android. // only direct to method if we are not running on android.
// See https://github.com/netty/netty/issues/2604 // See https://github.com/netty/netty/issues/2604
if (javaVersion() >= 9) { if (javaVersion() >= 9) {