這個(gè)帖子是關(guān)于JAVA中鮮為人知的特性的后續(xù)更新,如果想得到下次在線討論的更新,請(qǐng)通過(guò)郵件訂閱,并且不要忘了在評(píng)論區(qū)留下你的意見(jiàn)和建議。
Java是一個(gè)安全的開(kāi)發(fā)工具,它阻止開(kāi)發(fā)人員犯很多低級(jí)的錯(cuò)誤,而大部份的錯(cuò)誤都是基于內(nèi)存管理方面的。如果你想搞破壞,可以使用Unsafe這個(gè)類。這個(gè)類是屬于sun.* API中的類,并且它不是J2SE中真正的一部份,因此你可能找不到任何的官方文檔,更可悲的是,它也沒(méi)有比較好的代碼文檔。
實(shí)例化sun.misc.Unsafe
如果你嘗試創(chuàng)建Unsafe類的實(shí)例,基于以下兩種原因是不被允許的。
1)、Unsafe類的構(gòu)造函數(shù)是私有的;
2)、雖然它有靜態(tài)的getUnsafe()方法,但是如果你嘗試調(diào)用Unsafe.getUnsafe(),會(huì)得到一個(gè)SecutiryException。這個(gè)類只有被JDK信任的類實(shí)例化。
但是這總會(huì)是有變通的解決辦法的,一個(gè)簡(jiǎn)單的方式就是使用反射進(jìn)行實(shí)例化:
- Field f = Unsafe.class.getDeclaredField("theUnsafe"); //Internal reference
- f.setAccessible(true);
- Unsafe unsafe = (Unsafe) f.get(null);
注:IDE如Eclipse對(duì)會(huì)這樣的使用報(bào)錯(cuò),不過(guò)不用擔(dān)心,直接運(yùn)行代碼就行,可以正常運(yùn)行的。
(譯者注:還有一種解決方案,就是將Eclipse中這種限制獲取由錯(cuò)誤,修改為警告,具體操作為將Windows->Preference...->Java->Compiler->Errors/Warnings中的"Deprecated and restricted API",級(jí)別由Error修改為Warning就可以了)
現(xiàn)在進(jìn)入主題,使用這個(gè)對(duì)象我們可以做如下“有趣的”事情。
使用sun.misc.Unsafe
1)、突破限制創(chuàng)建實(shí)例
通過(guò)allocateInstance()方法,你可以創(chuàng)建一個(gè)類的實(shí)例,但是卻不需要調(diào)用它的構(gòu)造函數(shù)、初使化代碼、各種JVM安全檢查以及其它的一些底層的東西。即使構(gòu)造函數(shù)是私有,我們也可以通過(guò)這個(gè)方法創(chuàng)建它的實(shí)例。
(這個(gè)對(duì)單例模式情有獨(dú)鐘的程序員來(lái)說(shuō)將會(huì)是一個(gè)噩夢(mèng),它們沒(méi)有辦法阻止這種方式調(diào)用
)
看下面一個(gè)實(shí)例(注:為了配合這個(gè)主題,譯者將原實(shí)例中的public構(gòu)造函數(shù)修改為了私有的):
- 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)、使用直接獲取內(nèi)存的方式實(shí)現(xiàn)淺克隆
如何實(shí)現(xiàn)淺克隆?在clone(){...}方法中調(diào)用super.clone(),對(duì)嗎?這里存在的問(wèn)題是首先你必須繼續(xù)Cloneable接口,并且在所有你需要做淺克隆的對(duì)象中實(shí)現(xiàn)clone()方法,對(duì)于一個(gè)懶懶的程序員來(lái)說(shuō),這個(gè)工作量太大了。
我不推薦上面的做法而是直接使用Unsafe,我們可以僅使用幾行代碼就實(shí)現(xiàn)淺克隆,并且它可以像某些工具類一樣用于任意類的克隆。
這個(gè)戲法就是把一個(gè)對(duì)象的字節(jié)碼拷貝到內(nèi)存的另外一個(gè)地方,然后再將這個(gè)對(duì)象轉(zhuǎn)換為被克隆的對(duì)象類型。
3)、來(lái)自黑客的密碼安全
這個(gè)好似很有趣吧?實(shí)事就是這樣的。開(kāi)發(fā)人員創(chuàng)建密碼或者是保證密碼到字符串中,然后在應(yīng)用程序的代碼中使用這些密碼,使用過(guò)后,聰明的程序員會(huì)把字符串的引用設(shè)為NULL,因此它就不會(huì)被引用著并且很容易被垃圾收集器給回收掉。
但是從你將引用設(shè)為NULL到被垃圾收集器收集的這個(gè)時(shí)間段之內(nèi)(原文:But from the time, you made the reference null to the time garbage collector kicks in),它是處于字符串池中的,并且在你系統(tǒng)中進(jìn)行一個(gè)復(fù)雜的攻擊(原文:And a sophisticated attack on your system),也是可以讀取到你的內(nèi)存區(qū)域并且獲得密碼,雖然機(jī)會(huì)很小,但是總是存在的。
這就是為什么建議使用char[]數(shù)組存放密碼,當(dāng)使用完過(guò)后,你可以迭代處理當(dāng)前數(shù)組,修改/清空這些字符。
另外一個(gè)方式就是使用魔術(shù)類Unsafe。你可以創(chuàng)建另外一個(gè)和當(dāng)前密碼字符串具有相同長(zhǎng)度的臨時(shí)字符串,將臨時(shí)密碼中的每個(gè)字符都設(shè)值為"?"或者"*"(任何字符都可以),當(dāng)你完成密碼的邏輯后,你只需要簡(jiǎn)單的將臨時(shí)密碼中的字節(jié)數(shù)組拷貝到原始的密碼串中,這就是使用臨時(shí)密碼覆蓋真實(shí)的密碼。
示例代碼可能會(huì)是這樣:
- 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); // ????????????
運(yùn)行時(shí)動(dòng)態(tài)創(chuàng)建類
我們可以在運(yùn)行時(shí)運(yùn)態(tài)的創(chuàng)建類,例如通過(guò)編譯后的.class文件,操作方式就是將.class文件讀取到字節(jié)數(shù)據(jù)組中,并將其傳到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)、超大數(shù)組
從所周知,常量Integer.MAX_VALUE是JAVA中數(shù)組長(zhǎng)度的最大值,如果你想創(chuàng)建一個(gè)非常大的數(shù)組(雖然在通常的應(yīng)用中不可能會(huì)用上),可以通過(guò)對(duì)內(nèi)存進(jìn)行直接分配實(shí)現(xiàn)。
下面這個(gè)示例將會(huì)創(chuàng)建分配一段連續(xù)的內(nèi)存(數(shù)組),它的容易是允許最大容量的兩倍。
- class SuperArray {
- private final static int BYTE = 1;
- private long size;
- private long address;
-
- public SuperArray(long size) {
- this.size = size;
- //得到分配內(nèi)存的起始地址
- 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;
- }
- }
應(yīng)用示例
- 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
但請(qǐng)注意這可能會(huì)導(dǎo)致JVM掛掉。
結(jié)束語(yǔ)
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中運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu),盡管這些功能在JAVA開(kāi)發(fā)本身是不適用的,Unsafe是一個(gè)用于研究學(xué)習(xí)HotSpot虛擬機(jī)非常棒的工具,因?yàn)樗恍枰{(diào)用C++代碼,或者需要?jiǎng)?chuàng)建即時(shí)分析的工具。
參考
http://mishadoff.github.io/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/