Make hasUnsafe() return true only when all necessary low level operations are available for reliable direct buffer access

This commit is contained in:
Trustin Lee 2013-03-05 15:25:25 +09:00
parent 256c931db2
commit 307e6c47d8
4 changed files with 85 additions and 105 deletions

View File

@ -354,7 +354,7 @@ abstract class PoolArena<T> {
static final class DirectArena extends PoolArena<ByteBuffer> { static final class DirectArena extends PoolArena<ByteBuffer> {
private static final boolean USE_UNSAFE_DIRECTBUF = PlatformDependent.unsafeHasCopyMethods(); private static final boolean HAS_UNSAFE = PlatformDependent.hasUnsafe();
DirectArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) { DirectArena(PooledByteBufAllocator parent, int pageSize, int maxOrder, int pageShifts, int chunkSize) {
super(parent, pageSize, maxOrder, pageShifts, chunkSize); super(parent, pageSize, maxOrder, pageShifts, chunkSize);
@ -378,7 +378,7 @@ abstract class PoolArena<T> {
@Override @Override
protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) { protected PooledByteBuf<ByteBuffer> newByteBuf(int maxCapacity) {
if (USE_UNSAFE_DIRECTBUF) { if (HAS_UNSAFE) {
return new PooledUnsafeDirectByteBuf(maxCapacity); return new PooledUnsafeDirectByteBuf(maxCapacity);
} else { } else {
return new PooledDirectByteBuf(maxCapacity); return new PooledDirectByteBuf(maxCapacity);
@ -391,7 +391,7 @@ abstract class PoolArena<T> {
return; return;
} }
if (USE_UNSAFE_DIRECTBUF) { if (HAS_UNSAFE) {
PlatformDependent.copyMemory( PlatformDependent.copyMemory(
PlatformDependent.directBufferAddress(src) + srcOffset, PlatformDependent.directBufferAddress(src) + srcOffset,
PlatformDependent.directBufferAddress(dst) + dstOffset, length); PlatformDependent.directBufferAddress(dst) + dstOffset, length);

View File

@ -41,10 +41,9 @@ public final class UnpooledByteBufAllocator extends AbstractByteBufAllocator {
@Override @Override
public ByteBuf ioBuffer() { public ByteBuf ioBuffer() {
if (PlatformDependent.canFreeDirectBuffer()) { if (PlatformDependent.hasUnsafe()) {
return directBuffer(0); return directBuffer(0);
} }
return heapBuffer(0); return heapBuffer(0);
} }
} }

View File

@ -39,8 +39,6 @@ import java.util.regex.Pattern;
*/ */
public final class PlatformDependent { public final class PlatformDependent {
private static final ClassLoader LOADER = ClassLoader.getSystemClassLoader();
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 IS_ROOT = isRoot0(); private static final boolean IS_ROOT = isRoot0();
@ -51,8 +49,6 @@ public final class PlatformDependent {
private static final boolean HAS_UNSAFE = hasUnsafe0(); private static final boolean HAS_UNSAFE = hasUnsafe0();
private static final boolean CAN_USE_CHM_V8 = HAS_UNSAFE && JAVA_VERSION < 8; private static final boolean CAN_USE_CHM_V8 = HAS_UNSAFE && JAVA_VERSION < 8;
private static final boolean CAN_FREE_DIRECT_BUFFER = canFreeDirectBuffer0();
private static final boolean UNSAFE_HASE_COPY_METHODS = unsafeHasCopyMethods0();
private static final long ARRAY_BASE_OFFSET = arrayBaseOffset0(); private static final long ARRAY_BASE_OFFSET = arrayBaseOffset0();
private static final boolean HAS_JAVASSIST = hasJavassist0(); private static final boolean HAS_JAVASSIST = hasJavassist0();
@ -94,27 +90,13 @@ public final class PlatformDependent {
} }
/** /**
* Return {@code true} if {@code sun.misc.Unsafe} was found on the classpath and can be used. * Return {@code true} if {@code sun.misc.Unsafe} was found on the classpath and can be used for acclerated
* direct memory access.
*/ */
public static boolean hasUnsafe() { public static boolean hasUnsafe() {
return HAS_UNSAFE; return HAS_UNSAFE;
} }
/**
* Return {@code true} if direct buffers can be freed using an optimized way and so memory footprint will be very
* small.
*/
public static boolean canFreeDirectBuffer() {
return CAN_FREE_DIRECT_BUFFER;
}
/**
* Returns {@code true} if unsafe has all needed copy methods which is not the case on latest openjdk6 atm.
*/
public static boolean unsafeHasCopyMethods() {
return UNSAFE_HASE_COPY_METHODS;
}
/** /**
* Returns {@code true} if and only if Javassist is available. * Returns {@code true} if and only if Javassist is available.
*/ */
@ -178,8 +160,11 @@ public final class PlatformDependent {
} }
} }
public static long directBufferAddress(ByteBuffer buffer) { /**
return PlatformDependent0.directBufferAddress(buffer); * Try to allocate a new direct {@link ByteBuffer} using the best available allocator.
*/
public static ByteBuffer newDirectBuffer(int capacity) {
return PlatformDependent0.newDirectBuffer(capacity);
} }
/** /**
@ -187,11 +172,15 @@ public final class PlatformDependent {
* 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 (canFreeDirectBuffer() && buffer.isDirect()) { if (buffer.isDirect()) {
PlatformDependent0.freeDirectBuffer(buffer); PlatformDependent0.freeDirectBuffer(buffer);
} }
} }
public static long directBufferAddress(ByteBuffer buffer) {
return PlatformDependent0.directBufferAddress(buffer);
}
public static Object getObject(Object object, long fieldOffset) { public static Object getObject(Object object, long fieldOffset) {
return PlatformDependent0.getObject(object, fieldOffset); return PlatformDependent0.getObject(object, fieldOffset);
} }
@ -247,7 +236,7 @@ public final class PlatformDependent {
private static boolean isAndroid0() { private static boolean isAndroid0() {
boolean android; boolean android;
try { try {
Class.forName("android.app.Application", false, LOADER); Class.forName("android.app.Application", false, ClassLoader.getSystemClassLoader());
android = true; android = true;
} catch (Exception e) { } catch (Exception e) {
android = false; android = false;
@ -341,24 +330,13 @@ public final class PlatformDependent {
return false; return false;
} }
return PlatformDependent0.hasUnsafe(); try {
} return PlatformDependent0.hasUnsafe();
} catch (Throwable t) {
private static boolean canFreeDirectBuffer0() {
if (isAndroid()) {
return false; return false;
} }
return PlatformDependent0.canFreeDirectBuffer();
} }
private static boolean unsafeHasCopyMethods0() {
if (!hasUnsafe()) {
return false;
}
return PlatformDependent0.hasCopyMethods();
}
private static long arrayBaseOffset0() { private static long arrayBaseOffset0() {
if (!hasUnsafe()) { if (!hasUnsafe()) {
return -1; return -1;

View File

@ -31,21 +31,62 @@ final class PlatformDependent0 {
private static final Unsafe UNSAFE; private static final Unsafe UNSAFE;
private static final long CLEANER_FIELD_OFFSET; private static final long CLEANER_FIELD_OFFSET;
private static final long ADDRESS_FIELD_OFFSET; private static final long ADDRESS_FIELD_OFFSET;
/** /**
* {@code true} if and only if the platform supports unaligned access. * {@code true} if and only if the platform supports unaligned access.
* *
* @see <a href="http://en.wikipedia.org/wiki/Segmentation_fault#Bus_error">Wikipedia on segfault</a> * @see <a href="http://en.wikipedia.org/wiki/Segmentation_fault#Bus_error">Wikipedia on segfault</a>
*/ */
private static final boolean UNALIGNED; private static final boolean UNALIGNED;
private static final boolean HAS_COPY_METHODS;
static { static {
Unsafe unsafe; ByteBuffer direct = ByteBuffer.allocateDirect(1);
Field cleanerField;
try { try {
Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe"); cleanerField = direct.getClass().getDeclaredField("cleaner");
singleoneInstanceField.setAccessible(true); cleanerField.setAccessible(true);
unsafe = (Unsafe) singleoneInstanceField.get(null); Cleaner cleaner = (Cleaner) cleanerField.get(direct);
} catch (Throwable cause) { cleaner.clean();
} catch (Throwable t) {
cleanerField = null;
}
Field addressField;
try {
addressField = Buffer.class.getDeclaredField("address");
addressField.setAccessible(true);
if (addressField.getLong(ByteBuffer.allocate(1)) != 0) {
addressField = null;
} else {
direct = ByteBuffer.allocateDirect(1);
if (addressField.getLong(direct) == 0) {
addressField = null;
}
Cleaner cleaner = (Cleaner) cleanerField.get(direct);
cleaner.clean();
}
} catch (Throwable t) {
addressField = null;
}
Unsafe unsafe;
if (addressField != null && cleanerField != null) {
try {
Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe");
unsafeField.setAccessible(true);
unsafe = (Unsafe) unsafeField.get(null);
// Ensure the unsafe supports all necessary methods to work around the mistake in the latest OpenJDK.
// https://github.com/netty/netty/issues/1061
// http://www.mail-archive.com/jdk6-dev@openjdk.java.net/msg00698.html
unsafe.getClass().getDeclaredMethod(
"copyMemory", new Class[] { Object.class, long.class, Object.class, long.class, long.class });
} catch (Throwable cause) {
unsafe = null;
}
} else {
// If we cannot access the address of a direct buffer, there's no point of using unsafe.
// Let's just pretend unsafe is unavailable for overall simplicity.
unsafe = null; unsafe = null;
} }
UNSAFE = unsafe; UNSAFE = unsafe;
@ -54,19 +95,9 @@ final class PlatformDependent0 {
CLEANER_FIELD_OFFSET = -1; CLEANER_FIELD_OFFSET = -1;
ADDRESS_FIELD_OFFSET = -1; ADDRESS_FIELD_OFFSET = -1;
UNALIGNED = false; UNALIGNED = false;
HAS_COPY_METHODS = false;
} else { } else {
ByteBuffer direct = ByteBuffer.allocateDirect(1); ADDRESS_FIELD_OFFSET = objectFieldOffset(addressField);
Field cleanerField; CLEANER_FIELD_OFFSET = objectFieldOffset(cleanerField);
try {
cleanerField = direct.getClass().getDeclaredField("cleaner");
cleanerField.setAccessible(true);
Cleaner cleaner = (Cleaner) cleanerField.get(direct);
cleaner.clean();
} catch (Throwable t) {
cleanerField = null;
}
CLEANER_FIELD_OFFSET = cleanerField != null? objectFieldOffset(cleanerField) : -1;
boolean unaligned; boolean unaligned;
try { try {
@ -75,44 +106,12 @@ final class PlatformDependent0 {
unalignedMethod.setAccessible(true); unalignedMethod.setAccessible(true);
unaligned = Boolean.TRUE.equals(unalignedMethod.invoke(null)); unaligned = Boolean.TRUE.equals(unalignedMethod.invoke(null));
} catch (Throwable t) { } catch (Throwable t) {
unaligned = false; // We at least know x86 and x64 support unaligned access.
String arch = SystemPropertyUtil.get("os.arch", "");
//noinspection DynamicRegexReplaceableByCompiledPattern
unaligned = arch.matches("^(i[3-6]86|x86(_64)?|x64|amd64)$");
} }
if (unaligned) {
Field addressField = null;
try {
addressField = Buffer.class.getDeclaredField("address");
addressField.setAccessible(true);
if (addressField.getLong(ByteBuffer.allocate(1)) != 0) {
unaligned = false;
} else {
ByteBuffer directBuf = ByteBuffer.allocateDirect(1);
if (addressField.getLong(directBuf) == 0) {
unaligned = false;
}
freeDirectBuffer(directBuf);
}
} catch (Throwable t) {
unaligned = false;
}
ADDRESS_FIELD_OFFSET = addressField != null? objectFieldOffset(addressField) : -1;
} else {
ADDRESS_FIELD_OFFSET = -1;
}
UNALIGNED = unaligned; UNALIGNED = unaligned;
boolean hasCopyMethods;
try {
// Unsafe does not shop all copy methods in latest openjdk update..
// https://github.com/netty/netty/issues/1061
// http://www.mail-archive.com/jdk6-dev@openjdk.java.net/msg00698.html
UNSAFE.getClass().getDeclaredMethod("copyMemory",
new Class[] { Object.class, long.class, Object.class, long.class, long.class });
hasCopyMethods = true;
} catch (Throwable ignore) {
hasCopyMethods = false;
}
HAS_COPY_METHODS = hasCopyMethods;
} }
} }
@ -120,26 +119,30 @@ final class PlatformDependent0 {
return UNSAFE != null; return UNSAFE != null;
} }
static boolean canFreeDirectBuffer() { static ByteBuffer newDirectBuffer(int capacity) {
return ADDRESS_FIELD_OFFSET >= 0; if (hasUnsafe()) {
} return ByteBuffer.allocateDirect(capacity);
} else {
static long directBufferAddress(ByteBuffer buffer) { throw new Error("refusing to create a direct buffer without proper access to the low-level API");
return getLong(buffer, ADDRESS_FIELD_OFFSET); }
} }
static void freeDirectBuffer(ByteBuffer buffer) { static void freeDirectBuffer(ByteBuffer buffer) {
Cleaner cleaner; Cleaner cleaner;
try { try {
cleaner = (Cleaner) getObject(buffer, CLEANER_FIELD_OFFSET); cleaner = (Cleaner) getObject(buffer, CLEANER_FIELD_OFFSET);
if (cleaner == null) {
throw new IllegalArgumentException(
"attempted to deallocate the buffer which was allocated via JNIEnv->NewDirectByteBuffer()");
}
cleaner.clean(); cleaner.clean();
} catch (Throwable t) { } catch (Throwable t) {
// Nothing we can do here. // Nothing we can do here.
} }
} }
static boolean hasCopyMethods() { static long directBufferAddress(ByteBuffer buffer) {
return HAS_COPY_METHODS; return getLong(buffer, ADDRESS_FIELD_OFFSET);
} }
static long arrayBaseOffset() { static long arrayBaseOffset() {