sun.misc.unsafe類的使用
這個帖子是關於JAVA中鮮為人知的特性的後續更新,如果想得到下次線上討論的更新,請通過郵件訂閱,並且不要忘了在評論區留下你的意見和建議。
Java是一個安全的開發工具,它阻止開發人員犯很多低階的錯誤,而大部份的錯誤都是基於記憶體管理方面的。如果你想搞破壞,可以使用Unsafe這個類。這個類是屬於sun.* API中的類,並且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文件,更可悲的是,它也沒有比較好的程式碼文件。
例項化sun.misc.Unsafe
如果你嘗試建立Unsafe類的例項,基於以下兩種原因是不被允許的。
1)、Unsafe類的建構函式是私有的;
2)、雖然它有靜態的getUnsafe()方法,但是如果你嘗試呼叫Unsafe.getUnsafe(),會得到一個SecutiryException。這個類只有被JDK信任的類例項化。
但是這總會是有變通的解決辦法的,一個簡單的方式就是使用反射進行例項化:
Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal referencef.setAccessible(true);Unsafe unsafe = (Unsafe) f.get(null);
注:IDE如Eclipse對會這樣的使用報錯,不過不用擔心,直接執行程式碼就行,可以正常執行的。(譯者注:還有一種解決方案,就是將Eclipse中這種限制獲取由錯誤,修改為警告,具體操作為將Windows->Preference...->Java->Compiler->Errors/Warnings中的"Deprecated and restricted API",級別由Error修改為Warning就可以了)
現在進入主題,使用這個物件我們可以做如下“有趣的”事情。
使用sun.misc.Unsafe
1)、突破限制建立例項
通過allocateInstance()方法,你可以建立一個類的例項,但是卻不需要呼叫它的建構函式、初使化程式碼、各種JVM安全檢查以及其它的一些底層的東西。即使建構函式是私有,我們也可以通過這個方法建立它的例項。
(這個對單例模式情有獨鍾的程式設計師來說將會是一個噩夢,它們沒有辦法阻止這種方式呼叫)
看下面一個例項(注:為了配合這個主題,譯者將原例項中的public建構函式修改為了私有的):
public class UnsafeDemo { public static void main(String[] args) throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, InstantiationException { Field f = Unsafe.class.getDeclaredField("theUnsafe"); // Internal reference f.setAccessible(true); Unsafe unsafe = (Unsafe) f.get(null); // This creates an instance of player class without any initialization Player p = (Player) unsafe.allocateInstance(Player.class); System.out.println(p.getAge()); // Print 0 p.setAge(45); // Let's now set age 45 to un-initialized object System.out.println(p.getAge()); // Print 45 }}class Player { private int age = 12; private Player() { this.age = 50; } public int getAge() { return this.age; } public void setAge(int age) { this.age = age; }}
2)、使用直接獲取記憶體的方式實現淺克隆
如何實現淺克隆?在clone(){...}方法中呼叫super.clone(),對嗎?這裡存在的問題是首先你必須繼續Cloneable介面,並且在所有你需要做淺克隆的物件中實現clone()方法,對於一個懶懶的程式設計師來說,這個工作量太大了。
我不推薦上面的做法而是直接使用Unsafe,我們可以僅使用幾行程式碼就實現淺克隆,並且它可以像某些工具類一樣用於任意類的克隆。
這個戲法就是把一個物件的位元組碼拷貝到記憶體的另外一個地方,然後再將這個物件轉換為被克隆的物件型別。
3)、來自黑客的密碼安全
這個好似很有趣吧?實事就是這樣的。開發人員建立密碼或者是保證密碼到字串中,然後在應用程式的程式碼中使用這些密碼,使用過後,聰明的程式設計師會把字串的引用設為NULL,因此它就不會被引用著並且很容易被垃圾收集器給回收掉。
但是從你將引用設為NULL到被垃圾收集器收集的這個時間段之內(原文:But from the time, you made the reference null to the time garbage collector kicks in),它是處於字串池中的,並且在你係統中進行一個複雜的攻擊(原文:And a sophisticated attack on your system),也是可以讀取到你的記憶體區域並且獲得密碼,雖然機會很小,但是總是存在的。
這就是為什麼建議使用char[]陣列存放密碼,當使用完過後,你可以迭代處理當前陣列,修改/清空這些字元。
另外一個方式就是使用魔術類Unsafe。你可以建立另外一個和當前密碼字串具有相同長度的臨時字串,將臨時密碼中的每個字元都設值為"?"或者"*"(任何字元都可以),當你完成密碼的邏輯後,你只需要簡單的將臨時密碼中的位元組陣列拷貝到原始的密碼串中,這就是使用臨時密碼覆蓋真實的密碼。
示例程式碼可能會是這樣:
String password = new String("[email protected]$e");String fake = new String(password.replaceAll(".", "?"));System.out.println(password); // [email protected]$eSystem.out.println(fake); // ???????????? getUnsafe().copyMemory(fake, 0L, null, toAddress(password), sizeOf(password)); System.out.println(password); // ????????????System.out.println(fake); // ????????????
執行時動態建立類我們可以在執行時運態的建立類,例如通過編譯後的.class檔案,操作方式就是將.class檔案讀取到位元組資料組中,並將其傳到defineClass方法中。
//Sample code to craeet classesbyte[] classContents = getClassContent();Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length);c.getMethod("a").invoke(c.newInstance(), null); //Method to read .class fileprivate 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;}
4)、超大陣列
從所周知,常量Integer.MAX_VALUE是JAVA中陣列長度的最大值,如果你想建立一個非常大的陣列(雖然在通常的應用中不可能會用上),可以通過對記憶體進行直接分配實現。
下面這個示例將會建立分配一段連續的記憶體(陣列),它的容易是允許最大容量的兩倍。
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()); // 4294967294for (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
但請注意這可能會導致JVM掛掉。結束語
sun.misc.Unsafe provides almost unlimited capabilities for exploring and modification of VM’s runtime data structures. Despite the fact that these capabilities are almost inapplicable in Java development itself, Unsafe is a great tool for anyone who want to study HotSpot VM without C++ code debugging or need to create ad hoc profiling instruments.
sun.misc.Unsafe提供了可以隨意檢視及修改JVM中執行時的資料結構,儘管這些功能在JAVA開發本身是不適用的,Unsafe是一個用於研究學習HotSpot虛擬機器非常棒的工具,因為它不需要呼叫C++程式碼,或者需要建立即時分析的工具。
參考