關(guān)于Unsafe類的一點研究


Unsafe類是java中非常特別的一個類。它名字就叫做“不安全”,提供的操作可以直接讀寫內(nèi)存、獲得地址偏移值、鎖定或釋放線程。

通過正常途徑是無法獲得Unsafe實例的,首先它的構(gòu)造方法是私有的,然后,即使你調(diào)用它的getUnsafe方法,也會拋出SecurityException。

A collection of methods for performing low-level, unsafe operations. Although the class and all methods are public, use of this class islimited because only trusted code can obtain instances of it.

任何關(guān)于Unsafe類的文章都不會推薦我們在代碼中使用它,但這并不妨礙我們了解它可以做什么。下面我們來看下利用Unsafe類我們是否可以做點有趣的事情。

獲取Unsafe實例

public static Unsafe getUnsafeInstance() throws Exception{
    Field unsafeStaticField = 
    Unsafe.class.getDeclaredField("theUnsafe");
    unsafeStaticField.setAccessible(true);
    return (Unsafe) unsafeStaticField.get(Unsafe.class);
}

通過java反射機制,我們跳過了安全檢測,拿到了一個unsafe類的實例。

我找遍了Unsafe類的API,沒有發(fā)現(xiàn)可以直接獲取對象地址的方法,Unsafe中操作地址相關(guān)的方法都要求提供一個Object類型的參數(shù),用來獲取對象的初始地址。

修改和讀取數(shù)組中的值

Unsafe u = getUnsafeInstance();

int[] arr = {1,2,3,4,5,6,7,8,9,10};

int b = u.arrayBaseOffset(int[].class);

int s = u.arrayIndexScale(int[].class);

u.putInt(arr, (long)b+s*9, 1);

for(int i=0;i<10;i++){

    int v = u.getInt(arr, (long)b+s*i);

    System.out.print(v+“ ”);

}

打印結(jié)果:1 2 3 4 5 6 7 8 9 1 ,可以看到,成功讀出了數(shù)組中的值,而且最后一個值由10改為了1。

  • arrayBaseOffset: 返回當(dāng)前數(shù)組第一個元素地址相對于數(shù)組起始地址的偏移值,在本例中返回6。

  • arrayIndexScale: 返回當(dāng)前數(shù)組一個元素占用的字節(jié)數(shù),在本例中返回4。

  • putInt(obj,offset,intval): 獲取數(shù)組對象obj的起始地址,加上偏移值,得到對應(yīng)元素的地址,將intval寫入內(nèi)存。

  • getInt(obj,offset): 獲取數(shù)組對象obj的起始地址,加上偏移值,得到對應(yīng)元素的地址,從而獲得元素的值。

  • 偏移值: 數(shù)組元素偏移值 = arrayBaseOffset + arrayIndexScalse * i。

獲取對象實例

/** Allocate an instance but do not run any constructor.
Initializes the class if it has not yet been. */
public native Object allocateInstance(Class cls) throws InstantiationException;

allocateInstance: 在不執(zhí)行構(gòu)造方法的前提下,獲取一個類的實例,即使這個類的構(gòu)造方法是私有的。

修改靜態(tài)變量和實例變量的值

先定義一個Test類

public class Test {
    
    public int intfield ;
    
    public static int staticIntField;
    
    public static int[] arr;
    
    private Test(){
        System.out.println("constructor called");
    }
}

修改Test類的實例變量

Unsafe u = getUnsafeInstance();

Test t = (Test) u.allocateInstance(Test.class);

long b1 = u.objectFieldOffset(Test.class.getDeclaredField("intfield"));

u.putInt(t, b1, 2);

System.out.println("intfield:"+t.intfield);

這里使用allocateInstance方法獲取了一個Test類的實例,并且沒有打印“constructor called”,說明構(gòu)造方法沒有調(diào)用。
修改實例變量與修改數(shù)組的值類似,同樣要獲取地址偏移值,然后調(diào)用putInt方法。

  • objectFieldOffset: 獲取對象某個屬性的地址偏移值。

我們通過Unsafe類修改了Java堆中的數(shù)據(jù)。

修改Test類的靜態(tài)變量

Field staticIntField = Test.class.getDeclaredField("staticIntField");

Object o = u.staticFieldBase(staticIntField);

System.out.prinln(o==Test.class);

Long b4 = u.staticFieldOffset(staticIntField);
//因為是靜態(tài)變量,傳入的Object參數(shù)應(yīng)為class對象
u.putInt(o, b4, 10);

System.out.println("staticIntField:"+u.getInt(Test.class, b4)); 

打印結(jié)果:

true

staticIntField:10

靜態(tài)變量與實例變量不同之處在于,靜態(tài)變量位于于方法區(qū)中,它的地址偏移值與Test類在方法區(qū)的地址相關(guān),與Test類的實例無關(guān)。

  • staticFieldBase: 獲取靜態(tài)變量所屬的類在方法區(qū)的首地址。可以看到,返回的對象就是Test.class。

  • staticFieldOffset: 獲取靜態(tài)變量地址偏移值。

我們通過Unsafe類修改了方法區(qū)中的信息。

調(diào)戲String.intern

在jdk7中,String.intern不再拷貝string對象實例,而是保存第一次出現(xiàn)的對象的引用。在下面的代碼中,通過Unsafe修改被引用對象s的私有屬性value達到間接修改s1的效果!

String s = "abc";

//保存s的引用
s.intern();

//此時s1==s,地址相同
String s1 = "abc";

Unsafe u = getUnsafeInstance();

//獲取s的實例變量value
Field valueInString = String.class.getDeclaredField("value");

//獲取value的變量偏移值
long offset = u.objectFieldOffset(valueInString);

//value本身是一個char[],要修改它元素的值,仍要獲取baseOffset和indexScale
long base = u.arrayBaseOffset(char[].class);

long scale = u.arrayIndexScale(char[].class);

//獲取value
char[] values = (char[]) u.getObject(s, offset);

//為value賦值
u.putChar(values, base + scale, 'c');

System.out.println("s:"+s+" s1:"+s1);

//將s的值改為 abc
s = "abc";

String s2 = "abc";

String s3 = "abc";

System.out.println("s:"+s+" s1:"+s1);

打印結(jié)果:

s:acc s1:acc

s:acc s1:acc s2:acc s3:acc

我們發(fā)現(xiàn)了什么?所有值為“abc”的字符串都變成了“acc”?。?!

Unsafe類果然不安全?。?!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容