為什么給Java代碼加個(gè)空行,class文件就翻臉不認(rèn)人了?
為了寫出這幾行優(yōu)美的代碼,主要是為了讓它輸出優(yōu)美動(dòng)聽的樂符,我下了一番功夫。你不要覺得簡(jiǎn)單,我把它打印出來給普通的保潔阿姨去看,阿姨竟然連xjjdog都認(rèn)不出來。別說代碼了,中英文混血,就秒殺一大堆高干分子。
- public class HelloWorld {
- public static void main(String[] args) {
- System.out.println("love xjjdog");
- }
- }
想說愛我就那么難么?怎么這么多的廢話呢?這次探討的主要問題是,給Java源文件加個(gè)空行之后,它生成的字節(jié)碼,會(huì)有變化么?
1. 翻臉不認(rèn)人
Java號(hào)稱一次編譯到處運(yùn)行,大概就是class文件的功勞。不同的Java版本編譯之后的class文件那是肯定不一樣的,因?yàn)槔锩嬗幸粋€(gè)版本號(hào),那肯定影響了它們的內(nèi)容。
我們就看一下,如果給上面的代碼,加一個(gè)空行,它的class文件會(huì)不會(huì)變。
這個(gè)空行還不能隨便加。它可能在xjjdog上面,也可能在下面??赡茉趝中,也可能在文件末尾。
(1) 打臉
在驗(yàn)證之前,我們先看一下當(dāng)前的class文件md5值。
我非常喜歡被打臉,所以先看一種加空行也無所謂的情況。
再次編譯之后看md5值,果然被打臉了。還好我已經(jīng)練就了臉不紅心不跳的本領(lǐng),這個(gè)結(jié)果厚著臉皮接受。
(2) 抹藥
為了和主題遙相呼應(yīng),安慰一下受傷的心靈,我們把空行轉(zhuǎn)移到了這里。
再次編譯之后,看md5值(怎么感覺這句話已經(jīng)說過了呢)。
變了。這次真的變了。
使用hexdump命令分析兩次生成的字節(jié)碼,發(fā)現(xiàn)其中只不過變了一個(gè)數(shù)字。
2. 騷戴斯乃
特別不喜歡分析這種二進(jìn)制的東西。雖然CAFEBABE這個(gè)魔數(shù)在第一行歷歷在目??Х葘氊?怎么聽著像是某個(gè)番號(hào)?
我們還是用javap來看一下它的原型。
- javap -p -v HelloWorld.class
通過對(duì)比兩次生成的字節(jié)碼,我們終于發(fā)現(xiàn)了這個(gè)變動(dòng),是一個(gè)叫做LineNumberTable的結(jié)構(gòu)引起的。
使用asmtools.jar深入分析這個(gè)結(jié)構(gòu),可以看到同樣的信息。
LineNumberTable展示了Java源碼行號(hào)和字節(jié)碼指令的對(duì)應(yīng)關(guān)系。前面的數(shù)字代表Java源代碼中的行號(hào),而冒號(hào)后面的則代表字節(jié)碼里每行指令的映射關(guān)系。在對(duì)代碼進(jìn)行調(diào)試的時(shí)候,能夠快速定位,順利進(jìn)行。
也就是說,這些是輔助信息,我們可以在編譯的時(shí)候抹掉它。怎么抹掉呢?給javac一個(gè)參數(shù)就ok了。
- javac -g:none HelloWorld.java
這樣編譯后的字節(jié)碼,緊湊、優(yōu)雅、無用。不管你加多少空行,生成的字節(jié)碼都是一樣的??墒牵覀?cè)僖膊荒軙晨炝芾斓倪M(jìn)行調(diào)試了。
- {
- public HelloWorld();
- descriptor: ()V
- flags: ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."<init>":()V
- 4: return
- public static void main(java.lang.String[]);
- descriptor: ([Ljava/lang/String;)V
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=2, locals=1, args_size=1
- 0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
- 3: ldc #3 // String Hello xjjdog
- 5: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 8: return
- }
要想在開發(fā)階段讓字節(jié)碼又香又有用,可以直接使用參數(shù)-g開啟所有調(diào)試信息。IDEA可以在編譯選項(xiàng)里對(duì)這個(gè)參數(shù)進(jìn)行開啟。有很多同學(xué)在編譯之后的代碼里找不到局部變量的符號(hào)表,也是由于這個(gè)參數(shù)沒有開啟所引起的。
END
麻雀雖小,五臟俱全。能寫HelloWorld,就證明已經(jīng)凌駕于大部分人之上,能了解這些東西,就證明已經(jīng)是人上之人。怪不得大家都說:就差一個(gè)程序員了。
HelloWorld,表面上看起來人五人六,原來背地里都有自己的小99啊。