关于Unsafe类的一点研究
轉載自??關于Unsafe類的一點研究
Unsafe類是java中非常特別的一個類。它名字就叫做“不安全”,提供的操作可以直接讀寫內存、獲得地址偏移值、鎖定或釋放線程。
通過正常途徑是無法獲得Unsafe實例的,首先它的構造方法是私有的,然后,即使你調用它的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.
任何關于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,沒有發現可以直接獲取對象地址的方法,Unsafe中操作地址相關的方法都要求提供一個Object類型的參數,用來獲取對象的初始地址。
修改和讀取數組中的值
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+“ ”);}打印結果:1 2 3 4 5 6 7 8 9 1 ,可以看到,成功讀出了數組中的值,而且最后一個值由10改為了1。
-
arrayBaseOffset: 返回當前數組第一個元素地址相對于數組起始地址的偏移值,在本例中返回6。
-
arrayIndexScale: 返回當前數組一個元素占用的字節數,在本例中返回4。
-
putInt(obj,offset,intval): 獲取數組對象obj的起始地址,加上偏移值,得到對應元素的地址,將intval寫入內存。
-
getInt(obj,offset): 獲取數組對象obj的起始地址,加上偏移值,得到對應元素的地址,從而獲得元素的值。
-
偏移值: 數組元素偏移值 = 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: 在不執行構造方法的前提下,獲取一個類的實例,即使這個類的構造方法是私有的。
修改靜態變量和實例變量的值
先定義一個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”,說明構造方法沒有調用。
修改實例變量與修改數組的值類似,同樣要獲取地址偏移值,然后調用putInt方法。
- objectFieldOffset: 獲取對象某個屬性的地址偏移值。
我們通過Unsafe類修改了Java堆中的數據。
修改Test類的靜態變量
Field staticIntField = Test.class.getDeclaredField("staticIntField");Object o = u.staticFieldBase(staticIntField);System.out.prinln(o==Test.class);Long b4 = u.staticFieldOffset(staticIntField); //因為是靜態變量,傳入的Object參數應為class對象 u.putInt(o, b4, 10);System.out.println("staticIntField:"+u.getInt(Test.class, b4));打印結果:
true
staticIntField:10
靜態變量與實例變量不同之處在于,靜態變量位于于方法區中,它的地址偏移值與Test類在方法區的地址相關,與Test類的實例無關。
-
staticFieldBase: 獲取靜態變量所屬的類在方法區的首地址。可以看到,返回的對象就是Test.class。
-
staticFieldOffset: 獲取靜態變量地址偏移值。
我們通過Unsafe類修改了方法區中的信息。
調戲String.intern
在jdk7中,String.intern不再拷貝string對象實例,而是保存第一次出現的對象的引用。在下面的代碼中,通過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);打印結果:
s:acc s1:acc
s:acc s1:acc s2:acc s3:acc
我們發現了什么?所有值為“abc”的字符串都變成了“acc”!!!
Unsafe類果然不安全!!!
?
總結
以上是生活随笔為你收集整理的关于Unsafe类的一点研究的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 在Word文档中加入大写的拼音标注wor
- 下一篇: 这个应用也能帮你跳过开屏广告跳过开屏广告