sun.misc.Unsafe的理解

前言

如下sun.misc.Unsafe源碼和demo基於jdk1.7;java

最近在看J.U.C裏的源碼,不少都用到了sun.misc.Unsafe這個類,只知其一;不知其二,看起來總感受有點不盡興,因此打算對Unsafe的源碼及使用作個分析;c++

另外,網上找了份c++的源代碼natUnsafe.cc(惋惜比較老,Copyright (C) 2006, 2007年的,沒找到新的),也就是sun.misc.Unsafe的C++實現,跟Unsafe類中的native方法對照起來看更加容易理解;git

Unsafe類的做用

能夠用來在任意內存地址位置處讀寫數據,可見,對於普通用戶來講,使用起來仍是比較危險的;github

另外,還支持一些CAS原子操做;bootstrap

獲取Unsafe對象

遺憾的是,Unsafe對象不能直接經過new Unsafe()或調用Unsafe.getUnsafe()獲取,緣由以下:緩存

*不能直接new Unsafe(),緣由是Unsafe被設計成單例模式,構造方法是私有的;ide

*不能經過調用Unsafe.getUnsafe()獲取,由於getUnsafe被設計成只能從引導類加載器(bootstrap class loader)加載,從getUnsafe的源碼中也能夠看出來,以下:this

    @CallerSensitive
    public static Unsafe getUnsafe() {
        //獲得調用該方法的Class對象
        Class cc = Reflection.getCallerClass();
        //判斷調用該方法的類是不是引導類加載器(bootstrap class loader)
        //若是不是的話,好比由AppClassLoader調用該方法,則拋出SecurityException異常
        if (cc.getClassLoader() != null)
            throw new SecurityException("Unsafe");
        //返回單例對象
        return theUnsafe;
    }

雖然咱們不能經過以上方法獲得Unsafe對象,但得Unsafe類中有個私有的靜態全局屬性theUnsafe(Unsafe實例對象,經過反射,能夠獲取到該成員屬性theUnsafe對應的Field對象,並將其設置爲可訪問,從而獲得theUnsafe具體對象,以下代碼:spa

package concurrency;

import java.lang.reflect.Field;
import sun.misc.Unsafe;
import sun.reflect.Reflection;

public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException {
        // 經過反射獲得theUnsafe對應的Field對象
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        // 設置該Field爲可訪問
        field.setAccessible(true);
        // 經過Field獲得該Field對應的具體對象,傳入null是由於該Field爲static的
        Unsafe unsafe = (Unsafe) field.get(null);
        System.out.println(unsafe);

    }
}

 Unsafe類中的API

allocateInstance方法,不調用構造方法生成對象

 本地方法,功能是生成一個對象實例,可是不會運行該對象的構造方法;因爲natUnsafe.cc版本較老,沒找到對應的c++實現;線程

    /** Allocate an instance but do not run any constructor. Initializes the class if it has not yet been. */
    public native Object allocateInstance(Class cls)
        throws InstantiationException;

例子,利用Unsafe的allocateInstance方法,在未調用構造方法的狀況下生成了對象:

package concurrency;

import java.lang.reflect.Field;

import sun.misc.Unsafe;
import sun.reflect.Reflection;

class User {
    private String name = "";
    private int age = 0;

    public User() {
        this.name = "test";
        this.age = 22;
    }
    
    @Override
    public String toString() {
        return name + ": " + age;
    }
}


public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
        // 經過反射獲得theUnsafe對應的Field對象
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        // 設置該Field爲可訪問
        field.setAccessible(true);
        // 經過Field獲得該Field對應的具體對象,傳入null是由於該Field爲static的
        Unsafe unsafe = (Unsafe) field.get(null);

        User user = (User) unsafe.allocateInstance(User.class);
        System.out.println(user); //dont invoke constructor, print null: 0
        
        User userFromNormal = new User();
        System.out.println(userFromNormal); //print test: 22

    }
}

objectFieldOffset方法,返回成員屬性在內存中的地址相對於對象內存地址的偏移量

比較簡單,就是返回成員屬性內存地址相對於對象內存地址的偏移量,經過該方法能夠計算一個對象在內存中的空間大小,方法是經過反射獲得它的全部Field(包括父類繼承獲得的),找出Field中偏移量最大值,而後對該最大偏移值填充字節數即爲對象大小;

關於該方法的使用例子能夠看下面的修改內存數據的例子;

putLong,putInt,putDouble,putChar,putObject等方法,直接修改內存數據(能夠越過訪問權限)

這裏,還有put對應的get方法,很簡單就是直接讀取內存地址處的數據,不作舉例;

 咱們能夠舉個putLong(Object, long, long)方法詳細看下其具體實現,其它的相似,先看Java的源碼,沒啥好看的,就聲明瞭一個native本地方法:

三個參數說明下:

Object o//對象引用
long offset//對象內存地址的偏移量
long x//寫入的數據
    public native void    putLong(Object o, long offset, long x);

仍是看下natUnsafe.cc中的c++實現吧,很簡單,就是計算要寫入數據的內存地址,而後寫入數據,以下:

void
sun::misc::Unsafe::putLong (jobject obj, jlong offset, jlong value)
{
  jlong *addr = (jlong *) ((char *) obj + offset);//計算要修改的數據的內存地址=對象地址+成員屬性地址偏移量
  spinlock lock;//自旋鎖,經過循環來獲取鎖, i386處理器須要加鎖訪問64位數據,若是是int,則不須要改行代碼
  *addr = value;//往該內存地址位置直接寫入數據
}

以下例子,即便User類的成員屬性是私有的且沒有提供對外的public方法,咱們仍是能夠直接在它們的內存地址位置處寫入數據,併成功;

package concurrency;

import java.lang.reflect.Field;

import sun.misc.Unsafe;
import sun.reflect.Reflection;

class User {
    private String name = "test"; 
    private long id = 1;
    private int age = 2;
    private double height = 1.72;
    

    @Override
    public String toString() {
        return name + "," + id + "," + age + "," + height;
    }
}


public class Test {
    public static void main(String[] args) throws NoSuchFieldException,
            SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException {
        // 經過反射獲得theUnsafe對應的Field對象
        Field field = Unsafe.class.getDeclaredField("theUnsafe");
        // 設置該Field爲可訪問
        field.setAccessible(true);
        // 經過Field獲得該Field對應的具體對象,傳入null是由於該Field爲static的
        Unsafe unsafe = (Unsafe) field.get(null);

        User user = new User();
        System.out.println(user); //打印test,1,2,1.72
        
        Class userClass = user.getClass();
        Field name = userClass.getDeclaredField("name");
        Field id = userClass.getDeclaredField("id");
        Field age = userClass.getDeclaredField("age");
        Field height = userClass.getDeclaredField("height");
        //直接往內存地址寫數據
        unsafe.putObject(user, unsafe.objectFieldOffset(name), "midified-name");
        unsafe.putLong(user, unsafe.objectFieldOffset(id),100l);
        unsafe.putInt(user, unsafe.objectFieldOffset(age), 101);
        unsafe.putDouble(user, unsafe.objectFieldOffset(height), 100.1);
        
        System.out.println(user);//打印midified-name,100,101,100.1

    }
}

 copyMemory、freeMemory

copyMemory:內存數據拷貝

freeMemory:用於釋放allocateMemory和reallocateMemory申請的內存

 CAS操做的方法,compareAndSwapInt,compareAndSwapLong等

看下natUnsafe.cc中的c++實現吧,加深理解,其實就是將內存值與預期值做比較,判斷是否相等,相等的話,寫入數據,不相等不作操做,返回舊數據;

static inline bool
compareAndSwap (volatile jint *addr, jint old, jint new_val)
{
  jboolean result = false;
  spinlock lock;
  if ((result = (*addr == old)))
    *addr = new_val;
  return result;
}

J.U.C裏原子類就是基於以上CAS操做實現的;

getLongVolatile/putLongVolatile等等方法

這類方法使用volatile語義去存取數據,個人理解就是各個線程不緩存數據,直接在內存中讀取數據;

 

 參考鏈接:

https://github.com/aeste/gcc/blob/master/libjava/sun/misc/natUnsafe.cc

http://ifeve.com/sun-misc-unsafe/

http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/misc/Unsafe.java

相關文章
相關標籤/搜索