Allow to free direct buffers on java9 again

Motivation:

Java9 adds a new method to Unsafe which allows to free direct ByteBuffer via the cleaner without the need to use an commandline arguments.

Modifications:

- Add Cleaner interface
- Add CleanerJava9 which will be used when using Java9+ and take care of release direct ByteBuffer
- Let Cleaner0 implement Cleaner

Result:

Be able to free direct ByteBuffer on Java9+ again without any commandline arguments.
This commit is contained in:
Norman Maurer 2017-03-23 18:13:45 -07:00
parent 4c77e7c55a
commit 7b6119a0a4
5 changed files with 185 additions and 85 deletions

View File

@ -0,0 +1,29 @@
/*
* 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.util.internal;
import java.nio.ByteBuffer;
/**
* Allows to free direct {@link ByteBuffer}s.
*/
interface Cleaner {
/**
* Free a direct {@link ByteBuffer} if possible
*/
void freeDirectBuffer(ByteBuffer buffer);
}

View File

@ -29,41 +29,32 @@ import java.nio.ByteBuffer;
* *
* For more details see <a href="https://github.com/netty/netty/issues/2604">#2604</a>. * For more details see <a href="https://github.com/netty/netty/issues/2604">#2604</a>.
*/ */
final class Cleaner0 { 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 boolean CLEANER_IS_RUNNABLE;
private static final InternalLogger logger = InternalLoggerFactory.getInstance(Cleaner0.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(CleanerJava6.class);
static { static {
ByteBuffer direct = ByteBuffer.allocateDirect(1);
Field cleanerField;
long fieldOffset = -1; long fieldOffset = -1;
Method clean = null; Method clean = null;
boolean cleanerIsRunnable = false;
Throwable error = null; Throwable error = null;
if (PlatformDependent0.hasUnsafe()) { if (PlatformDependent0.hasUnsafe()) {
ByteBuffer direct = ByteBuffer.allocateDirect(1);
try { try {
cleanerField = direct.getClass().getDeclaredField("cleaner"); Field cleanerField = direct.getClass().getDeclaredField("cleaner");
fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField); fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField);
Object cleaner = PlatformDependent0.getObject(direct, fieldOffset); Object cleaner = PlatformDependent0.getObject(direct, fieldOffset);
try { clean = cleaner.getClass().getDeclaredMethod("clean");
// Cleaner implements Runnable from JDK9 onwards. clean.invoke(cleaner);
Runnable runnable = (Runnable) cleaner;
runnable.run();
cleanerIsRunnable = true;
} catch (ClassCastException ignored) {
clean = cleaner.getClass().getDeclaredMethod("clean");
clean.invoke(cleaner);
}
} catch (Throwable t) { } catch (Throwable t) {
// We don't have ByteBuffer.cleaner(). // We don't have ByteBuffer.cleaner().
fieldOffset = -1; fieldOffset = -1;
clean = null; clean = null;
cleanerIsRunnable = false;
error = t; error = t;
} }
} 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");
@ -72,31 +63,24 @@ final class Cleaner0 {
} }
CLEANER_FIELD_OFFSET = fieldOffset; CLEANER_FIELD_OFFSET = fieldOffset;
CLEAN_METHOD = clean; CLEAN_METHOD = clean;
CLEANER_IS_RUNNABLE = cleanerIsRunnable;
// free buffer if possible
freeDirectBuffer(direct);
} }
static void freeDirectBuffer(ByteBuffer buffer) { static boolean isSupported() {
if (CLEANER_FIELD_OFFSET == -1 || !buffer.isDirect()) { return CLEANER_FIELD_OFFSET != -1;
}
@Override
public void freeDirectBuffer(ByteBuffer buffer) {
if (!buffer.isDirect()) {
return; return;
} }
assert CLEAN_METHOD != null || CLEANER_IS_RUNNABLE:
"CLEANER_FIELD_OFFSET != -1 implies CLEAN_METHOD != null or CLEANER_IS_RUNNABLE == true";
try { try {
Object cleaner = PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET); Object cleaner = PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET);
if (cleaner != null) { if (cleaner != null) {
if (CLEANER_IS_RUNNABLE) { CLEAN_METHOD.invoke(cleaner);
((Runnable) cleaner).run();
} else {
CLEAN_METHOD.invoke(cleaner);
}
} }
} catch (Throwable t) { } catch (Throwable cause) {
// Nothing we can do here. PlatformDependent0.throwException(cause);
} }
} }
private Cleaner0() { }
} }

View File

@ -0,0 +1,82 @@
/*
* 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.util.internal;
import io.netty.util.internal.logging.InternalLogger;
import io.netty.util.internal.logging.InternalLoggerFactory;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
/**
* 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;
static {
final Method method;
final Throwable error;
if (PlatformDependent0.hasUnsafe()) {
ByteBuffer buffer = ByteBuffer.allocateDirect(1);
Object maybeInvokeMethod;
try {
// See https://bugs.openjdk.java.net/browse/JDK-8171377
Method m = PlatformDependent0.UNSAFE.getClass().getDeclaredMethod("invokeCleaner", ByteBuffer.class);
m.invoke(PlatformDependent0.UNSAFE, buffer);
maybeInvokeMethod = m;
} catch (NoSuchMethodException e) {
maybeInvokeMethod = e;
} catch (InvocationTargetException e) {
maybeInvokeMethod = e;
} catch (IllegalAccessException e) {
maybeInvokeMethod = e;
}
if (maybeInvokeMethod instanceof Throwable) {
method = null;
error = (Throwable) maybeInvokeMethod;
} else {
method = (Method) maybeInvokeMethod;
error = null;
}
} else {
method = null;
error = new UnsupportedOperationException("sun.misc.Unsafe unavailable");
}
if (error == null) {
logger.debug("java.nio.ByteBuffer.cleaner(): available");
} else {
logger.debug("java.nio.ByteBuffer.cleaner(): unavailable", error);
}
INVOKE_CLEANER = method;
}
static boolean isSupported() {
return INVOKE_CLEANER != null;
}
@Override
public void freeDirectBuffer(ByteBuffer buffer) {
try {
INVOKE_CLEANER.invoke(PlatformDependent0.UNSAFE, buffer);
} catch (Throwable cause) {
PlatformDependent0.throwException(cause);
}
}
}

View File

@ -67,9 +67,6 @@ public final class PlatformDependent {
private static final Pattern MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN = Pattern.compile( private static final Pattern MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN = Pattern.compile(
"\\s*-XX:MaxDirectMemorySize\\s*=\\s*([0-9]+)\\s*([kKmMgG]?)\\s*$"); "\\s*-XX:MaxDirectMemorySize\\s*=\\s*([0-9]+)\\s*([kKmMgG]?)\\s*$");
// this must be initialized before any code below triggers initialization of PlatformDependent0
private static final boolean IS_EXPLICIT_NO_UNSAFE = explicitNoUnsafe0();
private static final boolean IS_ANDROID = isAndroid0(); private static final boolean IS_ANDROID = isAndroid0();
private static final boolean IS_WINDOWS = isWindows0(); private static final boolean IS_WINDOWS = isWindows0();
private static final boolean MAYBE_SUPER_USER; private static final boolean MAYBE_SUPER_USER;
@ -99,9 +96,17 @@ public final class PlatformDependent {
private static final AtomicLong DIRECT_MEMORY_COUNTER; private static final AtomicLong DIRECT_MEMORY_COUNTER;
private static final long DIRECT_MEMORY_LIMIT; private static final long DIRECT_MEMORY_LIMIT;
private static final ThreadLocalRandomProvider RANDOM_PROVIDER; private static final ThreadLocalRandomProvider RANDOM_PROVIDER;
private static final Cleaner CLEANER;
public static final boolean BIG_ENDIAN_NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN; public static final boolean BIG_ENDIAN_NATIVE_ORDER = ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
private static final Cleaner NOOP = new Cleaner() {
@Override
public void freeDirectBuffer(ByteBuffer buffer) {
// NOOP
}
};
static { static {
if (javaVersion() >= 7) { if (javaVersion() >= 7) {
RANDOM_PROVIDER = new ThreadLocalRandomProvider() { RANDOM_PROVIDER = new ThreadLocalRandomProvider() {
@ -122,7 +127,7 @@ public final class PlatformDependent {
logger.debug("-Dio.netty.noPreferDirect: {}", !DIRECT_BUFFER_PREFERRED); logger.debug("-Dio.netty.noPreferDirect: {}", !DIRECT_BUFFER_PREFERRED);
} }
if (!hasUnsafe() && !isAndroid() && !IS_EXPLICIT_NO_UNSAFE) { if (!hasUnsafe() && !isAndroid()) {
logger.info( logger.info(
"Your platform does not provide complete low-level API for accessing direct buffers reliably. " + "Your platform does not provide complete low-level API for accessing direct buffers reliably. " +
"Unless explicitly requested, heap buffer will always be preferred to avoid potential system " + "Unless explicitly requested, heap buffer will always be preferred to avoid potential system " +
@ -158,6 +163,18 @@ public final class PlatformDependent {
logger.debug("io.netty.maxDirectMemory: {} bytes", maxDirectMemory); logger.debug("io.netty.maxDirectMemory: {} bytes", maxDirectMemory);
MAYBE_SUPER_USER = maybeSuperUser0(); MAYBE_SUPER_USER = maybeSuperUser0();
if (!isAndroid()) {
// only direct to method if we are not running on android.
// See https://github.com/netty/netty/issues/2604
if (javaVersion() >= 9) {
CLEANER = CleanerJava9.isSupported() ? new CleanerJava9() : NOOP;
} else {
CLEANER = CleanerJava6.isSupported() ? new CleanerJava6() : NOOP;
}
} else {
CLEANER = NOOP;
}
} }
/** /**
@ -322,15 +339,11 @@ public final class PlatformDependent {
} }
/** /**
* Try to deallocate the specified direct {@link ByteBuffer}. Please note this method does nothing if * Try to deallocate the specified direct {@link ByteBuffer}. Please note this method does nothing if
* the current platform does not support this operation or the specified buffer is not a direct buffer. * the current platform does not support this operation or the specified buffer is not a direct buffer.
*/ */
public static void freeDirectBuffer(ByteBuffer buffer) { public static void freeDirectBuffer(ByteBuffer buffer) {
if (hasUnsafe() && !isAndroid()) { CLEANER.freeDirectBuffer(buffer);
// only direct to method if we are not running on android.
// See https://github.com/netty/netty/issues/2604
PlatformDependent0.freeDirectBuffer(buffer);
}
} }
public static long directBufferAddress(ByteBuffer buffer) { public static long directBufferAddress(ByteBuffer buffer) {
@ -933,42 +946,13 @@ public final class PlatformDependent {
} }
} }
static boolean isExplicitNoUnsafe() {
return IS_EXPLICIT_NO_UNSAFE;
}
private static boolean explicitNoUnsafe0() {
final boolean noUnsafe = SystemPropertyUtil.getBoolean("io.netty.noUnsafe", false);
logger.debug("-Dio.netty.noUnsafe: {}", noUnsafe);
if (noUnsafe) {
logger.debug("sun.misc.Unsafe: unavailable (io.netty.noUnsafe)");
return true;
}
// Legacy properties
boolean tryUnsafe;
if (SystemPropertyUtil.contains("io.netty.tryUnsafe")) {
tryUnsafe = SystemPropertyUtil.getBoolean("io.netty.tryUnsafe", true);
} else {
tryUnsafe = SystemPropertyUtil.getBoolean("org.jboss.netty.tryUnsafe", true);
}
if (!tryUnsafe) {
logger.debug("sun.misc.Unsafe: unavailable (io.netty.tryUnsafe/org.jboss.netty.tryUnsafe)");
return true;
}
return false;
}
private static boolean hasUnsafe0() { private static boolean hasUnsafe0() {
if (isAndroid()) { if (isAndroid()) {
logger.debug("sun.misc.Unsafe: unavailable (Android)"); logger.debug("sun.misc.Unsafe: unavailable (Android)");
return false; return false;
} }
if (IS_EXPLICIT_NO_UNSAFE) { if (PlatformDependent0.isExplicitNoUnsafe()) {
return false; return false;
} }

View File

@ -36,10 +36,12 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull;
final class PlatformDependent0 { final class PlatformDependent0 {
private static final InternalLogger logger = InternalLoggerFactory.getInstance(PlatformDependent0.class); private static final InternalLogger logger = InternalLoggerFactory.getInstance(PlatformDependent0.class);
private static final Unsafe UNSAFE;
private static final long ADDRESS_FIELD_OFFSET; private static final long ADDRESS_FIELD_OFFSET;
private static final long BYTE_ARRAY_BASE_OFFSET; private static final long BYTE_ARRAY_BASE_OFFSET;
private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR; private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
private static final boolean IS_EXPLICIT_NO_UNSAFE = explicitNoUnsafe0();
static final Unsafe UNSAFE;
// constants borrowed from murmur3 // constants borrowed from murmur3
static final int HASH_CODE_ASCII_SEED = 0xc2b2ae35; static final int HASH_CODE_ASCII_SEED = 0xc2b2ae35;
@ -59,7 +61,7 @@ final class PlatformDependent0 {
Field addressField = null; Field addressField = null;
Unsafe unsafe; Unsafe unsafe;
if (PlatformDependent.isExplicitNoUnsafe()) { if (isExplicitNoUnsafe()) {
direct = null; direct = null;
addressField = null; addressField = null;
unsafe = null; unsafe = null;
@ -244,7 +246,7 @@ final class PlatformDependent0 {
public Object run() { public Object run() {
try { try {
Class<?> bitsClass = Class<?> bitsClass =
Class.forName("java.nio.Bits", false, PlatformDependent.getSystemClassLoader()); Class.forName("java.nio.Bits", false, getSystemClassLoader());
Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned"); Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned");
Throwable cause = ReflectionUtil.trySetAccessible(unalignedMethod); Throwable cause = ReflectionUtil.trySetAccessible(unalignedMethod);
if (cause != null) { if (cause != null) {
@ -281,10 +283,35 @@ final class PlatformDependent0 {
logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}", logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}",
DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable"); DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable");
}
if (direct != null) { static boolean isExplicitNoUnsafe() {
freeDirectBuffer(direct); return IS_EXPLICIT_NO_UNSAFE;
}
private static boolean explicitNoUnsafe0() {
final boolean noUnsafe = SystemPropertyUtil.getBoolean("io.netty.noUnsafe", false);
logger.debug("-Dio.netty.noUnsafe: {}", noUnsafe);
if (noUnsafe) {
logger.debug("sun.misc.Unsafe: unavailable (io.netty.noUnsafe)");
return true;
} }
// Legacy properties
boolean tryUnsafe;
if (SystemPropertyUtil.contains("io.netty.tryUnsafe")) {
tryUnsafe = SystemPropertyUtil.getBoolean("io.netty.tryUnsafe", true);
} else {
tryUnsafe = SystemPropertyUtil.getBoolean("org.jboss.netty.tryUnsafe", true);
}
if (!tryUnsafe) {
logger.debug("sun.misc.Unsafe: unavailable (io.netty.tryUnsafe/org.jboss.netty.tryUnsafe)");
return true;
}
return false;
} }
static boolean isUnaligned() { static boolean isUnaligned() {
@ -330,12 +357,6 @@ final class PlatformDependent0 {
} }
} }
static void freeDirectBuffer(ByteBuffer buffer) {
// Delegate to other class to not break on android
// See https://github.com/netty/netty/issues/2604
Cleaner0.freeDirectBuffer(buffer);
}
static long directBufferAddress(ByteBuffer buffer) { static long directBufferAddress(ByteBuffer buffer) {
return getLong(buffer, ADDRESS_FIELD_OFFSET); return getLong(buffer, ADDRESS_FIELD_OFFSET);
} }