這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)如何在Java中使用sun.misc.Unsafe類,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。

1. Unsafe API的大部分方法都是native實現(xiàn),它由105個方法組成,主要包括以下幾類:
(1)Info相關(guān)。主要返回某些低級別的內(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)。主要提供低級別同步原語(如基于CPU的CAS(Compare-And-Swap)原語):monitorEnter(),tryMonitorEnter(),monitorExit(),compareAndSwapInt(),putOrderedInt()
(6)Memory相關(guān)。直接內(nèi)存訪問方法(繞過JVM堆直接操縱本地內(nèi)存):allocateMemory(),copyMemory(),freeMemory(),getAddress(),getInt(),putInt()
2. Unsafe類實例的獲取
Unsafe類設(shè)計只提供給JVM信任的啟動類加載器所使用,是一個典型的單例模式類。它的實例獲取方法如下:
public static Unsafe getUnsafe() {
Class cc = sun.reflect.Reflection.getCallerClass(2);
if (cc.getClassLoader() != null)
throw new SecurityException("Unsafe");
return theUnsafe;
}非啟動類加載器直接調(diào)用Unsafe.getUnsafe()方法會拋出SecurityException(具體原因涉及JVM類的雙親加載機制)。
解決辦法有兩個,其一是通過JVM參數(shù)-Xbootclasspath指定要使用的類為啟動類,另外一個辦法就是java反射了。
Field f = Unsafe.class.getDeclaredField("theUnsafe");
f.setAccessible(true);
Unsafe unsafe = (Unsafe) f.get(null);通過將private單例實例暴力設(shè)置accessible為true,然后通過Field的get方法,直接獲取一個Object強制轉(zhuǎn)換為Unsafe。在IDE中,這些方法會被標(biāo)志為Error,可以通過以下設(shè)置解決:
Preferences -> Java -> Compiler -> Errors/Warnings -> Deprecated and restricted API -> Forbidden reference -> Warning
3. Unsafe類“有趣”的應(yīng)用場景
(1)繞過類初始化方法。當(dāng)你想要繞過對象構(gòu)造方法、安全檢查器或者沒有public的構(gòu)造方法時,allocateInstance()方法變得非常有用。
class A {
private long a; // not initialized value
public A() {
this.a = 1; // initialization
}
public long a() { return this.a; }
}以下是構(gòu)造方法、反射方法和allocateInstance()的對照
A o1 = new A(); // constructor o1.a(); // prints 1 A o2 = A.class.newInstance(); // reflection o2.a(); // prints 1 A o3 = (A) unsafe.allocateInstance(A.class); // unsafe o3.a(); // prints 0
allocateInstance()根本沒有進入構(gòu)造方法,在單例模式時,我們似乎看到了危機。
(2)內(nèi)存修改
內(nèi)存修改在c語言中是比較常見的,在Java中,可以用它繞過安全檢查器。
考慮以下簡單準(zhǔn)入檢查規(guī)則:
class Guard {
private int ACCESS_ALLOWED = 1;
public boolean giveAccess() {
return 42 == ACCESS_ALLOWED;
}
}在正常情況下,giveAccess總會返回false,但事情不總是這樣
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通過計算內(nèi)存偏移,并使用putInt()方法,類的ACCESS_ALLOWED被修改。在已知類結(jié)構(gòu)的時候,數(shù)據(jù)的偏移總是可以計算出來(與c++中的類中數(shù)據(jù)的偏移計算是一致的)。
(3)實現(xiàn)類似C語言的sizeOf()函數(shù)
通過結(jié)合Java反射和objectFieldOffset()函數(shù)實現(xiàn)一個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
}算法的思路非常清晰:從底層子類開始,依次取出它自己和它的所有超類的非靜態(tài)域,放置到一個HashSet中(重復(fù)的只計算一次,Java是單繼承),然后使用objectFieldOffset()獲得一個大偏移,最后還考慮了對齊。
在32位的JVM中,可以通過讀取class文件偏移為12的long來獲取size。
public static long sizeOf(Object object){
return getUnsafe().getAddress(
normalize(getUnsafe().getInt(object, 4L)) + 12L);
}其中normalize()函數(shù)是一個將有符號int轉(zhuǎn)為無符號long的方法
private static long normalize(int value) {
if(value >= 0) return value;
return (0L >>> 32) & value;
}兩個sizeOf()計算的類的尺寸是一致的。最標(biāo)準(zhǔn)的sizeOf()實現(xiàn)是使用java.lang.instrument,但是,它需要指定命令行參數(shù)-javaagent。
(4)實現(xiàn)Java淺復(fù)制
標(biāo)準(zhǔn)的淺復(fù)制方案是實現(xiàn)Cloneable接口或者自己實現(xiàn)的復(fù)制函數(shù),它們都不是多用途的函數(shù)。通過結(jié)合sizeOf()方法,可以實現(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()分別將對象轉(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對象,它的尺寸是動態(tài)計算的。
(5)消去內(nèi)存中的密碼
密碼字段存儲在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)動態(tài)加載類
標(biāo)準(zhǔn)的動態(tài)加載類的方法是Class.forName()(在編寫jdbc程序時,記憶深刻),使用Unsafe也可以動態(tài)加載java 的class文件。
byte[] classContents = getClassContent();
Class c = getUnsafe().defineClass(
null, classContents, 0, classContents.length);
c.getMethod("a").invoke(c.newInstance(), null); // 1
getClassContent()方法,將一個class文件,讀取到一個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;
}動態(tài)加載、代理、切片等功能中可以應(yīng)用。
(7)包裝受檢異常為運行時異常。
getUnsafe().throwException(new IOException());
當(dāng)你不希望捕獲受檢異常時,可以這樣做(并不推薦)。
(8)快速序列化
標(biāo)準(zhǔn)的java Serializable速度很慢,它還限制類必須有public無參構(gòu)造函數(shù)。Externalizable好些,它需要為要序列化的類指定模式。流行的高效序列化庫,比如kryo依賴于第三方庫,會增加內(nèi)存的消耗。可以通過getInt(),getLong(),getObject()等方法獲取類中的域的實際值,將類名稱等信息一起持久化到文件。kryo有使用Unsafe的嘗試,但是沒有具體的性能提升的數(shù)據(jù)。(http://code.google.com/p/kryo/issues/detail?id=75)
(9)在非Java堆中分配內(nèi)存
使用java 的new會在堆中為對象分配內(nèi)存,并且對象的生命周期內(nèi),會被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)存,使用它時,需要非常謹(jǐn)慎:忘記手動回收時,會產(chǎn)生內(nèi)存泄露;非法的地址訪問時,會導(dǎo)致JVM崩潰。在需要分配大的連續(xù)區(qū)域、實時編程(不能容忍JVM延遲)時,可以使用它。java.nio使用這一技術(shù)。
(10)Java并發(fā)中的應(yīng)用
通過使用Unsafe.compareAndSwap()可以用來實現(xiàn)高效的無鎖數(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;
}
}通過測試,以上數(shù)據(jù)結(jié)構(gòu)與java的原子變量的效率基本一致,Java原子變量也使用Unsafe的compareAndSwap()方法,而這個方法最終會對應(yīng)到cpu的對應(yīng)原語,因此,它的效率非常高。這里有一個實現(xiàn)無鎖HashMap的方案
上述就是小編為大家分享的如何在Java中使用sun.misc.Unsafe類了,如果剛好有類似的疑惑,不妨參照上述分析進行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)行業(yè)資訊頻道。
本文標(biāo)題:如何在Java中使用sun.misc.Unsafe類-創(chuàng)新互聯(lián)
本文網(wǎng)址:http://www.chinadenli.net/article28/dhsgcp.html
成都網(wǎng)站建設(shè)公司_創(chuàng)新互聯(lián),為您提供響應(yīng)式網(wǎng)站、微信公眾號、服務(wù)器托管、移動網(wǎng)站建設(shè)、全網(wǎng)營銷推廣、網(wǎng)站改版
聲明:本網(wǎng)站發(fā)布的內(nèi)容(圖片、視頻和文字)以用戶投稿、用戶轉(zhuǎn)載內(nèi)容為主,如果涉及侵權(quán)請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網(wǎng)站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內(nèi)容未經(jīng)允許不得轉(zhuǎn)載,或轉(zhuǎn)載時需注明來源: 創(chuàng)新互聯(lián)
猜你還喜歡下面的內(nèi)容