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:
parent
4c77e7c55a
commit
7b6119a0a4
29
common/src/main/java/io/netty/util/internal/Cleaner.java
Normal file
29
common/src/main/java/io/netty/util/internal/Cleaner.java
Normal 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);
|
||||
}
|
@ -29,41 +29,32 @@ import java.nio.ByteBuffer;
|
||||
*
|
||||
* 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 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 {
|
||||
ByteBuffer direct = ByteBuffer.allocateDirect(1);
|
||||
Field cleanerField;
|
||||
long fieldOffset = -1;
|
||||
Method clean = null;
|
||||
boolean cleanerIsRunnable = false;
|
||||
Throwable error = null;
|
||||
if (PlatformDependent0.hasUnsafe()) {
|
||||
ByteBuffer direct = ByteBuffer.allocateDirect(1);
|
||||
try {
|
||||
cleanerField = direct.getClass().getDeclaredField("cleaner");
|
||||
Field cleanerField = direct.getClass().getDeclaredField("cleaner");
|
||||
fieldOffset = PlatformDependent0.objectFieldOffset(cleanerField);
|
||||
Object cleaner = PlatformDependent0.getObject(direct, fieldOffset);
|
||||
try {
|
||||
// Cleaner implements Runnable from JDK9 onwards.
|
||||
Runnable runnable = (Runnable) cleaner;
|
||||
runnable.run();
|
||||
cleanerIsRunnable = true;
|
||||
} catch (ClassCastException ignored) {
|
||||
clean = cleaner.getClass().getDeclaredMethod("clean");
|
||||
clean.invoke(cleaner);
|
||||
}
|
||||
clean = cleaner.getClass().getDeclaredMethod("clean");
|
||||
clean.invoke(cleaner);
|
||||
} catch (Throwable t) {
|
||||
// We don't have ByteBuffer.cleaner().
|
||||
fieldOffset = -1;
|
||||
clean = null;
|
||||
cleanerIsRunnable = false;
|
||||
error = t;
|
||||
}
|
||||
} else {
|
||||
error = new UnsupportedOperationException("sun.misc.Unsafe unavailable");
|
||||
}
|
||||
if (error == null) {
|
||||
logger.debug("java.nio.ByteBuffer.cleaner(): available");
|
||||
@ -72,31 +63,24 @@ final class Cleaner0 {
|
||||
}
|
||||
CLEANER_FIELD_OFFSET = fieldOffset;
|
||||
CLEAN_METHOD = clean;
|
||||
CLEANER_IS_RUNNABLE = cleanerIsRunnable;
|
||||
|
||||
// free buffer if possible
|
||||
freeDirectBuffer(direct);
|
||||
}
|
||||
|
||||
static void freeDirectBuffer(ByteBuffer buffer) {
|
||||
if (CLEANER_FIELD_OFFSET == -1 || !buffer.isDirect()) {
|
||||
static boolean isSupported() {
|
||||
return CLEANER_FIELD_OFFSET != -1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void freeDirectBuffer(ByteBuffer buffer) {
|
||||
if (!buffer.isDirect()) {
|
||||
return;
|
||||
}
|
||||
assert CLEAN_METHOD != null || CLEANER_IS_RUNNABLE:
|
||||
"CLEANER_FIELD_OFFSET != -1 implies CLEAN_METHOD != null or CLEANER_IS_RUNNABLE == true";
|
||||
try {
|
||||
Object cleaner = PlatformDependent0.getObject(buffer, CLEANER_FIELD_OFFSET);
|
||||
if (cleaner != null) {
|
||||
if (CLEANER_IS_RUNNABLE) {
|
||||
((Runnable) cleaner).run();
|
||||
} else {
|
||||
CLEAN_METHOD.invoke(cleaner);
|
||||
}
|
||||
CLEAN_METHOD.invoke(cleaner);
|
||||
}
|
||||
} catch (Throwable t) {
|
||||
// Nothing we can do here.
|
||||
} catch (Throwable cause) {
|
||||
PlatformDependent0.throwException(cause);
|
||||
}
|
||||
}
|
||||
|
||||
private Cleaner0() { }
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -67,9 +67,6 @@ public final class PlatformDependent {
|
||||
private static final Pattern MAX_DIRECT_MEMORY_SIZE_ARG_PATTERN = Pattern.compile(
|
||||
"\\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_WINDOWS = isWindows0();
|
||||
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 long DIRECT_MEMORY_LIMIT;
|
||||
private static final ThreadLocalRandomProvider RANDOM_PROVIDER;
|
||||
private static final Cleaner CLEANER;
|
||||
|
||||
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 {
|
||||
if (javaVersion() >= 7) {
|
||||
RANDOM_PROVIDER = new ThreadLocalRandomProvider() {
|
||||
@ -122,7 +127,7 @@ public final class PlatformDependent {
|
||||
logger.debug("-Dio.netty.noPreferDirect: {}", !DIRECT_BUFFER_PREFERRED);
|
||||
}
|
||||
|
||||
if (!hasUnsafe() && !isAndroid() && !IS_EXPLICIT_NO_UNSAFE) {
|
||||
if (!hasUnsafe() && !isAndroid()) {
|
||||
logger.info(
|
||||
"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 " +
|
||||
@ -158,6 +163,18 @@ public final class PlatformDependent {
|
||||
logger.debug("io.netty.maxDirectMemory: {} bytes", maxDirectMemory);
|
||||
|
||||
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.
|
||||
*/
|
||||
public static void freeDirectBuffer(ByteBuffer buffer) {
|
||||
if (hasUnsafe() && !isAndroid()) {
|
||||
// only direct to method if we are not running on android.
|
||||
// See https://github.com/netty/netty/issues/2604
|
||||
PlatformDependent0.freeDirectBuffer(buffer);
|
||||
}
|
||||
CLEANER.freeDirectBuffer(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() {
|
||||
if (isAndroid()) {
|
||||
logger.debug("sun.misc.Unsafe: unavailable (Android)");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (IS_EXPLICIT_NO_UNSAFE) {
|
||||
if (PlatformDependent0.isExplicitNoUnsafe()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
@ -36,10 +36,12 @@ import static io.netty.util.internal.ObjectUtil.checkNotNull;
|
||||
final class PlatformDependent0 {
|
||||
|
||||
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 BYTE_ARRAY_BASE_OFFSET;
|
||||
private static final Constructor<?> DIRECT_BUFFER_CONSTRUCTOR;
|
||||
private static final boolean IS_EXPLICIT_NO_UNSAFE = explicitNoUnsafe0();
|
||||
|
||||
static final Unsafe UNSAFE;
|
||||
|
||||
// constants borrowed from murmur3
|
||||
static final int HASH_CODE_ASCII_SEED = 0xc2b2ae35;
|
||||
@ -59,7 +61,7 @@ final class PlatformDependent0 {
|
||||
Field addressField = null;
|
||||
Unsafe unsafe;
|
||||
|
||||
if (PlatformDependent.isExplicitNoUnsafe()) {
|
||||
if (isExplicitNoUnsafe()) {
|
||||
direct = null;
|
||||
addressField = null;
|
||||
unsafe = null;
|
||||
@ -244,7 +246,7 @@ final class PlatformDependent0 {
|
||||
public Object run() {
|
||||
try {
|
||||
Class<?> bitsClass =
|
||||
Class.forName("java.nio.Bits", false, PlatformDependent.getSystemClassLoader());
|
||||
Class.forName("java.nio.Bits", false, getSystemClassLoader());
|
||||
Method unalignedMethod = bitsClass.getDeclaredMethod("unaligned");
|
||||
Throwable cause = ReflectionUtil.trySetAccessible(unalignedMethod);
|
||||
if (cause != null) {
|
||||
@ -281,10 +283,35 @@ final class PlatformDependent0 {
|
||||
|
||||
logger.debug("java.nio.DirectByteBuffer.<init>(long, int): {}",
|
||||
DIRECT_BUFFER_CONSTRUCTOR != null ? "available" : "unavailable");
|
||||
}
|
||||
|
||||
if (direct != null) {
|
||||
freeDirectBuffer(direct);
|
||||
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;
|
||||
}
|
||||
|
||||
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) {
|
||||
return getLong(buffer, ADDRESS_FIELD_OFFSET);
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user