前言
以下sun.misc.Unsafe源碼和demo基於jdk1.7;
最近在看J.U.C里的源碼,很多都用到了sun.misc.Unsafe這個類,一知半解,看起來總感覺有點不盡興,所以打算對Unsafe的源碼及使用做個分析;
另外,網上找了份c++的源代碼natUnsafe.cc(可惜比較老,Copyright (C) 2006, 2007年的,沒找到新的),也就是sun.misc.Unsafe的C++實現,跟Unsafe類中的native方法對照起來看更加容易理解;
Unsafe類的作用
可以用來在任意內存地址位置處讀寫數據,可見,對於普通用戶來說,使用起來還是比較危險的;
另外,還支持一些CAS原子操作;
獲取Unsafe對象
遺憾的是,Unsafe對象不能直接通過new Unsafe()
或調用Unsafe.getUnsafe()
獲取,原因如下:
*不能直接new Unsafe()
,原因是Unsafe
被設計成單例模式,構造方法是私有的;
*不能通過調用Unsafe.getUnsafe()獲取,因為
getUnsafe
被設計成只能從引導類加載器(bootstrap class loader)加載,從getUnsafe
的源碼中也可以看出來,如下:
@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具體對象,如下代碼:
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