sun.misc.Unsafe操作手册
歡迎支持筆者新作:《深入理解Kafka:核心設(shè)計與實(shí)踐原理》和《RabbitMQ實(shí)戰(zhàn)指南》,同時歡迎關(guān)注筆者的微信公眾號:朱小廝的博客。
歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/java/sun-misc-unsafe-operation-manual/
Java是一個安全的開發(fā)工具,它阻止開發(fā)人員犯很多低級的錯誤,而大部份的錯誤都是基于內(nèi)存管理方面的。如果你想搞破壞,可以使用Unsafe這個類。這個類是屬于sun.* API中的類,并且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文檔,更可悲的是,它也沒有比較好的代碼文檔。
實(shí)例化sun.misc.Unsafe
如果你嘗試創(chuàng)建Unsafe類的實(shí)例,基于以下兩種原因是不被允許的:
但是這總會是有變通的解決辦法的,一個簡單的方式就是使用反射進(jìn)行實(shí)例化:
使用sun.misc.Unsafe
1. 創(chuàng)建實(shí)例
通過allocateInstance()方法,你可以創(chuàng)建一個類的實(shí)例,但是卻不需要調(diào)用它的構(gòu)造函數(shù)、初始化代碼、各種JVM安全檢查以及其它的一些底層的東西。即使構(gòu)造函數(shù)是私有,我們也可以通過這個方法創(chuàng)建它的實(shí)例。
public class UnsafePlayer {public static void main(String[] args) throws Exception {//通過反射實(shí)例化UnsafeField f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);//實(shí)例化私有的構(gòu)造函數(shù)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. 可以分配內(nèi)存和釋放內(nèi)存
類中提供的3個本地方法allocateMemory、reallocateMemory、freeMemory分別用于分配內(nèi)存,擴(kuò)充內(nèi)存和釋放內(nèi)存,與C語言中的3個方法對應(yīng)。
3. 通過內(nèi)存偏移地址修改變量值
public native long objectFieldOffset(Field field);
返回指定靜態(tài)field的內(nèi)存地址偏移量,在這個類的其他方法中這個值只是被用作一個訪問特定field的一個方式。這個值對于給定的field是唯一的,并且后續(xù)對該方法的調(diào)用都應(yīng)該返回相同的值。
public native int arrayBaseOffset(Class arrayClass);
獲取給定數(shù)組中第一個元素的偏移地址。為了存取數(shù)組中的元素,這個偏移地址與arrayIndexScale方法的非0返回值一起被使用。
public native int arrayIndexScale(Class arrayClass)
獲取用戶給定數(shù)組尋址的換算因子。如果不能返回一個合適的換算因子的時候就會返回0。這個返回值能夠與arrayBaseOffset一起使用去存取這個數(shù)組class中的元素
public native boolean compareAndSwapInt(Object obj, long offset,int expect, int update);
在obj的offset位置比較integer field和期望的值,如果相同則更新。這個方法的操作應(yīng)該是原子的,因此提供了一種不可中斷的方式更新integer field。當(dāng)然還有與Object、Long對應(yīng)的compareAndSwapObject和compareAndSwapLong方法。
public native void putOrderedInt(Object obj, long offset, int value);
設(shè)置obj對象中offset偏移地址對應(yīng)的整型field的值為指定值。這是一個有序或者有延遲的putIntVolatile方法,并且不保證值的改變被其他線程立即看到。只有在field被volatile修飾并且期望被意外修改的時候使用才有用。當(dāng)然還有與Object、Long對應(yīng)的putOrderedObject和putOrderedLong方法。
public native void putObjectVolatile(Object obj, long offset, Object value);
設(shè)置obj對象中offset偏移地址對應(yīng)的object型field的值為指定值。支持volatile store語義。
與這個方法對應(yīng)的get方法為:
public native Object getObjectVolatile(Object obj, long offset);
獲取obj對象中offset偏移地址對應(yīng)的object型field的值,支持volatile load語義
這兩個方法還有與Int、Boolean、Byte、Short、Char、Long、Float、Double等類型對應(yīng)的相關(guān)方法.
public native void putObject(Object obj, long offset, Object value);
設(shè)置obj對象中offset偏移地址對應(yīng)的object型field的值為指定值。與putObject方法對應(yīng)的是getObject方法。Int、Boolean、Byte、Short、Char、Long、Float、Double等類型都有g(shù)etXXX和putXXX形式的方法。
下面通過一個組合示例來了解一下如何使用它們,詳細(xì)如下:
public class UnsafePlayerCAS {public static void main(String[] args) throws Exception{//通過反射實(shí)例化UnsafeField f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);//實(shí)例化私有的構(gòu)造函數(shù)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() + ":對應(yīng)的內(nèi)存偏移地址:"+ unsafe.objectFieldOffset(field));}//上面的輸出為 name:對應(yīng)的內(nèi)存偏移地址:16//age:對應(yīng)的內(nèi)存偏移地址:12//修改內(nèi)存偏移地址為12的值(age),返回true,說明通過內(nèi)存偏移地址修改age的值成功System.out.println(unsafe.compareAndSwapInt(player, 12, age, age + 1));System.out.println("age修改后的值:" + player.getAge());//修改內(nèi)存偏移地址為12的值,但是修改后不保證立馬能被其他的線程看到。unsafe.putOrderedInt(player, 12, age + 2);System.out.println("age修改后的值:" + player.getAge());//修改內(nèi)存偏移地址為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. 掛起與恢復(fù)
將一個線程進(jìn)行掛起是通過park方法實(shí)現(xiàn)的,調(diào)用 park后,線程將一直阻塞直到超時或者中斷等條件出現(xiàn)。unpark可以終止一個掛起的線程,使其恢復(fù)正常。整個并發(fā)框架中對線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調(diào)用了Unsafe.park()方法。
public native void park(boolean isAbsolute, long timeout);
阻塞一個線程直到unpark出現(xiàn)、線程被中斷或者timeout時間到期。如果一個unpark調(diào)用已經(jīng)出現(xiàn)了,這里只計數(shù)。timeout為0表示永不過期。當(dāng)isAbsolute為true時,timeout是相對于新紀(jì)元之后的毫秒。否則這個值就是超時前的納秒數(shù)。這個方法執(zhí)行時也可能不合理地返回。
public native void unpark(Thread thread);
釋放被park創(chuàng)建的在一個線程上的阻塞.這個方法也可以被使用來終止一個先前調(diào)用park導(dǎo)致的阻塞這個操作操作時不安全的,因此線程必須保證是活的.這是java代碼不是native代碼。參數(shù)thread指要解除阻塞的線程。
下面來看一下LockSupport類中關(guān)于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); } //恢復(fù)阻塞線程 public static void unpark(Thread thread) {if (thread != null)UNSAFE.unpark(thread); } //一直阻塞當(dāng)前線程 public static void park(Object blocker) {Thread t = Thread.currentThread();setBlocker(t, blocker);UNSAFE.park(false, 0L);setBlocker(t, null); } //阻塞當(dāng)前線程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); } //一直阻塞當(dāng)前線程 public static void park() {UNSAFE.park(false, 0L); } //阻塞當(dāng)前線程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執(zhí)行成功threadUnPark.join();System.out.println("運(yùn)行成功....");}static class ThreadPark extends Thread{@Override public void run(){System.out.println(Thread.currentThread() + "我將被阻塞在這了60s....");LockSupport.parkNanos(1000000000L * 60);System.out.println(Thread.currentThread() +"我被恢復(fù)正常了....");}}static class ThreadUnpark extends Thread{public Thread thread = null;public ThreadUnpark(Thread thread) {this.thread = thread;}public void run(){System.out.println("提前恢復(fù)阻塞線程ThreadPark");//恢復(fù)阻塞線程LockSupport.unpark(thread);}} }程序輸出:
Thread[Thread-0,5,main]我將被阻塞在這了60s.... 提前恢復(fù)阻塞線程ThreadPark Thread[Thread-0,5,main]我被恢復(fù)正常了.... 運(yùn)行成功....當(dāng)然sun.misc.Unsafe中還有一些其它的功能,讀者可以繼續(xù)深挖。sun.misc.Unsafe提供了可以隨意查看及修改JVM中運(yùn)行時的數(shù)據(jù)結(jié)構(gòu),盡管這些功能在JAVA開發(fā)本身是不適用的,Unsafe是一個用于研究學(xué)習(xí)HotSpot虛擬機(jī)非常棒的工具,因?yàn)樗恍枰{(diào)用C++代碼,或者需要創(chuàng)建即時分析的工具。
參考資料
歡迎跳轉(zhuǎn)到本文的原文鏈接:https://honeypps.com/java/sun-misc-unsafe-operation-manual/
歡迎支持筆者新作:《深入理解Kafka:核心設(shè)計與實(shí)踐原理》和《RabbitMQ實(shí)戰(zhàn)指南》,同時歡迎關(guān)注筆者的微信公眾號:朱小廝的博客。
總結(jié)
以上是生活随笔為你收集整理的sun.misc.Unsafe操作手册的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 如何把一个运行完好的Kafka搞崩溃
- 下一篇: Kafka冷门知识——主题删除背后的秘密