您好,登錄后才能下訂單哦!
這篇文章主要為大家展示了“Java字節碼的知識點有哪些”,內容簡而易懂,條理清晰,希望能夠幫助大家解決疑惑,下面讓小編帶領大家一起研究并學習一下“Java字節碼的知識點有哪些”這篇文章吧。
首先我們需要寫個簡單的小程序,1+1的程序,學習就要從最簡單的1+1開始,代碼如下:
package top.luozhou.test;/** * @description: * @author: luozhou * @create: 2019-12-25 21:28 **/public class TestJava { public static void main(String[] args) { int a=1+1; System.out.println(a); } }
寫好java類文件后,首先執行命令javac TestJava.java 編譯類文件,生成TestJava.class。 然后執行反編譯命令javap -verbose TestJava,字節碼結果顯示如下:
Compiled from "TestJava.java"public class top.luozhou.test.TestJava minor version: 0 major version: 56 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #5.#14 // java/lang/Object."<init>":()V #2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream; #3 = Methodref #17.#18 // java/io/PrintStream.println:(I)V #4 = Class #19 // top/luozhou/test/TestJava #5 = Class #20 // java/lang/Object #6 = Utf8 <init> #7 = Utf8 ()V #8 = Utf8 Code #9 = Utf8 LineNumberTable #10 = Utf8 main #11 = Utf8 ([Ljava/lang/String;)V #12 = Utf8 SourceFile #13 = Utf8 TestJava.java #14 = NameAndType #6:#7 // "<init>":()V #15 = Class #21 // java/lang/System #16 = NameAndType #22:#23 // out:Ljava/io/PrintStream; #17 = Class #24 // java/io/PrintStream #18 = NameAndType #25:#26 // println:(I)V #19 = Utf8 top/luozhou/test/TestJava #20 = Utf8 java/lang/Object #21 = Utf8 java/lang/System #22 = Utf8 out #23 = Utf8 Ljava/io/PrintStream; #24 = Utf8 java/io/PrintStream #25 = Utf8 println #26 = Utf8 (I)V { public top.luozhou.test.TestJava(); 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 LineNumberTable: line 8: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: iconst_2 1: istore_1 2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 5: iload_1 6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 9: return LineNumberTable: line 10: 0 line 11: 2 line 12: 9}
1.基礎信息
上述結果刪除了部分不影響解析的冗余信息,接下來我們便來解析字節碼的結果。
minor version: 0 次版本號,為0表示未使用 major version: 56 主版本號,56表示jdk12,表示只能運行在jdk12版本以及之后的虛擬機中
flags: ACC_PUBLIC, ACC_SUPER
ACC_PUBLIC:這就是一個是否是public類型的訪問標志。
ACC_SUPER: 這個falg是為了解決通過 invokespecial 指令調用 super 方法的問題。可以將它理解成 Java 1.0.2 的一個缺陷補丁,只有通過這樣它才能正確找到 super 類方法。從 Java 1.0.2 開始,編譯器始終會在字節碼中生成 ACC_SUPER 訪問標識。感興趣的同學可以點擊這里來了解更多。
2.常量池
接下來,我們將要分析常量池,你也可以對照上面整體的字節碼來理解。
#1 = Methodref #5.#14 // java/lang/Object."<init>":()V
這是一個方法引用,這里的#5表示索引值,然后我們可以發現索引值為5的字節碼如下
#5 = Class #20 // java/lang/Object
它表示這是一個Object類,同理#14指向的是一個"<init>":()V表示引用的是初始化方法。
#2 = Fieldref #15.#16 // java/lang/System.out:Ljava/io/PrintStream;
上面這段表示是一個字段引用,同樣引用了#15和#16,實際上引用的就是java/lang/System類中的PrintStream對象。其他的常量池分析思路是一樣的,鑒于篇幅我就不一一說明了,只列下其中的幾個關鍵類型和信息。
NameAndType:這個表示是名稱和類型的常量表,可以指向方法名稱或者字段的索引,在上面的字節碼中都是表示的實際的方法。
Utf8:我們經常使用的是字符編碼,但是這個不是只有字符編碼的意思,它表示一種字符編碼是Utf8的字符串。它是虛擬機中最常用的表結構,你可以理解為它可以描述方法,字段,類等信息。 比如:
#4 = Class #19 #19 = Utf8 top/luozhou/test/TestJava
這里表示#4這個索引下是一個類,然后指向的類是#19,#19是一個Utf8表,最終存放的是top/luozhou/test/TestJava,那么這樣一連接起來就可以知道#4位置引用的類是top/luozhou/test/TestJava了。
3.構造方法信息
接下來,我們分析下構造方法的字節碼,我們知道,一個類初始化的時候最先執行它的構造方法,如果你沒有寫構造方法,系統會默認給你添加一個無參的構造方法。
public top.luozhou.test.TestJava(); 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 LineNumberTable: line 8: 0
descriptor: ()V :表示這是一個沒有返回值的方法。
flags: ACC_PUBLIC:是公共方法。
stack=1, locals=1, args_size=1 :表示棧中的數量為1,局部變量表中的變量為1,調用參數也為1。
這里為什么都是1呢?這不是默認的構造方法嗎?哪來的參數?其實Java語言有一個潛規則:在任何實例方法里面都可以通過this來訪問到此方法所屬的對象。而這種機制的實現就是通過Java編譯器在編譯的時候作為入參傳入到方法中了,熟悉python語言的同學肯定會知道,在python中定義一個方法總會傳入一個self的參數,這也是傳入此實例的引用到方法內部,Java只是把這種機制后推到編譯階段完成而已。所以,這里的1都是指this這個參數而已。
0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return LineNumberTable: line 8: 0
經過上面這個分析對于這個構造方法表達的意思也就很清晰了。
aload_0:表示把局部變量表中的第一個變量加載到棧中,也就是this。
invokespecial:直接調用初始化方法。
return:調用完畢方法結束。
LineNumberTable:這是一個行數的表,用來記錄字節碼的偏移量和代碼行數的映射關系。line 8: 0表示,源碼中第8行對應的就是偏移量0的字節碼,因為是默認的構造方法,所以這里并無法直觀體現出來。
另外這里會執行Object的構造方法是因為,Object是所有類的父類,子類的構造要先構造父類的構造方法。
4.main方法信息
public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: iconst_2 1: istore_1 2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream; 5: iload_1 6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V 9: return LineNumberTable: line 10: 0 line 11: 2 line 12: 9
有了之前構造方法的分析,我們接下來分析main方法也會熟悉很多,重復的我就略過了,這里重點分析code部分。
stack=2, locals=2, args_size=1:這里的棧和局部變量表為2,參數還是為1。這是為什么呢?因為main方法中聲明了一個變量a,所以局部變量表要加一個,棧也是,所以他們是2。那為什么args_size還是1呢?你不是說默認會把this傳入的嗎?應該是2啊。注意:之前說的是在任何實例方法中,而這個main方法是一個靜態方法,靜態方法直接可以通過類+方法名訪問,并不需要實例對象,所以這里就沒必要傳入了。
0: iconst_2:將int類型2推送到棧頂。
1: istore_1:將棧頂int類型數值存入第二個本地變量。
2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;:獲取PrintStream類。
5: iload_1: 把第二個int型本地變量推送到棧頂。
6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V:調用println方法。
9: return:調用完畢結束方法。
這里的LineNumberTable是有源碼的,我們可以對照下我前面描述是否正確:
line 10: 0: 第10行表示 0: iconst_2字節碼,這里我們發現編譯器直接給我們計算好了把2推送到棧頂了。
line 11: 2:第11行源碼對應的是 2: getstatic 獲取輸出的靜態類PrintStream。
line 12: 9:12行源碼對應的是return,表示方法結束。
這里我也畫了一個動態圖片來演示main方法執行的過程,希望能夠幫助你理解:
以上是“Java字節碼的知識點有哪些”這篇文章的所有內容,感謝各位的閱讀!相信大家都有了一定的了解,希望分享的內容對大家有所幫助,如果還想學習更多知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。