這個帖子是關於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 reference f.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("l00k@myHor$e"); String fake = new String(password.replaceAll(".", "?")); System.out.println(password); // l00k@myHor$e System.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 classes byte[] classContents = getClassContent(); Class c = getUnsafe().defineClass(null, classContents, 0, classContents.length); c.getMethod("a").invoke(c.newInstance(), null); //Method to read .class file 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; }
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()); // 4294967294 for (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++代碼,或者需要創建即時分析的工具。