Java Unsafe學(xué)習(xí)筆記分享
sun.misc.Unsafe
作用:可以用來(lái)在任意內(nèi)存地址位置處讀寫數(shù)據(jù),支持一些CAS原子操作
Java最初被設(shè)計(jì)為一種安全的受控環(huán)境。盡管如此,HotSpot還是包含了一個(gè)后門sun.misc.Unsafe,提供了一些可以直接操控內(nèi)存和線程的底層操作。Unsafe被JDK廣泛應(yīng)用于java.nio和并發(fā)包等實(shí)現(xiàn)中,這個(gè)不安全的類提供了一個(gè)觀察HotSpot JVM內(nèi)部結(jié)構(gòu)并且可以對(duì)其進(jìn)行修改,但是不建議在生產(chǎn)環(huán)境中使用
獲取Unsafe實(shí)例
Unsafe對(duì)象不能直接通過(guò)new Unsafe()或調(diào)用Unsafe.getUnsafe()獲取,原因如下:
- 不能直接new Unsafe(),原因是Unsafe被設(shè)計(jì)成單例模式,構(gòu)造方法是私有的;
- 不能通過(guò)調(diào)用Unsafe.getUnsafe()獲取,因?yàn)間etUnsafe被設(shè)計(jì)成只能從引導(dǎo)類加載器(bootstrap class loader)加載
@CallerSensitive public static Unsafe getUnsafe() { Class var0 = Reflection.getCallerClass(); if (!VM.isSystemDomainLoader(var0.getClassLoader())) { throw new SecurityException("Unsafe"); } else { return theUnsafe; } }
獲取實(shí)例
//方法一:我們可以令我們的代碼“受信任”。運(yùn)行程序時(shí),使用bootclasspath選項(xiàng),指定系統(tǒng)類路徑加上你使用的一個(gè)Unsafe路徑 java -Xbootclasspath:/usr/jdk1.7.0/jre/lib/rt.jar:. com.mishadoff.magic.UnsafeClient // 方法二 static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); UNSAFE = (Unsafe) field.get(null); } catch (Exception e) { } }
注意:忽略你的IDE。比如:eclipse顯示”Access restriction…”錯(cuò)誤,但如果你運(yùn)行代碼,它將正常運(yùn)行。如果這個(gè)錯(cuò)誤提示令人煩惱,可以通過(guò)以下設(shè)置來(lái)避免:
Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated and restricted API -> Forbidden reference -> Warning
重點(diǎn)API
- allocateInstance(Class<?> var1)不調(diào)用構(gòu)造方法生成對(duì)象
User instance = (User) UNSAFE.allocateInstance(User.class);
- objectFieldOffset(Field var1)返回成員屬性在內(nèi)存中的地址相對(duì)于對(duì)象內(nèi)存地址的偏移量
- putLong,putInt,putDouble,putChar,putObject等方法,直接修改內(nèi)存數(shù)據(jù)(可以越過(guò)訪問(wèn)權(quán)限)
package com.quancheng; import sun.misc.Unsafe; import java.lang.reflect.Field; public class CollectionApp { private static sun.misc.Unsafe UNSAFE; public static void main(String[] args) { try { User instance = (User) UNSAFE.allocateInstance(User.class); instance.setName("luoyoub"); System.err.println("instance:" + instance); instance.test(); Field name = instance.getClass().getDeclaredField("name"); UNSAFE.putObject(instance, UNSAFE.objectFieldOffset(name), "huanghui"); instance.test(); } catch (Exception e) { e.printStackTrace(); } } static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); UNSAFE = (Unsafe) field.get(null); } catch (Exception e) { } } } class User { private String name; public void setName(String name) { this.name = name; } public void test() { System.err.println("hello,world" + name); } }
copyMemory
:內(nèi)存數(shù)據(jù)拷貝freeMemory
:用于釋放allocateMemory和reallocateMemory申請(qǐng)的內(nèi)存compareAndSwapInt
/compareAndSwapLongCAS
操作getLongVolatile
/putLongVolatile
使用場(chǎng)景
避免初始化
當(dāng)你想要跳過(guò)對(duì)象初始化階段,或繞過(guò)構(gòu)造器的安全檢查,或?qū)嵗粋€(gè)沒(méi)有任何公共構(gòu)造器的類,allocateInstance方法是非常有用的,使用構(gòu)造器、反射和unsafe初始化它,將得到不同的結(jié)果
public class CollectionApp { private static sun.misc.Unsafe UNSAFE; public static void main(String[] args) throws IllegalAccessException, InstantiationException { A a = new A(); a.test(); // output ==> 1 A a1 = A.class.newInstance(); a1.test(); // output ==> 1 A instance = (A) UNSAFE.allocateInstance(A.class); instance.test(); // output ==> 0 } static { try { Field field = Unsafe.class.getDeclaredField("theUnsafe"); field.setAccessible(true); UNSAFE = (Unsafe) field.get(null); } catch (Exception e) { } } } class A{ private long a; public A(){ a = 1; } public void test(){ System.err.println("a==>" + a); } }
內(nèi)存崩潰(Memory corruption)
Unsafe可用于繞過(guò)安全的常用技術(shù),直接修改內(nèi)存變量;實(shí)際上,反射可以實(shí)現(xiàn)相同的功能。但值得關(guān)注的是,我們可以修改任何對(duì)象,甚至沒(méi)有這些對(duì)象的引用
Guard guard = new Guard(); guard.giveAccess(); // false, no access // bypass Unsafe unsafe = getUnsafe(); Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED"); unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption guard.giveAccess(); // true, access granted
注意:我們不必持有這個(gè)對(duì)象的引用
- 淺拷貝(Shallow copy)
- 動(dòng)態(tài)類(Dynamic classes)
我們可以在運(yùn)行時(shí)創(chuàng)建一個(gè)類,比如從已編譯的.class文件中。將類內(nèi)容讀取為字節(jié)數(shù)組,并正確地傳遞給defineClass方法;當(dāng)你必須動(dòng)態(tài)創(chuàng)建類,而現(xiàn)有代碼中有一些代理, 這是很有用的
private static byte[] getClassContent() throws Exception { File f = new File("/home/mishadoff/tmp/A.class"); FileInputStream input = new FileInputStream(f); byte[] content = new byte[(int)f.length()]; input.read(content); input.close(); return content; } byte[] classContents = getClassContent(); Class c = getUnsafe().defineClass( null, classContents, 0, classContents.length); c.getMethod("a").invoke(c.newInstance(), null); // 1
拋出異常(Throw an Exception)
該方法拋出受檢異常,但你的代碼不必捕捉或重新拋出它,正如運(yùn)行時(shí)異常一樣
getUnsafe().throwException(new IOException());
大數(shù)組(Big Arrays)
正如你所知,Java數(shù)組大小的最大值為Integer.MAX_VALUE。使用直接內(nèi)存分配,我們創(chuàng)建的數(shù)組大小受限于堆大小;實(shí)際上,這是堆外內(nèi)存(off-heap memory)技術(shù),在java.nio包中部分可用;
這種方式的內(nèi)存分配不在堆上,且不受GC管理,所以必須小心Unsafe.freeMemory()的使用。它也不執(zhí)行任何邊界檢查,所以任何非法訪問(wèn)可能會(huì)導(dǎo)致JVM崩潰
class SuperArray { private final static int BYTE = 1; private long size; private long address; public SuperArray(long size) { this.size = size; address = getUnsafe().allocateMemory(size * BYTE); } public void set(long i, byte value) { getUnsafe().putByte(address + i * BYTE, value); } public int get(long idx) { return getUnsafe().getByte(address + idx * BYTE); } public long size() { return size; } } long SUPER_SIZE = (long)Integer.MAX_VALUE * 2; SuperArray array = new SuperArray(SUPER_SIZE); System.out.println("Array size:" + array.size()); // 4294967294 for (int i = 0; i < 100; i++) { array.set((long)Integer.MAX_VALUE + i, (byte)3); sum += array.get((long)Integer.MAX_VALUE + i); } System.out.println("Sum of 100 elements:" + sum); // 300
并發(fā)(Concurrency)
幾句關(guān)于Unsafe的并發(fā)性。compareAndSwap方法是原子的,并且可用來(lái)實(shí)現(xiàn)高性能的、無(wú)鎖的數(shù)據(jù)結(jié)構(gòu)
掛起與恢復(fù)
定義:
public native void unpark(Thread jthread); public native void park(boolean isAbsolute, long time); // isAbsolute參數(shù)是指明時(shí)間是絕對(duì)的,還是相對(duì)的
將一個(gè)線程進(jìn)行掛起是通過(guò)park方法實(shí)現(xiàn)的,調(diào)用park后,線程將一直阻塞直到超時(shí)或者中斷等條件出現(xiàn)。unpark可以終止一個(gè)掛起的線程,使其恢復(fù)正常。整個(gè)并發(fā)框架中對(duì)線程的掛起操作被封裝在 LockSupport類中,LockSupport類中有各種版本pack方法,但最終都調(diào)用了Unsafe.park()方法;
unpark函數(shù)為線程提供“許可(permit)”,線程調(diào)用park函數(shù)則等待“許可”。這個(gè)有點(diǎn)像信號(hào)量,但是這個(gè)“許可”是不能疊加的,“許可”是一次性的;比如線程B連續(xù)調(diào)用了三次unpark函數(shù),當(dāng)線程A調(diào)用park函數(shù)就使用掉這個(gè)“許可”,如果線程A再次調(diào)用park,則進(jìn)入等待狀態(tài),見下例Example1
Example1: // 針對(duì)當(dāng)前線程已經(jīng)調(diào)用過(guò)unpark(多次調(diào)用unpark的效果和調(diào)用一次unpark的效果一樣) public static void main(String[] args) throws InterruptedException { Thread currThread = Thread.currentThread(); UNSAFE.unpark(currThread); UNSAFE.unpark(currThread); UNSAFE.unpark(currThread); UNSAFE.park(false, 0); UNSAFE.park(false, 0); System.out.println("SUCCESS!!!"); } // 恢復(fù)線程interrupt() && UNSAFE.unpark()運(yùn)行結(jié)果一樣 public static void main(String[] args) throws InterruptedException { Thread currThread = Thread.currentThread(); new Thread(()->{ try { Thread.sleep(3000); System.err.println("sub thread end"); // currThread.interrupt(); UNSAFE.unpark(currThread); } catch (Exception e) { e.printStackTrace(); } }).start(); UNSAFE.park(false, 0); System.out.println("SUCCESS!!!"); } // 如果是相對(duì)時(shí)間也就是isAbsolute為false(注意這里后面的單位納秒)到期的時(shí)候,與Thread.sleep效果相同,具體有什么區(qū)別有待深入研究 //相對(duì)時(shí)間后面的參數(shù)單位是納秒 UNSAFE.park(false, 3000000000l); System.out.println("SUCCESS!!!"); long time = System.currentTimeMillis()+3000; //絕對(duì)時(shí)間后面的參數(shù)單位是毫秒 UNSAFE.park(true, time); System.out.println("SUCCESS!!!");
注意,unpark函數(shù)可以先于park調(diào)用。比如線程B調(diào)用unpark函數(shù),給線程A發(fā)了一個(gè)“許可”,那么當(dāng)線程A調(diào)用park時(shí),它發(fā)現(xiàn)已經(jīng)有“許可”了,那么它會(huì)馬上再繼續(xù)運(yùn)行。實(shí)際上,park函數(shù)即使沒(méi)有“許可”,有時(shí)也會(huì)無(wú)理由地返回,實(shí)際上在SUN Jdk中,object.wait()也有可能被假喚醒;
注意:unpark方法最好不要在調(diào)用park前對(duì)當(dāng)前線程調(diào)用unpark
Unsafe API
sun.misc.Unsafe類包含105個(gè)方法。實(shí)際上,對(duì)各種實(shí)體操作有幾組重要方法,其中的一些如下: Info.僅返回一些低級(jí)的內(nèi)存信息 addressSize pageSize Objects.提供用于操作對(duì)象及其字段的方法 allocateInstance ##直接獲取對(duì)象實(shí)例 objectFieldOffset Classes.提供用于操作類及其靜態(tài)字段的方法 staticFieldOffset defineClass defineAnonymousClass ensureClassInitialized Arrays.操作數(shù)組 arrayBaseOffset arrayIndexScale Synchronization.低級(jí)的同步原語(yǔ) monitorEnter tryMonitorEnter monitorExit compareAndSwapInt putOrderedInt Memory.直接內(nèi)存訪問(wèn)方法 allocateMemory copyMemory freeMemory getAddress getInt putInt
知識(shí)點(diǎn)
Unsafe.park()當(dāng)遇到線程終止時(shí),會(huì)直接返回(不同于Thread.sleep,Thread.sleep遇到thread.interrupt()會(huì)拋異常)
// Thread.sleep會(huì)拋異常 public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ try { System.err.println("sub thread start"); Thread.sleep(10000); System.err.println("sub thread end"); } catch (InterruptedException e) { e.printStackTrace(); } }); thread.start(); TimeUnit.SECONDS.sleep(3); thread.interrupt(); System.out.println("SUCCESS!!!"); } output==> sub thread start SUCCESS!!! java.lang.InterruptedException: sleep interrupted at java.lang.Thread.sleep(Native Method) at com.quancheng.ConcurrentTest.lambda$main$0(ConcurrentTest.java:13) at java.lang.Thread.run(Thread.java:745) Process finished with exit code 0 public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(()->{ System.err.println("sub thread start"); UNSAFE.park(false,0); System.err.println("sub thread end"); }); thread.start(); TimeUnit.SECONDS.sleep(3); UNSAFE.unpark(thread); System.out.println("SUCCESS!!!"); } output==> sub thread start sub thread end SUCCESS!!! Process finished with exit code 0
unpark無(wú)法恢復(fù)處于sleep中的線程,只能與park配對(duì)使用,因?yàn)閡npark發(fā)放的許可只有park能監(jiān)聽到
public static void main(String[] args) throws InterruptedException { Thread thread = new Thread(() -> { try { System.err.println("sub thread start"); TimeUnit.SECONDS.sleep(10); System.err.println("sub thread end"); } catch (Exception e) { e.printStackTrace(); } }); thread.start(); TimeUnit.SECONDS.sleep(3); UNSAFE.unpark(thread); System.out.println("SUCCESS!!!"); }
park和unpark的靈活之處
上面已經(jīng)提到,unpark函數(shù)可以先于park調(diào)用,這個(gè)正是它們的靈活之處。
一個(gè)線程它有可能在別的線程unPark之前,或者之后,或者同時(shí)調(diào)用了park,那么因?yàn)閜ark的特性,它可以不用擔(dān)心自己的park的時(shí)序問(wèn)題,否則,如果park必須要在unpark之前,那么給編程帶來(lái)很大的麻煩??!
”考慮一下,兩個(gè)線程同步,要如何處理?
在Java5里是用wait/notify/notifyAll來(lái)同步的。wait/notify機(jī)制有個(gè)很蛋疼的地方是,比如線程B要用notify通知線程A,那么線程B要確保線程A已經(jīng)在wait調(diào)用上等待了,否則線程A可能永遠(yuǎn)都在等待。編程的時(shí)候就會(huì)很蛋疼。
另外,是調(diào)用notify,還是notifyAll?
notify只會(huì)喚醒一個(gè)線程,如果錯(cuò)誤地有兩個(gè)線程在同一個(gè)對(duì)象上wait等待,那么又悲劇了。為了安全起見,貌似只能調(diào)用notifyAll了“
park/unpark模型真正解耦了線程之間的同步,線程之間不再需要一個(gè)Object或者其它變量來(lái)存儲(chǔ)狀態(tài),不再需要關(guān)心對(duì)方的狀態(tài)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java躲不過(guò)設(shè)計(jì)模式的坑之代理模式詳解
設(shè)計(jì)模式看來(lái)更像是一種設(shè)計(jì)思維或設(shè)計(jì)思想,為你的項(xiàng)目工程提供方向,讓你的項(xiàng)目工程更加健壯、靈活,延續(xù)生命力。本文即將分享的是設(shè)計(jì)模式的其中一種:代理模式,感興趣的可以了解一下2022-09-09Mybatis如何通過(guò)接口實(shí)現(xiàn)sql執(zhí)行原理解析
為了簡(jiǎn)化MyBatis的使用,MyBatis提供了接口方式自動(dòng)化生成調(diào)用過(guò)程,可以大大簡(jiǎn)化MyBatis的開發(fā),下面這篇文章主要給大家介紹了關(guān)于Mybatis如何通過(guò)接口實(shí)現(xiàn)sql執(zhí)行原理解析的相關(guān)資料,需要的朋友可以參考下2023-01-01Java中Elasticsearch 實(shí)現(xiàn)分頁(yè)方式(三種方式)
Elasticsearch是用Java語(yǔ)言開發(fā)的,并作為Apache許可條款下的開放源碼發(fā)布,是一種流行的企業(yè)級(jí)搜索引擎,這篇文章主要介紹了Elasticsearch實(shí)現(xiàn)分頁(yè)的3種方式,需要的朋友可以參考下2022-07-07springboot不同環(huán)境使用不同配置文件打包方式
這篇文章主要介紹了springboot不同環(huán)境使用不同配置文件打包方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-11-11Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為實(shí)例分析
這篇文章主要介紹了Java中構(gòu)造器內(nèi)部的多態(tài)方法的行為,結(jié)合實(shí)例形式分析了java構(gòu)造器內(nèi)部多態(tài)方法相關(guān)原理、功能及操作技巧,需要的朋友可以參考下2019-10-10