public class ByteCodeDemo {
public static void main(String[] args) {
System.out.}
}
在編譯這個(gè)類(lèi)后,你可以用十六進(jìn)制編輯器打開(kāi).class文件然后參照虛擬機(jī)規(guī)范翻譯字節(jié)碼。幸運(yùn)的是有更簡(jiǎn)單的方法。JDK包含一個(gè)命令行的反匯編器:javap,它可以轉(zhuǎn)換字節(jié)碼為一種可讀的助記符形式,可以像下面這樣通過(guò)傳遞'-c'參數(shù)給javap得到字節(jié)碼列表: public class ByteCodeDemo extends java.lang.Object {
public ByteCodeDemo();
public static void main(java.lang.String[]);
}
Method ByteCodeDemo()
0 aload_0
1 invokespecial #1 <Method java.lang.Object()>
4 return
Method void main(java.lang.String[])
0 getstatic #2 <Field java.io.PrintStream out>
3 ldc #3 <String "Hello world">
5 invokevirtual #4 <Method void println(java.lang.String)>
8 return
僅僅從這個(gè)短小的列表你可以學(xué)到很多字節(jié)碼的知識(shí)。從main方法的第一個(gè)指令開(kāi)始: 0 getstatic #2 <Field java.io.PrintStream out>
開(kāi)始的整數(shù)是方法中的指令的偏移值,因此第一個(gè)指令以0開(kāi)始。緊隨偏移量是指令的助記符(mnemonic)。在這個(gè)范例中,'getstatic' 指令將一個(gè)靜態(tài)成員壓入一個(gè)稱(chēng)為操作數(shù)堆棧的數(shù)據(jù)結(jié)構(gòu),后續(xù)的指令可以引用這個(gè)數(shù)據(jù)結(jié)構(gòu)中的成員。getstatic 指令后是要壓入的成員。在這個(gè)例子中,要壓入的成員是"#2 <Field java.io.PrintStream out>" 。假如你直接檢查字節(jié)碼,你會(huì)看到成員信息沒(méi)有直接嵌入指令而是像所有由java類(lèi)使用的常量那樣存儲(chǔ)在一個(gè)共享池中。將成員信息存儲(chǔ)在一個(gè)常量池中可以減小字節(jié)碼指令的大小,因?yàn)橹噶钪恍枰鎯?chǔ)常量池中的一個(gè)索引而不是整個(gè)常量。在這個(gè)例子中,成員信息位于常量池中的#2處。常量池中的項(xiàng)目的順序是和編譯器相關(guān)的,因此在你的環(huán)境中看到的可能不是'#2' 。 public float add(float f, int n) {
return f + n;
}
假如你將這個(gè)方法加入上面的范例中,重新編譯它,然后運(yùn)行javap,你將看到的字節(jié)碼類(lèi)似這個(gè): Method float add(float, int)
0 fload_1
1 iload_2
2 i2f
3 fadd
4 freturn
在方法的開(kāi)始,虛擬機(jī)將方法的參數(shù)放入一個(gè)稱(chēng)為局部變量表的數(shù)據(jù)結(jié)構(gòu)中。將像名字暗示的那樣,局部變量表也包含了你聲明的任何局部變量。在這個(gè)例子中,方法以三個(gè)局部變量表的項(xiàng)開(kāi)始,這些都是add方法的參數(shù),位置0保存this引用,而位置1和2分別保存float和int參數(shù)。 //返回 str1+str2 的串連
String concat(String str1, String str2) {
return str1 + str2;
}
//將 str2 附加到 str1
void concat(StringBuffer str1, String str2) {
str1.append(str2);
}
猜猜每個(gè)方法需要多少個(gè)方法調(diào)用。現(xiàn)在編譯這些方法并且運(yùn)行javap,你會(huì)得到類(lèi)似下面的輸出: Method java.lang.String concat1(java.lang.String, java.lang.String)
0 new #5 <Class java.lang.StringBuffer>
3 dup
4 invokespecial #6 <Method java.lang.StringBuffer()>
7 aload_1
8 invokevirtual #7 <Method java.lang.StringBuffer append(java.lang.String)>
11 aload_2
12 invokevirtual #7 <Method java.lang.StringBuffer append(java.lang.String)>
15 invokevirtual #8 <Method java.lang.String toString()>
18 areturn
Method void concat2(java.lang.StringBuffer, java.lang.String)
0 aload_1
1 aload_2
2 invokevirtual #7 <Method java.lang.StringBuffer append(java.lang.String)>
5 pop
6 return
concat1方法執(zhí)行了5個(gè)方法調(diào)用s: new, invokespecial和三個(gè)invokevirtuals,這比concat2方法執(zhí)行了更多的工作,后者只執(zhí)行了一個(gè)invokevirtual調(diào)用。大多Java程序員已經(jīng)得到過(guò)警告,因?yàn)镾tring是不可變的,而使用StringBuffer進(jìn)行字符串連接效率更高。使用javap分析這個(gè)使得這點(diǎn)變得很生動(dòng)。假如你不能肯定兩個(gè)語(yǔ)言構(gòu)造在性能上是否相等,你應(yīng)該使用javap分析字節(jié)碼。然而,對(duì)just-in-time (JIT)編譯器要小心,因?yàn)镴IT編譯器將字節(jié)碼重新編譯為本機(jī)代碼而能執(zhí)行一些javap不能揭示的附加優(yōu)化。除非你有你的虛擬機(jī)的源代碼,否則你應(yīng)該補(bǔ)充你的字節(jié)碼的基準(zhǔn)性能分析。 public class ChangeALot {
public static final boolean debug=false;
public static boolean log=false;
}
public class EternallyConstant {
public static void main(String [] args) {
System.out.println("EternallyConstant beginning execution");
if (ChangeALot.debug)
System.out.println("Debug mode is on");
if (ChangeALot.log)
System.out.println("Logging mode is on");
}
}
假如你運(yùn)行EternallyConstant,你會(huì)得到信息: EternallyConstant beginning execution
Logging mode is on
debug變量怎么了?即使你將debug設(shè)置為true,信息"Debug mode is on"并沒(méi)有出現(xiàn)。答案在字節(jié)碼中。對(duì) EternallyConstant運(yùn)行javap你會(huì)看到: Method void main(java.lang.String[])
0 getstatic #2 <Field java.io.PrintStream out>
3 ldc #3 <String "EternallyConstant beginning execution">
5 invokevirtual #4 <Method void println(java.lang.String)>
8 getstatic #5 <Field boolean log>
11 ifeq 22
14 getstatic #2 <Field java.io.PrintStream out>
17 ldc #6 <String "Logging mode is on">
19 invokevirtual #4 <Method void println(java.lang.String)>
22 return
驚異吧!在log成員上有一個(gè)'ifeq'檢查,而代碼根本沒(méi)有檢查debug成員。因?yàn)閐ebug成員被標(biāo)記為final類(lèi)型,編譯器知道debug成員在運(yùn)行時(shí)永遠(yuǎn)不會(huì)改變,因此它通過(guò)移除'if'聲明進(jìn)行優(yōu)化。這確實(shí)是一個(gè)非常有用的優(yōu)化,因?yàn)樗饝?yīng)你在程序中嵌入調(diào)試代碼而在將它設(shè)置為false時(shí)不用付出運(yùn)行時(shí)的代價(jià)。不幸的是這個(gè)優(yōu)化能夠?qū)е轮饕木幾g時(shí)混亂。假如你改變一個(gè)final成員,你必須記住重新編譯任何可能引用該成員的類(lèi)。這是因?yàn)檫@個(gè)'reference'可能已經(jīng)經(jīng)過(guò)優(yōu)化了。Java開(kāi)發(fā)環(huán)境不能總是發(fā)現(xiàn)這個(gè)微妙的相關(guān)性,一些能導(dǎo)致非常希奇的錯(cuò)誤。因此,古老的C++格言對(duì)于java環(huán)境仍然有效:"When in douBT, rebuild all."(有疑問(wèn),重新編譯所有的代碼)。 新聞熱點(diǎn)
疑難解答
圖片精選
網(wǎng)友關(guān)注