国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁(yè) > 開(kāi)發(fā) > Java > 正文

Java中的魔法類(lèi):sun.misc.Unsafe示例詳解

2024-07-14 08:41:00
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

前言

Unsafe類(lèi)在jdk 源碼的多個(gè)類(lèi)中用到,這個(gè)類(lèi)的提供了一些繞開(kāi)JVM的更底層功能,基于它的實(shí)現(xiàn)可以提高效率。但是,它是一把雙刃劍:正如它的名字所預(yù)示的那樣,它是Unsafe的,它所分配的內(nèi)存需要手動(dòng)free(不被GC回收)。Unsafe類(lèi),提供了JNI某些功能的簡(jiǎn)單替代:確保高效性的同時(shí),使事情變得更簡(jiǎn)單。

這個(gè)類(lèi)是屬于sun.* API中的類(lèi),并且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文檔,更可悲的是,它也沒(méi)有比較好的代碼文檔。

這篇文章主要是以下文章的整理、翻譯。

http://mishadoff.com/blog/javascript/48841.html">java-magic-part-4-sun-dot-misc-dot-unsafe/

1. Unsafe API的大部分方法都是native實(shí)現(xiàn),它由105個(gè)方法組成,主要包括以下幾類(lèi):

(1)Info相關(guān)。主要返回某些低級(jí)別的內(nèi)存信息:addressSize(), pageSize()

(2)Objects相關(guān)。主要提供Object和它的域操縱方法:allocateInstance(),objectFieldOffset()

(3)Class相關(guān)。主要提供Class和它的靜態(tài)域操縱方法:staticFieldOffset(),defineClass(),defineAnonymousClass(),ensureClassInitialized()

(4)Arrays相關(guān)。數(shù)組操縱方法:arrayBaseOffset(),arrayIndexScale()

(5)Synchronization相關(guān)。主要提供低級(jí)別同步原語(yǔ)(如基于CPU的CAS(Compare-And-Swap)原語(yǔ)):monitorEnter(),tryMonitorEnter(),monitorExit(),compareAndSwapInt(),putOrderedInt()

(6)Memory相關(guān)。直接內(nèi)存訪問(wèn)方法(繞過(guò)JVM堆直接操縱本地內(nèi)存):allocateMemory(),copyMemory(),freeMemory(),getAddress(),getInt(),putInt()

2. Unsafe類(lèi)實(shí)例的獲取

Unsafe類(lèi)設(shè)計(jì)只提供給JVM信任的啟動(dòng)類(lèi)加載器所使用,是一個(gè)典型的單例模式類(lèi)。它的實(shí)例獲取方法如下:

public static Unsafe getUnsafe() { Class cc = sun.reflect.Reflection.getCallerClass(2); if (cc.getClassLoader() != null)  throw new SecurityException("Unsafe"); return theUnsafe;}

非啟動(dòng)類(lèi)加載器直接調(diào)用Unsafe.getUnsafe()方法會(huì)拋出SecurityException(具體原因涉及JVM類(lèi)的雙親加載機(jī)制)。

解決辦法有兩個(gè),其一是通過(guò)JVM參數(shù)-Xbootclasspath指定要使用的類(lèi)為啟動(dòng)類(lèi),另外一個(gè)辦法就是java反射了。

Field f = Unsafe.class.getDeclaredField("theUnsafe");f.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);

通過(guò)將private單例實(shí)例暴力設(shè)置accessible為true,然后通過(guò)Field的get方法,直接獲取一個(gè)Object強(qiáng)制轉(zhuǎn)換為Unsafe。在IDE中,這些方法會(huì)被標(biāo)志為Error,可以通過(guò)以下設(shè)置解決:

Preferences -> Java -> Compiler -> Errors/Warnings ->Deprecated and restricted API -> Forbidden reference -> Warning

3. Unsafe類(lèi)“有趣”的應(yīng)用場(chǎng)景

(1)繞過(guò)類(lèi)初始化方法。當(dāng)你想要繞過(guò)對(duì)象構(gòu)造方法、安全檢查器或者沒(méi)有public的構(gòu)造方法時(shí),allocateInstance()方法變得非常有用。

class A { private long a; // not initialized value public A() {  this.a = 1; // initialization } public long a() { return this.a; }}

以下是構(gòu)造方法、反射方法和allocateInstance()的對(duì)照

A o1 = new A(); // constructoro1.a(); // prints 1 A o2 = A.class.newInstance(); // reflectiono2.a(); // prints 1 A o3 = (A) unsafe.allocateInstance(A.class); // unsafeo3.a(); // prints 0

allocateInstance()根本沒(méi)有進(jìn)入構(gòu)造方法,在單例模式時(shí),我們似乎看到了危機(jī)。

(2)內(nèi)存修改

內(nèi)存修改在c語(yǔ)言中是比較常見(jiàn)的,在Java中,可以用它繞過(guò)安全檢查器。

考慮以下簡(jiǎn)單準(zhǔn)入檢查規(guī)則:

class Guard { private int ACCESS_ALLOWED = 1;  public boolean giveAccess() {  return 42 == ACCESS_ALLOWED; }}

在正常情況下,giveAccess總會(huì)返回false,但事情不總是這樣

Guard guard = new Guard();guard.giveAccess(); // false, no access // bypassUnsafe unsafe = getUnsafe();Field f = guard.getClass().getDeclaredField("ACCESS_ALLOWED");unsafe.putInt(guard, unsafe.objectFieldOffset(f), 42); // memory corruption guard.giveAccess(); // true, access granted

通過(guò)計(jì)算內(nèi)存偏移,并使用putInt()方法,類(lèi)的ACCESS_ALLOWED被修改。在已知類(lèi)結(jié)構(gòu)的時(shí)候,數(shù)據(jù)的偏移總是可以計(jì)算出來(lái)(與c++中的類(lèi)中數(shù)據(jù)的偏移計(jì)算是一致的)。

(3)實(shí)現(xiàn)類(lèi)似C語(yǔ)言的sizeOf()函數(shù)

通過(guò)結(jié)合Java反射和objectFieldOffset()函數(shù)實(shí)現(xiàn)一個(gè)C-like sizeOf()函數(shù)。

public static long sizeOf(Object o) { Unsafe u = getUnsafe(); HashSet fields = new HashSet(); Class c = o.getClass(); while (c != Object.class) {  for (Field f : c.getDeclaredFields()) {   if ((f.getModifiers() & Modifier.STATIC) == 0) {    fields.add(f);   }  }  c = c.getSuperclass(); }  // get offset long maxSize = 0; for (Field f : fields) {  long offset = u.objectFieldOffset(f);  if (offset > maxSize) {   maxSize = offset;  } } return ((maxSize/8) + 1) * 8; // padding}

算法的思路非常清晰:從底層子類(lèi)開(kāi)始,依次取出它自己和它的所有超類(lèi)的非靜態(tài)域,放置到一個(gè)HashSet中(重復(fù)的只計(jì)算一次,Java是單繼承),然后使用objectFieldOffset()獲得一個(gè)最大偏移,最后還考慮了對(duì)齊。

在32位的JVM中,可以通過(guò)讀取class文件偏移為12的long來(lái)獲取size。

public static long sizeOf(Object object){ return getUnsafe().getAddress(  normalize(getUnsafe().getInt(object, 4L)) + 12L);}

其中normalize()函數(shù)是一個(gè)將有符號(hào)int轉(zhuǎn)為無(wú)符號(hào)long的方法

private static long normalize(int value) { if(value >= 0) return value; return (0L >>> 32) & value;}

兩個(gè)sizeOf()計(jì)算的類(lèi)的尺寸是一致的。最標(biāo)準(zhǔn)的sizeOf()實(shí)現(xiàn)是使用java.lang.instrument,但是,它需要指定命令行參數(shù)-javaagent。

(4)實(shí)現(xiàn)Java淺復(fù)制

標(biāo)準(zhǔn)的淺復(fù)制方案是實(shí)現(xiàn)Cloneable接口或者自己實(shí)現(xiàn)的復(fù)制函數(shù),它們都不是多用途的函數(shù)。通過(guò)結(jié)合sizeOf()方法,可以實(shí)現(xiàn)淺復(fù)制。

static Object shallowCopy(Object obj) { long size = sizeOf(obj); long start = toAddress(obj); long address = getUnsafe().allocateMemory(size); getUnsafe().copyMemory(start, address, size); return fromAddress(address);}

以下的toAddress()和fromAddress()分別將對(duì)象轉(zhuǎn)換到它的地址以及相反操作。

static long toAddress(Object obj) { Object[] array = new Object[] {obj}; long baseOffset = getUnsafe().arrayBaseOffset(Object[].class); return normalize(getUnsafe().getInt(array, baseOffset));} static Object fromAddress(long address) { Object[] array = new Object[] {null}; long baseOffset = getUnsafe().arrayBaseOffset(Object[].class); getUnsafe().putLong(array, baseOffset, address); return array[0];}

以上的淺復(fù)制函數(shù)可以應(yīng)用于任意java對(duì)象,它的尺寸是動(dòng)態(tài)計(jì)算的。

(5)消去內(nèi)存中的密碼

密碼字段存儲(chǔ)在String中,但是,String的回收是受到JVM管理的。最安全的做法是,在密碼字段使用完之后,將它的值覆蓋。

Field stringValue = String.class.getDeclaredField("value");stringValue.setAccessible(true);char[] mem = (char[]) stringValue.get(password);for (int i=0; i < mem.length; i++) { mem[i] = '?';}

(6)動(dòng)態(tài)加載類(lèi)

標(biāo)準(zhǔn)的動(dòng)態(tài)加載類(lèi)的方法是Class.forName()(在編寫(xiě)jdbc程序時(shí),記憶深刻),使用Unsafe也可以動(dòng)態(tài)加載java 的class文件。

byte[] classContents = getClassContent();Class c = getUnsafe().defineClass(    null, classContents, 0, classContents.length); c.getMethod("a").invoke(c.newInstance(), null); // 1getClassContent()方法,將一個(gè)class文件,讀取到一個(gè)byte數(shù)組。 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;}

動(dòng)態(tài)加載、代理、切片等功能中可以應(yīng)用。

(7)包裝受檢異常為運(yùn)行時(shí)異常。

getUnsafe().throwException(new IOException());

當(dāng)你不希望捕獲受檢異常時(shí),可以這樣做(并不推薦)。

(8)快速序列化

標(biāo)準(zhǔn)的java Serializable速度很慢,它還限制類(lèi)必須有public無(wú)參構(gòu)造函數(shù)。Externalizable好些,它需要為要序列化的類(lèi)指定模式。流行的高效序列化庫(kù),比如kryo依賴(lài)于第三方庫(kù),會(huì)增加內(nèi)存的消耗。可以通過(guò)getInt(),getLong(),getObject()等方法獲取類(lèi)中的域的實(shí)際值,將類(lèi)名稱(chēng)等信息一起持久化到文件。kryo有使用Unsafe的嘗試,但是沒(méi)有具體的性能提升的數(shù)據(jù)。(http://code.google.com/p/kryo/issues/detail?id=75)

(9)在非Java堆中分配內(nèi)存

使用java 的new會(huì)在堆中為對(duì)象分配內(nèi)存,并且對(duì)象的生命周期內(nèi),會(huì)被JVM GC管理。

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; }}

Unsafe分配的內(nèi)存,不受Integer.MAX_VALUE的限制,并且分配在非堆內(nèi)存,使用它時(shí),需要非常謹(jǐn)慎:忘記手動(dòng)回收時(shí),會(huì)產(chǎn)生內(nèi)存泄露;非法的地址訪問(wèn)時(shí),會(huì)導(dǎo)致JVM崩潰。在需要分配大的連續(xù)區(qū)域、實(shí)時(shí)編程(不能容忍JVM延遲)時(shí),可以使用它。java.nio使用這一技術(shù)。

(10)Java并發(fā)中的應(yīng)用

通過(guò)使用Unsafe.compareAndSwap()可以用來(lái)實(shí)現(xiàn)高效的無(wú)鎖數(shù)據(jù)結(jié)構(gòu)。

class CASCounter implements Counter { private volatile long counter = 0; private Unsafe unsafe; private long offset; public CASCounter() throws Exception {  unsafe = getUnsafe();  offset = unsafe.objectFieldOffset(CASCounter.class.getDeclaredField("counter")); } @Override public void increment() {  long before = counter;  while (!unsafe.compareAndSwapLong(this, offset, before, before + 1)) {   before = counter;  } } @Override public long getCounter() {  return counter; }}

通過(guò)測(cè)試,以上數(shù)據(jù)結(jié)構(gòu)與java的原子變量的效率基本一致,Java原子變量也使用Unsafe的compareAndSwap()方法,而這個(gè)方法最終會(huì)對(duì)應(yīng)到cpu的對(duì)應(yīng)原語(yǔ),因此,它的效率非常高。這里有一個(gè)實(shí)現(xiàn)無(wú)鎖HashMap的方案(http://www.azulsystems.com/about_us/presentations/lock-free-hash ,這個(gè)方案的思路是:分析各個(gè)狀態(tài),創(chuàng)建拷貝,修改拷貝,使用CAS原語(yǔ),自旋鎖),在普通的服務(wù)器機(jī)器(核心<32),使用ConcurrentHashMap(JDK8以前,默認(rèn)16路分離鎖實(shí)現(xiàn),JDK8中ConcurrentHashMap已經(jīng)使用無(wú)鎖實(shí)現(xiàn))明顯已經(jīng)夠用。

總結(jié)

以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,如果有疑問(wèn)大家可以留言交流,謝謝大家對(duì)VeVb武林網(wǎng)的支持。


注:相關(guān)教程知識(shí)閱讀請(qǐng)移步到JAVA教程頻道。
發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 凤山县| 萨迦县| 老河口市| 扶绥县| 淮南市| 洛宁县| 科技| 拉孜县| 睢宁县| 南溪县| 陇西县| 呼玛县| 合阳县| 额敏县| 工布江达县| 揭西县| 兴业县| 增城市| 清河县| 明星| 黄浦区| 唐河县| 中西区| 乳源| 邹平县| 台湾省| 建德市| 固始县| 沁阳市| 当阳市| 托克逊县| 理塘县| 瓮安县| 枞阳县| 翼城县| 镇安县| 泾源县| 芜湖市| 黔西县| 鲜城| 大理市|