Java Unsafe 類

Unsafe類是啥?

Java最初被設計爲一種安全的受控環境。儘管如此,Java HotSpot仍是包含了一個「後門」,提供了一些能夠直接操控內存和線程的低層次操做。這個後門類——sun.misc.Unsafe——被JDK普遍用於本身的包中,如java.nio和java.util.concurrent。可是絲絕不建議在生產環境中使用這個後門。由於這個API十分不安全、不輕便、並且不穩定。這個不安全的類提供了一個觀察HotSpot JVM內部結構而且能夠對其進行修改。有時它能夠被用來在不適用C++調試的狀況下學習虛擬機內部結構,有時也能夠被拿來作性能監控和開發工具。css

爲何叫Unsafe?

Java官方不推薦使用Unsafe類,由於官方認爲,這個類別人很難正確使用,非正確使用會給JVM帶來致命錯誤。並且將來Java可能封閉丟棄這個類。java

如何使用Unsafe?

1. 獲取Unsafe實例:

通讀Unsafe源碼,Unsafe提供了一個私有的靜態實例,而且經過檢查classloader是否爲null來避免java程序直接使用unsafe:程序員

//Unsafe源碼
private static final Unsafe theUnsafe;
@CallerSensitive
public static Unsafe getUnsafe() {
    Class var0 = Reflection.getCallerClass();
    if(var0.getClassLoader() != null) {
        throw new SecurityException("Unsafe");
    } else {
        return theUnsafe;
    }
}

咱們能夠經過以下代碼反射獲取Unsafe靜態類:數組

/** * 獲取Unsafe */
Field f = null;
Unsafe unsafe = null;
try {
    f = Unsafe.class.getDeclaredField("theUnsafe");
    f.setAccessible(true);
    unsafe = (Unsafe) f.get(null);
} catch (NoSuchFieldException e) {
    e.printStackTrace();
} catch (IllegalAccessException e) {
    e.printStackTrace();
}

2. 經過Unsafe分配使用堆外內存:

C++中有malloc,realloc和free方法來操做內存。在Unsafe類中對應爲:安全

//分配var1字節大小的內存,返回起始地址偏移量
public native long allocateMemory(long var1);
//從新給var1起始地址的內存分配長度爲var3字節大小的內存,返回新的內存起始地址偏移量
public native long reallocateMemory(long var1, long var3);
//釋放起始地址爲var1的內存
public native void freeMemory(long var1);

分配內存方法還有重分配內存方法都是分配的堆外內存,返回的是一個long類型的地址偏移量。這個偏移量在你的Java程序中每塊內存都是惟一的。
舉例:markdown

/** * 在堆外分配一個byte */
long allocatedAddress = unsafe.allocateMemory(1L);
unsafe.putByte(allocatedAddress, (byte) 100);
byte shortValue = unsafe.getByte(allocatedAddress);
System.out.println(new StringBuilder().append("Address:").append(allocatedAddress).append(" Value:").append(shortValue));
/** * 從新分配一個long */
allocatedAddress = unsafe.reallocateMemory(allocatedAddress, 8L);
unsafe.putLong(allocatedAddress, 1024L);
long longValue = unsafe.getLong(allocatedAddress);
System.out.println(new StringBuilder().append("Address:").append(allocatedAddress).append(" Value:").append(longValue));
/** * Free掉,這個數據可能髒掉 */
unsafe.freeMemory(allocatedAddress);
longValue = unsafe.getLong(allocatedAddress);
System.out.println(new StringBuilder().append("Address:").append(allocatedAddress).append(" Value:").append(longValue));

輸出:併發

Address:46490464 Value:100
Address:46490480 Value:1024
Address:46490480 Value:22

3. 操做類對象

咱們能夠經過Unsafe類來操做修改某一field。原理是首先獲取對象的基址(對象在內存的偏移量起始地址)。以後獲取某個filed在這個對象對應的類中的偏移地址,二者相加修改。app

/** * 獲取類的某個對象的某個field偏移地址 */
try {
    f = SampleClass.class.getDeclaredField("i");
} catch (NoSuchFieldException e) {
    e.printStackTrace();
}
long iFiledAddressShift = unsafe.objectFieldOffset(f);
SampleClass sampleClass = new SampleClass();
//獲取對象的偏移地址,須要將目標對象設爲輔助數組的第一個元素(也是惟一的元素)。因爲這是一個複雜類型元素(不是基本數據類型),它的地址存儲在數組的第一個元素。而後,獲取輔助數組的基本偏移量。數組的基本偏移量是指數組對象的起始地址與數組第一個元素之間的偏移量。
Object helperArray[]    = new Object[1];
helperArray[0]      = sampleClass;
long baseOffset     = unsafe.arrayBaseOffset(Object[].class);
long addressOfSampleClass    = unsafe.getLong(helperArray, baseOffset);
int i = unsafe.getInt(addressOfSampleClass + iFiledAddressShift);
System.out.println(new StringBuilder().append(" Field I Address:").append(addressOfSampleClass).append("+").append(iFiledAddressShift).append(" Value:").append(i));

輸出:框架

Field I Address:3610777760+24 Value:5

4. 線程掛起和恢復

將一個線程進行掛起是經過park方法實現的,調用 park後,線程將一直阻塞直到超時或者中斷等條件出現。unpark能夠終止一個掛起的線程,使其恢復正常。整個併發框架中對線程的掛起操做被封裝在 LockSupport類中,LockSupport類中有各類版本pack方法,但最終都調用了Unsafe.park()方法。less

public class LockSupport {  
    public static void unpark(Thread thread) {  
        if (thread != null)  
            unsafe.unpark(thread);  
    }  

    public static void park(Object blocker) {  
        Thread t = Thread.currentThread();  
        setBlocker(t, blocker);  
        unsafe.park(false, 0L);  
        setBlocker(t, null);  
    }  

    public static void parkNanos(Object blocker, long nanos) {  
        if (nanos > 0) {  
            Thread t = Thread.currentThread();  
            setBlocker(t, blocker);  
            unsafe.park(false, nanos);  
            setBlocker(t, null);  
        }  
    }  

    public static void parkUntil(Object blocker, long deadline) {  
        Thread t = Thread.currentThread();  
        setBlocker(t, blocker);  
        unsafe.park(true, deadline);  
        setBlocker(t, null);  
    }  

    public static void park() {  
        unsafe.park(false, 0L);  
    }  

    public static void parkNanos(long nanos) {  
        if (nanos > 0)  
            unsafe.park(false, nanos);  
    }  

    public static void parkUntil(long deadline) {  
        unsafe.park(true, deadline);  
    }  
}

5. CAS操做

/** * 比較obj的offset處內存位置中的值和指望的值,若是相同則更新。此更新是不可中斷的。 * * @param obj 須要更新的對象 * @param offset obj中整型field的偏移量 * @param expect 但願field中存在的值 * @param update 若是指望值expect與field的當前值相同,設置filed的值爲這個新值 * @return 若是field的值被更改返回true */  
public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);

6. Clone

如何實現淺克隆?在clone(){…}方法中調用super.clone(),對嗎?這裏存在的問題是首先你必須繼續Cloneable接口,而且在全部你須要作淺克隆的對象中實現clone()方法,對於一個懶懶的程序員來講,這個工做量太大了。
我不推薦上面的作法而是直接使用Unsafe,咱們能夠僅使用幾行代碼就實現淺克隆,而且它能夠像某些工具類同樣用於任意類的克隆。
首先,咱們須要一個計算Object大小的工具類:

class ObjectInfo {
    /** * Field name */
    public final String name;
    /** * Field type name */
    public final String type;
    /** * Field data formatted as string */
    public final String contents;
    /** * Field offset from the start of parent object */
    public final int offset;
    /** * Memory occupied by this field */
    public final int length;
    /** * Offset of the first cell in the array */
    public final int arrayBase;
    /** * Size of a cell in the array */
    public final int arrayElementSize;
    /** * Memory occupied by underlying array (shallow), if this is array type */
    public final int arraySize;
    /** * This object fields */
    public final List<ObjectInfo> children;

    public ObjectInfo(String name, String type, String contents, int offset, int length, int arraySize,
                      int arrayBase, int arrayElementSize) {
        this.name = name;
        this.type = type;
        this.contents = contents;
        this.offset = offset;
        this.length = length;
        this.arraySize = arraySize;
        this.arrayBase = arrayBase;
        this.arrayElementSize = arrayElementSize;
        children = new ArrayList<ObjectInfo>(1);
    }

    public void addChild(final ObjectInfo info) {
        if (info != null)
            children.add(info);
    }

    /** * Get the full amount of memory occupied by a given object. This value may be slightly less than * an actual value because we don't worry about memory alignment - possible padding after the last object field. * <p/> * The result is equal to the last field offset + last field length + all array sizes + all child objects deep sizes * * @return Deep object size */
    public long getDeepSize() {
        //return length + arraySize + getUnderlyingSize( arraySize != 0 );
        return addPaddingSize(arraySize + getUnderlyingSize(arraySize != 0));
    }

    long size = 0;

    private long getUnderlyingSize(final boolean isArray) {
        //long size = 0;
        for (final ObjectInfo child : children)
            size += child.arraySize + child.getUnderlyingSize(child.arraySize != 0);
        if (!isArray && !children.isEmpty()) {
            int tempSize = children.get(children.size() - 1).offset + children.get(children.size() - 1).length;
            size += addPaddingSize(tempSize);
        }

        return size;
    }

    private static final class OffsetComparator implements Comparator<ObjectInfo> {
        @Override
        public int compare(final ObjectInfo o1, final ObjectInfo o2) {
            return o1.offset - o2.offset; //safe because offsets are small non-negative numbers
        }
    }

    //sort all children by their offset
    public void sort() {
        Collections.sort(children, new OffsetComparator());
    }

    @Override
    public String toString() {
        final StringBuilder sb = new StringBuilder();
        toStringHelper(sb, 0);
        return sb.toString();
    }

    private void toStringHelper(final StringBuilder sb, final int depth) {
        depth(sb, depth).append("name=").append(name).append(", type=").append(type)
                .append(", contents=").append(contents).append(", offset=").append(offset)
                .append(", length=").append(length);
        if (arraySize > 0) {
            sb.append(", arrayBase=").append(arrayBase);
            sb.append(", arrayElemSize=").append(arrayElementSize);
            sb.append(", arraySize=").append(arraySize);
        }
        for (final ObjectInfo child : children) {
            sb.append('\n');
            child.toStringHelper(sb, depth + 1);
        }
    }

    private StringBuilder depth(final StringBuilder sb, final int depth) {
        for (int i = 0; i < depth; ++i)
            sb.append("\t");
        return sb;
    }

    private long addPaddingSize(long size) {
        if (size % 8 != 0) {
            return (size / 8 + 1) * 8;
        }
        return size;
    }

}

class ClassIntrospector {

    private static final Unsafe unsafe;
    /** * Size of any Object reference */
    private static final int objectRefSize;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);

            objectRefSize = unsafe.arrayIndexScale(Object[].class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /** * Sizes of all primitive values */
    private static final Map<Class, Integer> primitiveSizes;

    static {
        primitiveSizes = new HashMap<Class, Integer>(10);
        primitiveSizes.put(byte.class, 1);
        primitiveSizes.put(char.class, 2);
        primitiveSizes.put(int.class, 4);
        primitiveSizes.put(long.class, 8);
        primitiveSizes.put(float.class, 4);
        primitiveSizes.put(double.class, 8);
        primitiveSizes.put(boolean.class, 1);
    }

    /** * Get object information for any Java object. Do not pass primitives to * this method because they will boxed and the information you will get will * be related to a boxed version of your value. * * @param obj Object to introspect * @return Object info * @throws IllegalAccessException */
    public ObjectInfo introspect(final Object obj)
            throws IllegalAccessException {
        try {
            return introspect(obj, null);
        } finally { // clean visited cache before returning in order to make
            // this object reusable
            m_visited.clear();
        }
    }

    // we need to keep track of already visited objects in order to support
    // cycles in the object graphs
    private IdentityHashMap<Object, Boolean> m_visited = new IdentityHashMap<Object, Boolean>(
            100);

    private ObjectInfo introspect(final Object obj, final Field fld)
            throws IllegalAccessException {
        // use Field type only if the field contains null. In this case we will
        // at least know what's expected to be
        // stored in this field. Otherwise, if a field has interface type, we
        // won't see what's really stored in it.
        // Besides, we should be careful about primitives, because they are
        // passed as boxed values in this method
        // (first arg is object) - for them we should still rely on the field
        // type.
        boolean isPrimitive = fld != null && fld.getType().isPrimitive();
        boolean isRecursive = false; // will be set to true if we have already
        // seen this object
        if (!isPrimitive) {
            if (m_visited.containsKey(obj))
                isRecursive = true;
            m_visited.put(obj, true);
        }

        final Class type = (fld == null || (obj != null && !isPrimitive)) ? obj
                .getClass() : fld.getType();
        int arraySize = 0;
        int baseOffset = 0;
        int indexScale = 0;
        if (type.isArray() && obj != null) {
            baseOffset = unsafe.arrayBaseOffset(type);
            indexScale = unsafe.arrayIndexScale(type);
            arraySize = baseOffset + indexScale * Array.getLength(obj);
        }

        final ObjectInfo root;
        if (fld == null) {
            root = new ObjectInfo("", type.getCanonicalName(), getContents(obj,
                    type), 0, getShallowSize(type), arraySize, baseOffset,
                    indexScale);
        } else {
            final int offset = (int) unsafe.objectFieldOffset(fld);
            root = new ObjectInfo(fld.getName(), type.getCanonicalName(),
                    getContents(obj, type), offset, getShallowSize(type),
                    arraySize, baseOffset, indexScale);
        }

        if (!isRecursive && obj != null) {
            if (isObjectArray(type)) {
                // introspect object arrays
                final Object[] ar = (Object[]) obj;
                for (final Object item : ar)
                    if (item != null)
                        root.addChild(introspect(item, null));
            } else {
                for (final Field field : getAllFields(type)) {
                    if ((field.getModifiers() & Modifier.STATIC) != 0) {
                        continue;
                    }
                    field.setAccessible(true);
                    root.addChild(introspect(field.get(obj), field));
                }
            }
        }

        root.sort(); // sort by offset
        return root;
    }

    // get all fields for this class, including all superclasses fields
    private static List<Field> getAllFields(final Class type) {
        if (type.isPrimitive())
            return Collections.emptyList();
        Class cur = type;
        final List<Field> res = new ArrayList<Field>(10);
        while (true) {
            Collections.addAll(res, cur.getDeclaredFields());
            if (cur == Object.class)
                break;
            cur = cur.getSuperclass();
        }
        return res;
    }

    // check if it is an array of objects. I suspect there must be a more
    // API-friendly way to make this check.
    private static boolean isObjectArray(final Class type) {
        if (!type.isArray())
            return false;
        if (type == byte[].class || type == boolean[].class
                || type == char[].class || type == short[].class
                || type == int[].class || type == long[].class
                || type == float[].class || type == double[].class)
            return false;
        return true;
    }

    // advanced toString logic
    private static String getContents(final Object val, final Class type) {
        if (val == null)
            return "null";
        if (type.isArray()) {
            if (type == byte[].class)
                return Arrays.toString((byte[]) val);
            else if (type == boolean[].class)
                return Arrays.toString((boolean[]) val);
            else if (type == char[].class)
                return Arrays.toString((char[]) val);
            else if (type == short[].class)
                return Arrays.toString((short[]) val);
            else if (type == int[].class)
                return Arrays.toString((int[]) val);
            else if (type == long[].class)
                return Arrays.toString((long[]) val);
            else if (type == float[].class)
                return Arrays.toString((float[]) val);
            else if (type == double[].class)
                return Arrays.toString((double[]) val);
            else
                return Arrays.toString((Object[]) val);
        }
        return val.toString();
    }

    // obtain a shallow size of a field of given class (primitive or object
    // reference size)
    private static int getShallowSize(final Class type) {
        if (type.isPrimitive()) {
            final Integer res = primitiveSizes.get(type);
            return res != null ? res : 0;
        } else
            return objectRefSize;
    }
}

咱們經過這兩個類計算一個Object的大小,經過Unsafe的 public native void copyMemory(Object var1, long var2, Object var4, long var5, long var7)方法來拷貝:
兩個工具方法:

private static Object helperArray[] = new Object[1];
/** * 獲取對象起始位置偏移量 * @param unsafe * @param object * @return */
public static long getObjectAddress(Unsafe unsafe, Object object){
    helperArray[0] = object;
    long baseOffset = unsafe.arrayBaseOffset(Object[].class);
    return unsafe.getLong(helperArray, baseOffset);
}

private final static ClassIntrospector ci = new ClassIntrospector();

/** * 獲取Object的大小 * @param object * @return */
public static long getObjectSize(Object object){
    ObjectInfo res = null;
    try {
        res = ci.introspect(object);
    } catch (IllegalAccessException e) {
        e.printStackTrace();
    }
    return res.getDeepSize();
}

測試:

SampleClass sampleClass = new SampleClass();
sampleClass.setI(999);
sampleClass.setL(999999999L);

SampleClass sampleClassCopy = new SampleClass();
long copyAddress = getObjectAddress(unsafe,sampleClassCopy);
unsafe.copyMemory(sampleClass, 0, null,copyAddress, getObjectSize(sampleClass));
i = unsafe.getInt(copyAddress + iFiledAddressShift);
System.out.println(i);
System.out.println(sampleClassCopy.getL());

輸出:

999
999999999
相關文章
相關標籤/搜索