sun.misc.Unsafe操作手冊
Java是一個安全的開發工具,它阻止開發人員犯很多低階的錯誤,而大部份的錯誤都是基於記憶體管理方面的。如果你想搞破壞,可以使用Unsafe這個類。這個類是屬於sun.* API中的類,並且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文件,更可悲的是,它也沒有比較好的程式碼文件。
例項化sun.misc.Unsafe
如果你嘗試建立Unsafe類的例項,基於以下兩種原因是不被允許的:
- Unsafe類的建構函式是私有的;
- 雖然它有靜態的getUnsafe()方法,但是如果你嘗試呼叫Unsafe.getUnsafe(),會得到一個SecutiryException。這個類只有被JDK信任的類例項化。
但是這總會是有變通的解決辦法的,一個簡單的方式就是使用反射進行例項化:
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
使用sun.misc.Unsafe
1. 建立例項
通過allocateInstance()方法,你可以建立一個類的例項,但是卻不需要呼叫它的建構函式、初始化程式碼、各種JVM安全檢查以及其它的一些底層的東西。即使建構函式是私有,我們也可以通過這個方法建立它的例項。
public class UnsafePlayer {
public static void main(String[] args) throws Exception {
//通過反射例項化Unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
//例項化私有的建構函式
Player player = (Player) unsafe.allocateInstance(Player.class );
player.setName("jack");
System.out.println(player.getName());
}
public static class Player{
@Getter @Setter private String name;
private Player(){}
}
}
2. 可以分配記憶體和釋放記憶體
類中提供的3個本地方法allocateMemory、reallocateMemory、freeMemory分別用於分配記憶體,擴充記憶體和釋放記憶體,與C語言中的3個方法對應。
3. 通過記憶體偏移地址修改變數值
public native long objectFieldOffset(Field field);
返回指定靜態field的記憶體地址偏移量,在這個類的其他方法中這個值只是被用作一個訪問特定field的一個方式。這個值對於給定的field是唯一的,並且後續對該方法的呼叫都應該返回相同的值。
public native int arrayBaseOffset(Class arrayClass);
獲取給定陣列中第一個元素的偏移地址。為了存取陣列中的元素,這個偏移地址與arrayIndexScale方法的非0返回值一起被使用。
public native int arrayIndexScale(Class arrayClass)
獲取使用者給定陣列定址的換算因子。如果不能返回一個合適的換算因子的時候就會返回0。這個返回值能夠與arrayBaseOffset一起使用去存取這個陣列class中的元素
public native boolean compareAndSwapInt(Object obj, long offset,int expect, int update);
在obj的offset位置比較integer field和期望的值,如果相同則更新。這個方法的操作應該是原子的,因此提供了一種不可中斷的方式更新integer field。當然還有與Object、Long對應的compareAndSwapObject和compareAndSwapLong方法。
public native void putOrderedInt(Object obj, long offset, int value);
設定obj物件中offset偏移地址對應的整型field的值為指定值。這是一個有序或者有延遲的putIntVolatile方法,並且不保證值的改變被其他執行緒立即看到。只有在field被volatile修飾並且期望被意外修改的時候使用才有用。當然還有與Object、Long對應的putOrderedObject和putOrderedLong方法。
public native void putObjectVolatile(Object obj, long offset, Object value);
設定obj物件中offset偏移地址對應的object型field的值為指定值。支援volatile store語義。
與這個方法對應的get方法為:
public native Object getObjectVolatile(Object obj, long offset);
獲取obj物件中offset偏移地址對應的object型field的值,支援volatile load語義
這兩個方法還有與Int、Boolean、Byte、Short、Char、Long、Float、Double等型別對應的相關方法.
public native void putObject(Object obj, long offset, Object value);
設定obj物件中offset偏移地址對應的object型field的值為指定值。與putObject方法對應的是getObject方法。Int、Boolean、Byte、Short、Char、Long、Float、Double等型別都有getXXX和putXXX形式的方法。
下面通過一個組合示例來了解一下如何使用它們,詳細如下:
public class UnsafePlayerCAS {
public static void main(String[] args) throws Exception{
//通過反射例項化Unsafe
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);
//例項化私有的建構函式
Player player = (Player) unsafe.allocateInstance(Player.class);
String name = "jack";
int age = 19;
player.setName(name);
player.setAge(age);
for (Field field : Player.class.getDeclaredFields()) {
System.out.println(field.getName() + ":對應的記憶體偏移地址:"
+ unsafe.objectFieldOffset(field));
}
//上面的輸出為 name:對應的記憶體偏移地址:16
//age:對應的記憶體偏移地址:12
//修改記憶體偏移地址為12的值(age),返回true,說明通過記憶體偏移地址修改age的值成功
System.out.println(unsafe.compareAndSwapInt(player, 12, age, age + 1));
System.out.println("age修改後的值:" + player.getAge());
//修改記憶體偏移地址為12的值,但是修改後不保證立馬能被其他的執行緒看到。
unsafe.putOrderedInt(player, 12, age + 2);
System.out.println("age修改後的值:" + player.getAge());
//修改記憶體偏移地址為16的值,volatile修飾,修改能立馬對其他執行緒可見
unsafe.putObjectVolatile(player, 16, "tom");
System.out.println("name:" + player.getName());
System.out.println(unsafe.getObjectVolatile(player,16));
}
public static class Player{
@Getter @Setter private String name;
@Getter @Setter private int age;
private Player(){}
}
}
4. 掛起與恢復
將一個執行緒進行掛起是通過park方法實現的,呼叫 park後,執行緒將一直阻塞直到超時或者中斷等條件出現。unpark可以終止一個掛起的執行緒,使其恢復正常。整個併發框架中對執行緒的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都呼叫了Unsafe.park()方法。
public native void park(boolean isAbsolute, long timeout);
阻塞一個執行緒直到unpark出現、執行緒被中斷或者timeout時間到期。如果一個unpark呼叫已經出現了,這裡只計數。timeout為0表示永不過期。當isAbsolute為true時,timeout是相對於新紀元之後的毫秒。否則這個值就是超時前的納秒數。這個方法執行時也可能不合理地返回。
public native void unpark(Thread thread);
釋放被park建立的在一個執行緒上的阻塞.這個方法也可以被使用來終止一個先前呼叫park
導致的阻塞這個操作操作時不安全的,因此執行緒必須保證是活的.這是java程式碼不是native程式碼。引數thread指要解除阻塞的執行緒。
下面來看一下LockSupport類中關於Unsafe.park和Unsafe.unpark的使用:
private static void setBlocker(Thread t, Object arg) {
// Even though volatile, hotspot doesn't need a write barrier here.
UNSAFE.putObject(t, parkBlockerOffset, arg);
}
//恢復阻塞執行緒
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);
}
//阻塞當前執行緒nanos納秒
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);
}
//阻塞當前執行緒nanos納秒
public static void parkNanos(long nanos) {
if (nanos > 0)
UNSAFE.park(false, nanos);
}
public static void parkUntil(long deadline) {
UNSAFE.park(true, deadline);
}
下面是使用LockSupport的示例:
public class LockDemo {
public static void main(String[] args) throws InterruptedException {
ThreadPark threadPark = new ThreadPark();
threadPark.start();
ThreadUnpark threadUnPark = new ThreadUnpark(threadPark);
threadUnPark.start();
//等待threadUnPark執行成功
threadUnPark.join();
System.out.println("執行成功....");
}
static class ThreadPark extends Thread{
@Override public void run(){
System.out.println(Thread.currentThread() + "我將被阻塞在這了60s....");
LockSupport.parkNanos(1000000000L * 60);
System.out.println(Thread.currentThread() +"我被恢復正常了....");
}
}
static class ThreadUnpark extends Thread{
public Thread thread = null;
public ThreadUnpark(Thread thread) {
this.thread = thread;
}
public void run(){
System.out.println("提前恢復阻塞執行緒ThreadPark");
//恢復阻塞執行緒
LockSupport.unpark(thread);
}
}
}
程式輸出:
Thread[Thread-0,5,main]我將被阻塞在這了60s....
提前恢復阻塞執行緒ThreadPark
Thread[Thread-0,5,main]我被恢復正常了....
執行成功....
當然sun.misc.Unsafe中還有一些其它的功能,讀者可以繼續深挖。sun.misc.Unsafe提供了可以隨意檢視及修改JVM中執行時的資料結構,儘管這些功能在JAVA開發本身是不適用的,Unsafe是一個用於研究學習HotSpot虛擬機器非常棒的工具,因為它不需要呼叫C++程式碼,或者需要建立即時分析的工具。
參考資料
- https://blog.csdn.net/fenglibing/article/details/17138079
- https://blog.csdn.net/dfdsggdgg/article/details/51538601
- https://blog.csdn.net/aesop_wubo/article/details/7537278
- https://blog.csdn.net/dfdsggdgg/article/details/51543545
歡迎支援《RabbitMQ實戰指南》以及關注微信公眾號:朱小廝的部落格。