All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.zodiac.sdk.nio.common.Unsafe Maven / Gradle / Ivy

There is a newer version: 1.6.8
Show newest version
package org.zodiac.sdk.nio.common;

import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.nio.Buffer;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedExceptionAction;

public class Unsafe {

    public static final String ENABLE_UNSAFE = "platform.io.enable_unsafe";
    public static final String ENABLE_UNSAFE_BUF = "platform.ssl.enable_unsafe_buf";

    /**
     * Size of int in bytes
     */
    public static final int SIZEOF_INT = Integer.SIZE / Byte.SIZE;
    /**
     * Size of long in bytes
     */
    public static final int SIZEOF_LONG = Long.SIZE / Byte.SIZE;
    /**
     * Size of short in bytes
     */
    public static final int SIZEOF_SHORT = Short.SIZE / Byte.SIZE;

    /** The offset to the first element in a byte array. */
    public static final long BYTE_ARRAY_BASE_OFFSET;

    public static final int BUF_UNSAFE = 0;
    public static final int BUF_DIRECT = 1;
    public static final int BUF_HEAP = 2;
    public static final long ARRAY_BASE_OFFSET;
    public static final long BUFFER_ADDRESS_OFFSET;
    public static final boolean IS_LINUX = isLinux();
    public static final boolean IS_ANDROID = isAndroid();
    public static final boolean UNSAFE_AVAILABLE = isUnsafeAvailable();
    public static final boolean UNSAFE_BUF_AVAILABLE;
    public static final boolean DIRECT_BUFFER_AVAILABLE;
    public static final boolean LITTLE_ENDIAN = ByteOrder.nativeOrder().equals(ByteOrder.LITTLE_ENDIAN);
    // This number limits the number of bytes to copy per call to Unsafe's
    // copyMemory method. A writeIndex is imposed to allow for safe point polling
    // during a large copy
    private static final long UNSAFE_COPY_THRESHOLD = 1024L * 1024L;
    private static final boolean BIG_ORDER = checkBigOrder();
    private static final boolean RAW_DIRECT_AVAILABLE;
    private static final int JAVA_VERSION = majorJavaVersion();
    private static final Constructor DIRECT_BUFFER_CONSTRUCTOR;
    private static final int CAS_UPDATE_YIELD_TIME = 16;
    private static final sun.misc.Unsafe JDK_UNSAFE;

    // jdk.internal.misc.Unsafe
    // jdk.internal.ref.Cleaner

    static {
        if (UNSAFE_AVAILABLE) {
            JDK_UNSAFE = getJdkUnsafe();
            BYTE_ARRAY_BASE_OFFSET = JDK_UNSAFE.arrayBaseOffset(byte[].class);
            UNSAFE_BUF_AVAILABLE = isEnableUnsafeBuf();
            BUFFER_ADDRESS_OFFSET = fieldOffset(getDeclaredField(Buffer.class, "address"));
            DIRECT_BUFFER_AVAILABLE = BUFFER_ADDRESS_OFFSET != -1;
            ARRAY_BASE_OFFSET = JDK_UNSAFE.arrayBaseOffset(byte[].class);
            DIRECT_BUFFER_CONSTRUCTOR = getDirectBufferConstructor();
            RAW_DIRECT_AVAILABLE = DIRECT_BUFFER_CONSTRUCTOR != null;
        } else {
            if (isEnableUnsafeBuf()) {
                throw new Error("UnsafeBuf enabled but no unsafe available");
            }
            JDK_UNSAFE = null;
            BYTE_ARRAY_BASE_OFFSET = -1;
            UNSAFE_BUF_AVAILABLE = false;
            BUFFER_ADDRESS_OFFSET = -1;
            DIRECT_BUFFER_AVAILABLE = false;
            ARRAY_BASE_OFFSET = -1;
            DIRECT_BUFFER_CONSTRUCTOR = null;
            RAW_DIRECT_AVAILABLE = false;
        }
    }

    private Unsafe() {}

    private static Constructor getDirectBufferConstructor() {
        Constructor directBufferConstructor = null;
        final ByteBuffer direct = ByteBuffer.allocateDirect(1);
        long address = -1;
        try {
            final Object res = AccessController.doPrivileged(new PrivilegedAction() {
                @Override
                public Object run() {
                    try {
                        final Constructor constructor =
                            direct.getClass().getDeclaredConstructor(long.class, int.class);
                        Throwable cause = trySetAccessible(constructor);
                        if (cause != null) {
                            return cause;
                        }
                        return constructor;
                    } catch (Throwable e) {
                        return e;
                    }
                }
            });
            if (res instanceof Constructor) {
                address = allocate(1);
                try {
                    ((Constructor)res).newInstance(address, 1);
                    directBufferConstructor = (Constructor)res;
                } catch (Throwable e) {
                    return null;
                }
            } else {
                return null;
            }
        } finally {
            if (address != -1) {
                free(address);
            }
        }
        return directBufferConstructor;
    }

    private static void checkUnsafeAvailable() {
        if (!UNSAFE_AVAILABLE) {
            throw new RuntimeException("Unsafe not available");
        }
    }

    private static boolean isAndroid() {
        return "Dalvik".equalsIgnoreCase(System.getProperty("java.vm.name"));
    }

    private static boolean isLinux() {
        return System.getProperty("os.name", "").toLowerCase().startsWith("lin");
    }

    private static boolean isUnsafeAvailable() {
        return !IS_ANDROID && isEnableUnsafe();
    }

    public static int addAndGetInt(Object object, int offset, int add) {
        int expect = getIntVolatile(object, offset);
        int update = expect + add;
        if (compareAndSwapInt(object, offset, expect, update)) {
            return update;
        }
        for (int i = 0; i < CAS_UPDATE_YIELD_TIME; i++) {
            expect = getIntVolatile(object, offset);
            update = expect + add;
            if (compareAndSwapInt(object, offset, expect, update)) {
                return update;
            }
        }
        for (;;) {
            expect = getIntVolatile(object, offset);
            update = expect + add;
            if (compareAndSwapInt(object, offset, expect, update)) {
                return update;
            } else {
                Thread.yield();
            }
        }
    }

    public static long addAndGetLong(Object object, long offset, long add) {
        long expect = getLongVolatile(object, offset);
        long update = expect + add;
        if (compareAndSwapLong(object, offset, expect, update)) {
            return update;
        }
        for (int i = 0; i < CAS_UPDATE_YIELD_TIME; i++) {
            expect = getLongVolatile(object, offset);
            update = expect + add;
            if (compareAndSwapLong(object, offset, expect, update)) {
                return update;
            }
        }
        for (;;) {
            expect = getLongVolatile(object, offset);
            update = expect + add;
            if (compareAndSwapLong(object, offset, expect, update)) {
                return update;
            } else {
                Thread.yield();
            }
        }
    }

    public static int getMemoryTypeId() {
        if (UNSAFE_BUF_AVAILABLE) {
            return BUF_UNSAFE;
        } else if (DIRECT_BUFFER_AVAILABLE) {
            return BUF_DIRECT;
        } else {
            return BUF_HEAP;
        }
    }

    public static String getMemoryType() {
        if (UNSAFE_BUF_AVAILABLE) {
            return "unsafe";
        } else if (DIRECT_BUFFER_AVAILABLE) {
            return "direct";
        } else {
            return "heap";
        }
    }

    public static long address(ByteBuffer buffer) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getLong(buffer, BUFFER_ADDRESS_OFFSET);
    }

    public static long allocate(long length) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.allocateMemory(length);
    }

    public static Object allocateInstance(Class clazz) {
        checkUnsafeAvailable();
        try {
            return JDK_UNSAFE.allocateInstance(clazz);
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        }
    }

    public static boolean compareAndSwapInt(Object o, long offset, int expect, int val) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.compareAndSwapInt(o, offset, expect, val);
    }

    public static boolean compareAndSwapLong(Object o, long offset, long expect, long val) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.compareAndSwapLong(o, offset, expect, val);
    }

    public static boolean compareAndSwapObject(Object o, long offset, Object expect, Object val) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.compareAndSwapObject(o, offset, expect, val);
    }

    public static void putOrderedInt(Object o, long offset, int expect) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putOrderedInt(o, offset, expect);
    }

    public static long objectFieldOffset(Field f) {
        return JDK_UNSAFE.objectFieldOffset(f);
    }

    public static void throwException(Throwable t) {
        JDK_UNSAFE.throwException(t);
    }

    public static void copyFromArray(byte[] src, long srcPos, long dstAddr, long length) {
        checkUnsafeAvailable();
        long offset = ARRAY_BASE_OFFSET + srcPos;
        // JDK_UNSAFE.copyMemory(src, offset, null, dstAddr, length);
        unsafeCopy(src, offset, null, dstAddr, length);
    }

    public static void copyMemory(long srcAddress, long targetAddress, long length) {
        copyMemory(null, srcAddress, null, targetAddress, length);
    }

    public static void copyMemory(Object src, long srcOffset, Object target, long targetOffset, long length) {
        checkUnsafeAvailable();
        // see java.nio.Bits.copyToArray
        if (length < UNSAFE_COPY_THRESHOLD) {
            // JDK_UNSAFE.copyMemory(src, srcOffset, target, targetOffset, length);
            unsafeCopy(src, srcOffset, target, targetOffset, length);
        } else {
            for (; length > 0;) {
                long size = Math.min(length, UNSAFE_COPY_THRESHOLD);
                // JDK_UNSAFE.copyMemory(src, srcOffset, target, targetOffset, size);
                unsafeCopy(src, srcOffset, target, targetOffset, size);
                length -= size;
                srcOffset += size;
                targetOffset += size;
            }
        }
    }

    public static void copyMemory(byte[] src, int srcOffset, ByteBuffer dest, int destOffset, int length) {
        long destAddress = destOffset;
        Object destBase = null;
        if (dest.isDirect()) {
            destAddress = destAddress + ((sun.nio.ch.DirectBuffer)dest).address();
        } else {
            destAddress = destAddress + BYTE_ARRAY_BASE_OFFSET + dest.arrayOffset();
            destBase = dest.array();
        }
        long srcAddress = srcOffset + BYTE_ARRAY_BASE_OFFSET;
        unsafeCopy(src, srcAddress, destBase, destAddress, length);
    }

    public static void copyToArray(long srcAddr, byte[] dst, long dstPos, long length) {
        checkUnsafeAvailable();
        long dstOffset = ARRAY_BASE_OFFSET + dstPos;
        // JDK_UNSAFE.copyMemory(null, srcAddr, dst, dstOffset, length);
        unsafeCopy(null, srcAddr, dst, dstOffset, length);
    }

    public static void free(long address) {
        checkUnsafeAvailable();
        JDK_UNSAFE.freeMemory(address);
    }

    public static long getArrayBaseOffset() {
        return ARRAY_BASE_OFFSET;
    }

    public static boolean getBoolean(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getBoolean(target, offset);
    }

    public static boolean getBooleanVolatile(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getBooleanVolatile(target, offset);
    }

    public static byte getByte(long address) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getByte(address);
    }

    public static byte getByte(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getByte(target, offset);
    }

    public static double getDouble(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getDouble(target, offset);
    }

    public static float getFloat(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getFloat(target, offset);
    }

    public static int getInt(long address) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getInt(address);
    }

    public static int getInt(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getInt(target, offset);
    }

    public static int getIntVolatile(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getIntVolatile(target, offset);
    }

    public static long getLong(long address) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getLong(address);
    }

    public static long getLong(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getLong(target, offset);
    }

    public static long getLongVolatile(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getLongVolatile(target, offset);
    }

    public static Object getObject(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getObject(target, offset);
    }

    public static Object getObjectVolatile(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getObjectVolatile(target, offset);
    }

    public static short getShort(long address) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getShort(address);
    }

    public static short getShort(Object target, long offset) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getShort(target, offset);
    }

    private static boolean checkBigOrder() {
        return ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN;
    }

    public static boolean isBigOrder() {
        return BIG_ORDER;
    }

    public static boolean isLittleOrder() {
        return !BIG_ORDER;
    }

    public static long fieldOffset(Field field) {
        return field == null || JDK_UNSAFE == null ? -1 : JDK_UNSAFE.objectFieldOffset(field);
    }

    public static long fieldOffset(Class clazz, String fieldName) {
        Field field = getDeclaredField(clazz, fieldName);
        if (field == null) {
            throw new RuntimeException("no such field: " + fieldName);
        }
        return fieldOffset(field);
    }

    public static void putBoolean(Object target, long offset, boolean value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putBoolean(target, offset, value);
    }

    public static void putBooleanVolatile(Object target, long offset, boolean value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putBooleanVolatile(target, offset, value);
    }

    public static void putByte(long address, byte value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putByte(address, value);
    }

    public static void putByte(Object target, long offset, byte value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putByte(target, offset, value);
    }

    public static void putDouble(Object target, long offset, double value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putDouble(target, offset, value);
    }

    public static void putFloat(Object target, long offset, float value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putFloat(target, offset, value);
    }

    public static void putInt(long address, int value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putInt(address, value);
    }

    public static void putInt(Object target, long offset, int value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putInt(target, offset, value);
    }

    public static int putInt(ByteBuffer buf, int offset, int val) {
        if (buf.isDirect()) {
            JDK_UNSAFE.putInt(((sun.nio.ch.DirectBuffer)buf).address() + offset, val);
        } else {
            JDK_UNSAFE.putInt(buf.array(), offset + buf.arrayOffset() + BYTE_ARRAY_BASE_OFFSET, val);
        }
        return offset + SIZEOF_INT;
    }

    public static void putIntVolatile(Object target, long offset, int value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putIntVolatile(target, offset, value);
    }

    public static void putLong(long address, long value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putLong(address, value);
    }

    public static void putLong(Object target, long offset, long value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putLong(target, offset, value);
    }

    public static int putLong(ByteBuffer buf, int offset, long val) {
        checkUnsafeAvailable();
        if (buf.isDirect()) {
            JDK_UNSAFE.putLong(((sun.nio.ch.DirectBuffer)buf).address() + offset, val);
        } else {
            JDK_UNSAFE.putLong(buf.array(), BYTE_ARRAY_BASE_OFFSET + buf.arrayOffset() + offset, val);
        }
        return offset + SIZEOF_LONG;
    }

    public static void putLongVolatile(Object target, long offset, long value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putLongVolatile(target, offset, value);
    }

    public static void putObject(Object target, long offset, Object value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putObject(target, offset, value);
    }

    public static void putObjectVolatile(Object target, long offset, Object value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putObjectVolatile(target, offset, value);
    }

    public static void putShort(long address, short value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putShort(address, value);
    }

    public static void putShort(Object target, long offset, short value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putShort(target, offset, value);
    }

    public static int putShort(ByteBuffer buf, int offset, short val) {
        if (buf.isDirect()) {
            JDK_UNSAFE.putShort(((sun.nio.ch.DirectBuffer)buf).address() + offset, val);
        } else {
            JDK_UNSAFE.putShort(buf.array(), BYTE_ARRAY_BASE_OFFSET + buf.arrayOffset() + offset, val);
        }
        return offset + SIZEOF_SHORT;
    }

    public static short toShort(ByteBuffer buf, int offset) {
        if (LITTLE_ENDIAN) {
            return Short.reverseBytes(getAsShort(buf, offset));
        }
        return getAsShort(buf, offset);
    }

    public static int toInt(ByteBuffer buf, int offset) {
        if (LITTLE_ENDIAN) {
            return Integer.reverseBytes(getAsInt(buf, offset));
        }
        return getAsInt(buf, offset);
    }

    public static long toLong(ByteBuffer buf, int offset) {
        if (LITTLE_ENDIAN) {
            return Long.reverseBytes(getAsLong(buf, offset));
        }
        return getAsLong(buf, offset);
    }

    public static void setMemory(long address, long numBytes, byte value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.setMemory(address, numBytes, value);
    }

    public static int javaVersion() {
        return JAVA_VERSION;
    }

    public static ByteBuffer allocateDirectByteBuffer(int cap) {
        if (RAW_DIRECT_AVAILABLE) {
            long address = allocate(cap);
            if (address == -1) {
                throw new RuntimeException("no enough space(direct): " + cap);
            }
            try {
                return (ByteBuffer)DIRECT_BUFFER_CONSTRUCTOR.newInstance(address, cap);
            } catch (Throwable e) {
                free(address);
                throw new Error(e);
            }
        } else {
            return ByteBuffer.allocateDirect(cap);
        }
    }

    public static void freeByteBuffer(ByteBuffer buffer) {
        if (buffer.isDirect()) {
            if (((sun.nio.ch.DirectBuffer)buffer).cleaner() != null) {
                ((sun.nio.ch.DirectBuffer)buffer).cleaner().clean();
            } else {
                free(address(buffer));
            }
        }
    }

    static short getAsShort(ByteBuffer buf, int offset) {
        if (buf.isDirect()) {
            return JDK_UNSAFE.getShort(((sun.nio.ch.DirectBuffer)buf).address() + offset);
        }
        return JDK_UNSAFE.getShort(buf.array(), BYTE_ARRAY_BASE_OFFSET + buf.arrayOffset() + offset);
    }

    static int getAsInt(ByteBuffer buf, int offset) {
        if (buf.isDirect()) {
            return JDK_UNSAFE.getInt(((sun.nio.ch.DirectBuffer)buf).address() + offset);
        }
        return JDK_UNSAFE.getInt(buf.array(), BYTE_ARRAY_BASE_OFFSET + buf.arrayOffset() + offset);
    }

    static long getAsLong(ByteBuffer buf, int offset) {
        if (buf.isDirect()) {
            return JDK_UNSAFE.getLong(((sun.nio.ch.DirectBuffer)buf).address() + offset);
        }
        return JDK_UNSAFE.getLong(buf.array(), BYTE_ARRAY_BASE_OFFSET + buf.arrayOffset() + offset);
    }

    private static int majorJavaVersion() {
        final String javaSpecVersion = System.getProperty("java.specification.version", "8");
        final String[] components = javaSpecVersion.split("\\.");
        final int[] version = new int[components.length];
        for (int i = 0; i < components.length; i++) {
            version[i] = Integer.parseInt(components[i]);
        }
        if (version[0] == 1) {
            assert version[1] >= 6;
            return version[1];
        } else {
            return version[0];
        }
    }

    public static int arrayBaseOffset(Class clazz) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.arrayBaseOffset(clazz);
    }

    public static int arrayIndexScale(Class clazz) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.arrayIndexScale(clazz);
    }

    private static sun.misc.Unsafe getJdkUnsafe() {
        try {
            return AccessController.doPrivileged(new PrivilegedExceptionAction() {
                @Override
                public sun.misc.Unsafe run() throws Exception {
                    Class k = sun.misc.Unsafe.class;
                    for (Field f : k.getDeclaredFields()) {
                        f.setAccessible(true);
                        Object x = f.get(null);
                        if (k.isInstance(x)) {
                            return k.cast(x);
                        }
                    }
                    // The sun.misc.Unsafe field does not exist.
                    throw new Error("unsafe is null");
                }
            });
        } catch (Throwable e) {
            throw new Error("get unsafe failed", e);
        }
    }

    public static void putOrderedObject(Object target, long offset, Object value) {
        checkUnsafeAvailable();
        JDK_UNSAFE.putOrderedObject(target, offset, value);
    }

    public static Object getAndSetObject(Object target, long offset, Object value) {
        checkUnsafeAvailable();
        return JDK_UNSAFE.getAndSetObject(target, offset, value);
    }

    public static boolean isEnableUnsafe() {
        return isTrue(ENABLE_UNSAFE, true);
    }

    public static boolean isEnableUnsafeBuf() {
        return isTrue(ENABLE_UNSAFE_BUF);
    }

    public static Throwable trySetAccessible(AccessibleObject object) {
        try {
            object.setAccessible(true);
            return null;
        } catch (Throwable e) {
            return e;
        }
    }

    public static Field getDeclaredField(Class clazz, String name) {
        if (clazz == null) {
            return null;
        }
        Field[] fs = clazz.getDeclaredFields();
        for (Field f : fs) {
            if (f.getName().equals(name)) {
                return f;
            }
        }
        return null;
    }

    private static boolean isTrue(String key) {
        return isTrue(key, false);
    }

    private static boolean isTrue(String key, boolean def) {
        String v = System.getProperty(key);
        if (null == v || v.isEmpty()) {
            return def;
        }
        return "1".equals(v) || "true".equals(v);
    }

    private static void unsafeCopy(Object src, long srcAddr, Object dst, long destAddr, long len) {
        while (len > 0) {
            long size = (len > UNSAFE_COPY_THRESHOLD) ? UNSAFE_COPY_THRESHOLD : len;
            JDK_UNSAFE.copyMemory(src, srcAddr, dst, destAddr, size);
            len -= size;
            srcAddr += size;
            destAddr += size;
        }
    }

    public static void main(String[] args) {
        System.out.println(System.getProperty("java.specification.version"));
    }
}