輕松看懂Java字節(jié)碼,看了都說好!
? ? ?
? ?正文? ?
/???Java字節(jié)碼? ?/
計算機(jī)只認(rèn)識0和1。這意味著任何語言編寫的程序最終都需要經(jīng)過編譯器編譯成機(jī)器碼才能被計算機(jī)執(zhí)行。所以,我們所編寫的程序在不同的平臺上運行前都要經(jīng)過重新編譯才能被執(zhí)行。而Java剛誕生的時候曾經(jīng)提過一個非常著名的宣傳口號:?"一次編寫,到處運行"。
Write Once, Run Anywhere.
為了實現(xiàn)該目的,Sun公司以及其他虛擬機(jī)提供商發(fā)布了許多可以運行在不同平臺上的JVM虛擬機(jī),而這些虛擬機(jī)都擁有一個共同的功能,那就是可以載入和執(zhí)行同一種與平臺無關(guān)的字節(jié)碼(ByteCode)。?
于是,我們的源代碼不再必須根據(jù)不同平臺翻譯成0和1,而是間接翻譯成字節(jié)碼,儲存字節(jié)碼的文件再交由運行于不同平臺上的JVM虛擬機(jī)去讀取執(zhí)行,從而實現(xiàn)一次編寫,到處運行的目的。
如今,JVM也不再只支持Java,由此衍生出了許多基于JVM的編程語言,如Groovy, Scala, Koltin等等。

源代碼中的各種變量,關(guān)鍵字和運算符號的語義最終都會編譯成多條字節(jié)碼命令。而字節(jié)碼命令所能提供的語義描述能力是要明顯強(qiáng)于Java本身的,所以有其他一些同樣基于JVM的語言能提供許多Java所不支持的語言特性。
/???例子? ?/
下面以一個簡單的例子來逐步講解字節(jié)碼。
//Main.java
public?class?Main?{
????private?int?m;
????public?int?inc()?{
????????return?m?+?1;
????}
}
通過以下命令, 可以在當(dāng)前所在路徑下生成一個?Main.class?文件。
javac?Main.java
以文本的形式打開生成的class文件,內(nèi)容如下:
cafe?babe?0000?0034?0013?0a00?0400?0f09
0003?0010?0700?1107?0012?0100?016d?0100
0149?0100?063c?696e?6974?3e01?0003?2829
5601?0004?436f?6465?0100?0f4c?696e?654e
756d?6265?7254?6162?6c65?0100?0369?6e63
0100?0328?2949?0100?0a53?6f75?7263?6546
696c?6501?0009?4d61?696e?2e6a?6176?610c
0007?0008?0c00?0500?0601?0010?636f?6d2f
7268?7974?686d?372f?4d61?696e?0100?106a
6176?612f?6c61?6e67?2f4f?626a?6563?7400
2100?0300?0400?0000?0100?0200?0500?0600
0000?0200?0100?0700?0800?0100?0900?0000
1d00?0100?0100?0000?052a?b700?01b1?0000
0001?000a?0000?0006?0001?0000?0003?0001
000b?000c?0001?0009?0000?001f?0002?0001
0000?0007?2ab4?0002?0460?ac00?0000?0100
0a00?0000?0600?0100?0000?0800?0100?0d00
0000?0200?0e
對于文件中的16進(jìn)制代碼,除了開頭的cafe babe,剩下的內(nèi)容大致可以翻譯成:啥玩意啊這......
英雄莫慌,我們就從我們所能認(rèn)識的"cafe babe"講起吧。文件開頭的4個字節(jié)稱之為?魔數(shù),唯有以"cafe babe"開頭的class文件方可被虛擬機(jī)所接受,這4個字節(jié)就是字節(jié)碼文件的身份識別。
目光右移,0000是編譯器jdk版本的次版本號0,0034轉(zhuǎn)化為十進(jìn)制是52,是主版本號,java的版本號從45開始,除1.0和1.1都是使用45.x外,以后每升一個大版本,版本號加一。也就是說,編譯生成該class文件的jdk版本為1.8.0。通過java -version命令稍加驗證, 可得結(jié)果。
Java(TM)?SE?Runtime?Environment?(build?1.8.0_131-b11)
Java?HotSpot(TM)?64-Bit?Server?VM?(build?25.131-b11,?mixed?mode)
結(jié)果驗證成立。
繼續(xù)往下是常量池。但我并不打算繼續(xù)直接分析這個十六進(jìn)制文件,這樣會比較繁瑣,我們通過另一種更容易讓人看懂的方式來分析這個class文件。
反編譯字節(jié)碼文件
使用到j(luò)ava內(nèi)置的一個反編譯工具javap可以反編譯字節(jié)碼文件。通過javap -help可了解javap的基本用法
用法:?javap?<options>?<classes>
其中,?可能的選項包括:
??-help??--help??-?????????輸出此用法消息
??-version?????????????????版本信息
??-v??-verbose?????????????輸出附加信息
??-l???????????????????????輸出行號和本地變量表
??-public ?????????????????僅顯示公共類和成員
??-protected ??????????????顯示受保護(hù)的/公共類和成員
??-package?????????????????顯示程序包/受保護(hù)的/公共類
???????????????????????????和成員?(默認(rèn))
??-p??-private?????????????顯示所有類和成員
??-c???????????????????????對代碼進(jìn)行反匯編
??-s???????????????????????輸出內(nèi)部類型簽名
??-sysinfo?????????????????顯示正在處理的類的
???????????????????????????系統(tǒng)信息?(路徑,?大小,?日期,?MD5?散列)
??-constants???????????????顯示最終常量
??-classpath?<path>????????指定查找用戶類文件的位置
??-cp?<path>???????????????指定查找用戶類文件的位置
??-bootclasspath?<path>????覆蓋引導(dǎo)類文件的位置
輸入命令javap -verbose -p Main.class查看輸出內(nèi)容:
Classfile?/E:/JavaCode/TestProj/out/production/TestProj/com/rhythm7/Main.class
??Last?modified?2018-4-7;?size?362?bytes
??MD5?checksum?4aed8540b098992663b7ba08c65312de
??Compiled?from?"Main.java"
public?class?com.rhythm7.Main
??minor?version:?0
??major?version:?52
??flags:?ACC_PUBLIC,?ACC_SUPER
Constant?pool:
???#1?=?Methodref??????????#4.#18?????????//?java/lang/Object."":()V
???#2?=?Fieldref???????????#3.#19?????????//?com/rhythm7/Main.m:I
???#3?=?Class??????????????#20????????????//?com/rhythm7/Main
???#4?=?Class??????????????#21????????????//?java/lang/Object
???#5?=?Utf8???????????????m
???#6?=?Utf8???????????????I
???#7?=?Utf8???????????????
???#8?=?Utf8???????????????()V
???#9?=?Utf8???????????????Code
??#10?=?Utf8???????????????LineNumberTable
??#11?=?Utf8???????????????LocalVariableTable
??#12?=?Utf8???????????????this
??#13?=?Utf8???????????????Lcom/rhythm7/Main;
??#14?=?Utf8???????????????inc
??#15?=?Utf8???????????????()I
??#16?=?Utf8???????????????SourceFile
??#17?=?Utf8???????????????Main.java
??#18?=?NameAndType????????#7:#8??????????//?"":()V
??#19?=?NameAndType????????#5:#6??????????//?m:I
??#20?=?Utf8???????????????com/rhythm7/Main
??#21?=?Utf8???????????????java/lang/Object
{
??private?int?m;
????descriptor:?I
????flags:?ACC_PRIVATE
??public?com.rhythm7.Main();
????descriptor:?()V
????flags:?ACC_PUBLIC
????Code:
??????stack=1,?locals=1,?args_size=1
?????????0:?aload_0
?????????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."":()V
?????????4:?return
??????LineNumberTable:
????????line?3:?0
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature
????????????0???????5?????0??this???Lcom/rhythm7/Main;
??public?int?inc();
????descriptor:?()I
????flags:?ACC_PUBLIC
????Code:
??????stack=2,?locals=1,?args_size=1
?????????0:?aload_0
?????????1:?getfield??????#2??????????????????//?Field?m:I
?????????4:?iconst_1
?????????5:?iadd
?????????6:?ireturn
??????LineNumberTable:
????????line?8:?0
??????LocalVariableTable:
????????Start??Length??Slot??Name???Signature
????????????0???????7?????0??this???Lcom/rhythm7/Main;
}
SourceFile:?"Main.java"
字節(jié)碼文件信息
開頭的7行信息包括:Class文件當(dāng)前所在位置,最后修改時間,文件大小,MD5值,編譯自哪個文件,類的全限定名,jdk次版本號,主版本號。然后緊接著的是該類的訪問標(biāo)志:ACC_PUBLIC,?ACC_SUPER,訪問標(biāo)志的含義如下:

常量池
Constant pool意為常量池。常量池可以理解成Class文件中的資源倉庫。主要存放的是兩大類常量:字面量(Literal)和符號引用(Symbolic References)。字面量類似于java中的常量概念,如文本字符串,final常量等,而符號引用則屬于編譯原理方面的概念,包括以下三種:
類和接口的全限定名(Fully Qualified Name) 字段的名稱和描述符號(Descriptor) 方法的名稱和描述符
#1?=?Methodref??????????#4.#18?????????//?java/lang/Object." ":()V
#4?=?Class??????????????#21????????????//?java/lang/Object
#7?=?Utf8???????????????
#8?=?Utf8???????????????()V
#18?=?NameAndType????????#7:#8??????????//?"":()V
#21?=?Utf8???????????????java/lang/Object
java/lang/Object." " :()V
#2?=?Fieldref???????????#3.#19?????????//?com/rhythm7/Main.m:I
#3?=?Class??????????????#20????????????//?com/rhythm7/Main
#5?=?Utf8???????????????m
#6?=?Utf8???????????????I
#19?=?NameAndType????????#5:#6??????????//?m:I
#20?=?Utf8???????????????com/rhythm7/Main

方法表集合
private?int?m;
??descriptor:?I
??flags:?ACC_PRIVATE
public?com.rhythm7.Main();
???descriptor:?()V
???flags:?ACC_PUBLIC
???Code:
?????stack=1,?locals=1,?args_size=1
????????0:?aload_0
????????1:?invokespecial?#1??????????????????//?Method?java/lang/Object."":()V
????????4:?return
?????LineNumberTable:
???????line?3:?0
?????LocalVariableTable:
???????Start??Length??Slot??Name???Signature
???????????0???????5?????0??this???Lcom/rhythm7/Main;
SourceFile
分析try-catch-finally
public?class?TestCode?{
????public?int?foo()?{
????????int?x;
????????try?{
????????????x?=?1;
????????????return?x;
????????}?catch?(Exception?e)?{
????????????x?=?2;
????????????return?x;
????????}?finally?{
????????????x?=?3;
????????}
????}
}
public?class?TestCode?{
javac?TestCode.java
javap?-verbose?TestCode.class
public?int?foo();
????descriptor:?()I
????flags:?ACC_PUBLIC
????Code:
??????stack=1,?locals=5,?args_size=1
?????????0:?iconst_1?//int型1入棧?->棧頂=1
?????????1:?istore_1?//將棧頂?shù)膇nt型數(shù)值存入第二個局部變量?->局部2=1
?????????2:?iload_1?//將第二個int型局部變量推送至棧頂?->棧頂=1
?????????3:?istore_2?//!!將棧頂int型數(shù)值存入第三個局部變量?->局部3=1
?????????4:?iconst_3?//int型3入棧?->棧頂=3
?????????5:?istore_1?//將棧頂?shù)膇nt型數(shù)值存入第二個局部變量?->局部2=3
?????????6:?iload_2?//!!將第三個int型局部變量推送至棧頂?->棧頂=1
?????????7:?ireturn?//從當(dāng)前方法返回棧頂int數(shù)值?->1
?????????8:?astore_2?//?->局部3=Exception
?????????9:?iconst_2?//?->棧頂=2
????????10:?istore_1?//?->局部2=2
????????11:?iload_1?//->棧頂=2
????????12:?istore_3?//!!?->局部4=2
????????13:?iconst_3?//?->棧頂=3
????????14:?istore_1?//?->局部1=3
????????15:?iload_3?//!!?->棧頂=2
????????16:?ireturn?//?->?2
????????17:?astore????????4?//將棧頂引用型數(shù)值存入第五個局部變量=any
????????19:?iconst_3?//將int型數(shù)值3入棧?->?棧頂3
????????20:?istore_1?//將棧頂?shù)谝粋€int數(shù)值存入第二個局部變量?->?局部2=3
????????21:?aload?????????4?//將局部第五個局部變量(引用型)推送至棧頂
????????23:?athrow?//將棧頂?shù)漠惓伋?/span>
??????Exception?table:
?????????from????to??target?type
?????????????0?????4?????8???Class?java/lang/Exception?//0到4行對應(yīng)的異常,對應(yīng)#8中儲存的異常
?????????????0?????4????17???any?//Exeption之外的其他異常
?????????????8????13????17???any
????????????17????19????17???any
不發(fā)生異常時: return 1 發(fā)生異常時: return 2 發(fā)生非Exception及其子類的異常,拋出異常,不返回值
kotlin 函數(shù)擴(kuò)展的實現(xiàn)
//SayHello.kt
package?com.rhythm7
fun?Any.sayHello()?{
????println("Hello")
}
Classfile?/E:/JavaCode/TestProj/out/production/TestProj/com/rhythm7/SayHelloKt.class
Last?modified?2018-4-8;?size?958?bytes
?MD5?checksum?780a04b75a91be7605cac4655b499f19
?Compiled?from?"SayHello.kt"
public?final?class?com.rhythm7.SayHelloKt
?minor?version:?0
?major?version:?52
?flags:?ACC_PUBLIC,?ACC_FINAL,?ACC_SUPER
Constant?pool:
????//省略常量池部分字節(jié)碼
{
?public?static?final?void?sayHello(java.lang.Object);
???descriptor:?(Ljava/lang/Object;)V
???flags:?ACC_PUBLIC,?ACC_STATIC,?ACC_FINAL
???Code:
?????stack=2,?locals=2,?args_size=1
????????0:?aload_0
????????1:?ldc???????????#9??????????????????//?String?$receiver
????????3:?invokestatic??#15?????????????????//?Method?kotlin/jvm/internal/Intrinsics.checkParameterIsNotNull:(Ljava/lang/Object;Ljava/lang/String;)V
????????6:?ldc???????????#17?????????????????//?String?Hello
????????8:?astore_1
????????9:?getstatic?????#23?????????????????//?Field?java/lang/System.out:Ljava/io/PrintStream;
???????12:?aload_1
???????13:?invokevirtual?#28?????????????????//?Method?java/io/PrintStream.println:(Ljava/lang/Object;)V
???????16:?return
?????LocalVariableTable:
???????Start??Length??Slot??Name???Signature
???????????0??????17?????0?$receiver???Ljava/lang/Object;
?????LineNumberTable:
???????line?4:?6
???????line?5:?16
???RuntimeInvisibleParameterAnnotations:
?????0:
???????0:?#7()
}
SourceFile:?"SayHello.kt"
public?static?final?void?sayHello(java.lang.Object);
來源:juejin.im/post/6844903588716609543
版權(quán)申明:內(nèi)容來源網(wǎng)絡(luò),版權(quán)歸原創(chuàng)者所有。除非無法確認(rèn),我們都會標(biāo)明作者及出處,如有侵權(quán)煩請告知,我們會立即刪除并表示歉意。謝謝!

