日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

【JVM】通过javap命令分析Java汇编指令

發布時間:2025/3/15 java 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【JVM】通过javap命令分析Java汇编指令 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • javap命令簡述
  • javap測試及內容詳解
    • 例子1
    • 例子2
  • 總結
  • 轉載說明

javap命令簡述

javap是jdk自帶的反解析工具。它的作用就是根據class字節碼文件,反解析出當前類對應的code區(匯編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等等信息。
當然這些信息中,有些信息(如本地變量表、指令和代碼行偏移量映射表、常量池中方法的參數名稱等等)需要在使用javac編譯成class文件時,指定參數才能輸出,比如,你直接javac xx.java,就不會在生成對應的局部變量表等信息,如果你使用javac -g xx.java就可以生成所有相關信息了。如果你使用的eclipse,則默認情況下,eclipse在編譯時會幫你生成局部變量表、指令和代碼行偏移量映射表等信息的。
通過反編譯生成的匯編代碼,我們可以深入的了解java代碼的工作機制。比如我們可以查看i++;這行代碼實際運行時是先獲取變量i的值,然后將這個值加1,最后再將加1后的值賦值給變量i。
通過局部變量表,我們可以查看局部變量的作用域范圍、所在槽位等信息,甚至可以看到槽位復用等信息。

javap的用法格式:

javap <options> <classes>

其中classes就是你要反編譯的class文件。
在命令行中直接輸入javap或javap -help可以看到javap的options有如下選項:

-help --help -? 輸出此用法消息-version 版本信息,其實是當前javap所在jdk的版本信息,不是class在哪個jdk下生成的。-v -verbose 輸出附加信息(包括行號、本地變量表,反匯編等詳細信息)-l 輸出行號和本地變量表-public 僅顯示公共類和成員-protected 顯示受保護的/公共類和成員-package 顯示程序包/受保護的/公共類 和成員 (默認)-p -private 顯示所有類和成員-c 對代碼進行反匯編-s 輸出內部類型簽名-sysinfo 顯示正在處理的類的系統信息 (路徑, 大小, 日期, MD5 散列)-constants 顯示靜態最終常量-classpath <path> 指定查找用戶類文件的位置-bootclasspath <path> 覆蓋引導類文件的位置

一般常用的是-v -l -c三個選項。
javap -v classxx,不僅會輸出行號、本地變量表信息、反編譯匯編代碼,還會輸出當前類用到的常量池等信息。
javap -l 會輸出行號和本地變量表信息。
javap -c 會對當前class字節碼進行反編譯生成匯編代碼。
查看匯編代碼時,需要知道里面的jvm指令,可以參考官方文檔:
https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html
另外通過jclasslib工具也可以看到上面這些信息,而且是可視化的,效果更好一些。

javap測試及內容詳解

前面已經介紹過javap輸出的內容有哪些,東西比較多,這里主要介紹其中code區(匯編指令)、局部變量表和代碼行偏移映射三個部分。
如果需要分析更多的信息,可以使用javap -v進行查看。
另外,為了更方便理解,所有匯編指令不單拎出來講解,而是在反匯編代碼中以注釋的方式講解。

下面寫段代碼測試一下:

例子1

分析一下下面的代碼反匯編之后結果:

public class TestDate {private int count = 0;public static void main(String[] args) {TestDate testDate = new TestDate();testDate.test1();}public void test1(){Date date = new Date();String name1 = "wangerbei";test2(date,name1); System.out.println(date+name1);}public void test2(Date dateP,String name2){dateP = null;name2 = "zhangsan";}public void test3(){count++;}public void test4(){int a = 0;{int b = 0;b = a+1;}int c = a+1;} }

上面代碼通過javac -g 生成class文件,然后通過javap命令對字節碼進行反匯編:
$ javap -c -l TestDate
得到下面內容(指令等部分是我參照著官方文檔總結的):

Warning: Binary file TestDate contains com.justest.test.TestDate Compiled from "TestDate.java" public class com.justest.test.TestDate {//默認的構造方法,在構造方法執行時主要完成一些初始化操作,包括一些成員變量的初始化賦值等操作public com.justest.test.TestDate();Code:0: aload_0 //從本地變量表中加載索引為0的變量的值,也即this的引用,壓入棧1: invokespecial #10 //出棧,調用java/lang/Object."<init>":()V 初始化對象,就是this指定的對象的init()方法完成初始化4: aload_0 // 4到6表示,調用this.count = 0,也即為count復制為0。這里this引用入棧5: iconst_0 //將常量0,壓入到操作數棧6: putfield //出棧前面壓入的兩個值(this引用,常量值0), 將0取出,并賦值給count9: return //指令與代碼行數的偏移對應關系,每一行第一個數字對應代碼行數,第二個數字對應前面code中指令前面的數字LineNumberTable:line 5: 0line 7: 4line 5: 9//局部變量表,start+length表示這個變量在字節碼中的生命周期起始和結束的偏移位置(this生命周期從頭0到結尾10),slot就是這個變量在局部變量表中的槽位(槽位可復用),name就是變量名稱,Signatur局部變量類型描述LocalVariableTable:Start Length Slot Name Signature0 10 0 this Lcom/justest/test/TestDate;public static void main(java.lang.String[]);Code: // new指令,創建一個class com/justest/test/TestDate對象,new指令并不能完全創建一個對象,對象只有在初,只有在調用初始化方法完成后(也就是調用了invokespecial指令之后),對象才創建成功,0: new //創建對象,并將對象引用壓入棧3: dup //將操作數棧定的數據復制一份,并壓入棧,此時棧中有兩個引用值4: invokespecial #20 //pop出棧引用值,調用其構造函數,完成對象的初始化7: astore_1 //pop出棧引用值,將其(引用)賦值給局部變量表中的變量testDate8: aload_1 //將testDate的引用值壓入棧,因為testDate.test1();調用了testDate,這里使用aload_1從局部變量表中獲得對應的變量testDate的值并壓入操作數棧9: invokevirtual #21 // Method test1:()V 引用出棧,調用testDate的test1()方法12: return //整個main方法結束返回LineNumberTable:line 10: 0line 11: 8line 12: 12//局部變量表,testDate只有在創建完成并賦值后,才開始聲明周期LocalVariableTable:Start Length Slot Name Signature0 13 0 args [Ljava/lang/String;8 5 1 testDate Lcom/justest/test/TestDate;public void test1();Code:0: new #27 // 0到7創建Date對象,并賦值給date變量3: dup4: invokespecial #29 // Method java/util/Date."<init>":()V7: astore_18: ldc #30 // String wangerbei,將常量“wangerbei”壓入棧10: astore_2 //將棧中的“wangerbei”pop出,賦值給name111: aload_0 //11到14,對應test2(date,name1);默認前面加this.12: aload_1 //從局部變量表中取出date變量13: aload_2 //取出name1變量14: invokevirtual #32 // Method test2: (Ljava/util/Date;Ljava/lang/String;)V 調用test2方法// 17到38對應System.out.println(date+name1);17: getstatic #36 // Field java/lang/System.out:Ljava/io/PrintStream;//20到35是jvm中的優化手段,多個字符串變量相加,不會兩兩創建一個字符串對象,而使用StringBuilder來創建一個對象20: new #42 // class java/lang/StringBuilder23: dup24: invokespecial #44 // Method java/lang/StringBuilder."<init>":()V27: aload_128: invokevirtual #45 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;31: aload_232: invokevirtual #49 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;35: invokevirtual #52 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;38: invokevirtual #56 // Method java/io/PrintStream.println:(Ljava/lang/String;)V invokevirtual指令表示基于類調用方法41: returnLineNumberTable:line 15: 0line 16: 8line 17: 11line 18: 17line 19: 41LocalVariableTable:Start Length Slot Name Signature0 42 0 this Lcom/justest/test/TestDate;8 34 1 date Ljava/util/Date;11 31 2 name1 Ljava/lang/String;public void test2(java.util.Date, java.lang.String);Code:0: aconst_null //將一個null值壓入棧1: astore_1 //將null賦值給dateP2: ldc #66 // String zhangsan 從常量池中取出字符串“zhangsan”壓入棧中4: astore_2 //將字符串賦值給name25: returnLineNumberTable:line 22: 0line 23: 2line 24: 5LocalVariableTable:Start Length Slot Name Signature0 6 0 this Lcom/justest/test/TestDate;0 6 1 dateP Ljava/util/Date;0 6 2 name2 Ljava/lang/String;public void test3();Code:0: aload_0 //取出this,壓入棧1: dup //復制操作數棧棧頂的值,并壓入棧,此時有兩個this對象引用值在操作數組棧2: getfield #12// Field count:I this出棧,并獲取其count字段,然后壓入棧,此時棧中有一個this和一個count的值5: iconst_1 //取出一個int常量1,壓入操作數棧6: iadd // 從棧中取出count和1,將count值和1相加,結果入棧7: putfield #12 // Field count:I 一次彈出兩個,第一個彈出的是上一步計算值,第二個彈出的this,將值賦值給this的count字段10: returnLineNumberTable:line 27: 0line 28: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lcom/justest/test/TestDate;public void test4();Code:0: iconst_01: istore_12: iconst_03: istore_24: iload_15: iconst_16: iadd7: istore_28: iload_19: iconst_110: iadd11: istore_212: returnLineNumberTable:line 33: 0line 35: 2line 36: 4line 38: 8line 39: 12//看下面,b和c的槽位slot一樣,這是因為b的作用域就在方法塊中,方法塊結束,局部變量表中的槽位就被釋放,后面的變量就可以復用這個槽位LocalVariableTable:Start Length Slot Name Signature0 13 0 this Lcom/justest/test/TestDate;2 11 1 a I4 4 2 b I12 1 2 c I }

例子2

下面一個例子
先有一個User類:

public class User {private String name;private int age;public String getName() {return name;}public void setName(String name) {this.name = name;}public int getAge() {return age;}public void setAge(int age) {this.age = age;} }

然后寫一個操作User對象的測試類:

public class TestUser {private int count;public void test(int a){count = count + a;}public User initUser(int age,String name){User user = new User();user.setAge(age);user.setName(name);return user;}public void changeUser(User user,String newName){user.setName(newName);} }

先javac -g 編譯成class文件。
然后對TestUser類進行反匯編:

$ javap -c -l TestUser

得到反匯編結果如下:

Warning: Binary file TestUser contains com.justest.test.TestUser Compiled from "TestUser.java"public class com.justest.test.TestUser {//默認的構造函數public com.justest.test.TestUser();Code:0: aload_01: invokespecial #10 // Method java/lang/Object."<init>":()V4: returnLineNumberTable:line 3: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/justest/test/TestUser;public void test(int);Code:0: aload_0 //取this對應的對應引用值,壓入操作數棧1: dup //復制棧頂的數據,壓入棧,此時棧中有兩個值,都是this對象引用2: getfield #18 // 引用出棧,通過引用獲得對應count的值,并壓入棧5: iload_1 //從局部變量表中取得a的值,壓入棧中6: iadd //彈出棧中的count值和a的值,進行加操作,并將結果壓入棧7: putfield #18 // 經過上一步操作后,棧中有兩個值,棧頂為上一步操作結果,棧頂下面是this引用,這一步putfield指令,用于將棧頂的值賦值給引用對象的count字段10: return //return voidLineNumberTable:line 8: 0line 9: 10LocalVariableTable:Start Length Slot Name Signature0 11 0 this Lcom/justest/test/TestUser;0 11 1 a Ipublic com.justest.test.User initUser(int, java.lang.String);Code:0: new #23 // class com/justest/test/User 創建User對象,并將引用壓入棧3: dup //復制棧頂值,再次壓入棧,棧中有兩個User對象的地址引用4: invokespecial #25 // Method com/justest/test/User."<init>":()V 調用user對象初始化7: astore_3 //從棧中pop出User對象的引用值,并賦值給局部變量表中user變量8: aload_3 //從局部變量表中獲得user的值,也就是User對象的地址引用,壓入棧中9: iload_1 //從局部變量表中獲得a的值,并壓入棧中,注意aload和iload的區別,一個取值是對象引用,一個是取int類型數據10: invokevirtual #26 // Method com/justest/test/User.setAge:(I)V 操作數棧pop出兩個值,一個是User對象引用,一個是a的值,調用setAge方法,并將a的值傳給這個方法,setAge操作的就是堆中對象的字段了13: aload_3 //同7,壓入棧14: aload_2 //從局部變量表取出name,壓入棧15: invokevirtual #29 // MethodUser.setName:(Ljava/lang/String;)V 操作數棧pop出兩個值,一個是User對象引用,一個是name的值,調用setName方法,并將a的值傳給這個方法,setName操作的就是堆中對象的字段了18: aload_3 //從局部變量取出User引用,壓入棧19: areturn //areturn指令用于返回一個對象的引用,也就是上一步中User的引用,這個返回值將會被壓入調用當前方法的那個方法的棧中objectref is popped from the operand stack of the current frame ([§2.6](https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-2.html#jvms-2.6)) and pushed onto the operand stack of the frame of the invokerLineNumberTable:line 12: 0line 13: 8line 14: 13line 15: 18LocalVariableTable:Start Length Slot Name Signature0 20 0 this Lcom/justest/test/TestUser;0 20 1 age I0 20 2 name Ljava/lang/String;8 12 3 user Lcom/justest/test/User;public void changeUser(com.justest.test.User, java.lang.String);Code:0: aload_1 //局部變量表中取出user,也即User對象引用,壓入棧1: aload_2 //局部變量表中取出newName,壓入棧2: invokevirtual #29 // Method User.setName:(Ljava/lang/String;)V pop出棧newName值和TestUser引用,調用其setName方法,并將newName的值傳給這個方法5: returnLineNumberTable:line 19: 0line 20: 5LocalVariableTable:Start Length Slot Name Signature0 6 0 this Lcom/justest/test/TestUser;0 6 1 user Lcom/justest/test/User;0 6 2 newName Ljava/lang/String;public static void main(java.lang.String[]);Code:0: new #1 // class com/justest/test/TestUser 創建TestUser對象,將引用壓入棧3: dup //復制引用,壓入棧4: invokespecial #43 // Method "<init>":()V 引用值出棧,調用構造方法,對象初始化7: astore_1 //引用值出棧,賦值給局部變量表中變量tu8: aload_1 //取出tu值,壓入棧9: bipush 10 //將int值10壓入棧11: ldc #44 // String wangerbei 從常量池中取出“wangerbei” 壓入棧13: invokevirtual #46 // Method initUser(ILjava/lang/String;)Lcom/justest/test/User; 調用tu的initUser方法,并返回User對象 ,出棧三個值:tu引用,10和“wangerbei”,并且initUser方法的返回值,即User的引用,也會被壓入棧中,參考前面initUser中的areturn指令16: astore_2 //User引用出棧,賦值給user變量17: aload_1 //取出tu值,壓入棧18: aload_2 //取出user值,壓入棧19: ldc #48 // String lisi 從常量池中取出“lisi”壓入棧21: invokevirtual #50 // Method changeUser:(Lcom/justest/test/User;Ljava/lang/String;)V 調用tu的changeUser方法,并將user引用和lisi傳給這個方法24: return //return voidLineNumberTable:line 23: 0line 24: 8line 25: 17line 26: 24LocalVariableTable:Start Length Slot Name Signature0 25 0 args [Ljava/lang/String;8 17 1 tu Lcom/justest/test/TestUser;17 8 2 user Lcom/justest/test/User;}

總結

1、通過javap命令可以查看一個java類反匯編、常量池、變量表、指令代碼行號表等等信息。

2、平常,我們比較關注的是java類中每個方法的反匯編中的指令操作過程,這些指令都是順序執行的,可以參考官方文檔查看每個指令的含義,很簡單:

https://docs.oracle.com/javase/specs/jvms/se7/html/jvms-6.html#jvms-6.5.areturn

3、通過對前面兩個例子代碼反匯編中各個指令操作的分析,可以發現,一個方法的執行通常會涉及下面幾塊內存的操作:

(1)java棧中:局部變量表、操作數棧。這些操作基本上都值操作。
(2)java堆。通過對象的地址引用去操作。
(3)常量池。
(4)其他如幀數據區、方法區(jdk1.8之前,常量池也在方法區)等部分,測試中沒有顯示出來,這里說明一下。

在做值相關操作時:
一個指令,可以從局部變量表、常量池、堆中對象、方法調用、系統調用中等取得數據,這些數據(可能是指,可能是對象的引用)被壓入操作數棧。
一個指令,也可以從操作數數棧中取出一到多個值(pop多次),完成賦值、加減乘除、方法傳參、系統調用等等操作。


轉載說明

原文作者:王二北
原文鏈接:https://www.jianshu.com/p/6a8997560b05
原文來源:簡書
轉載用途:學習、分享、非商業用途
轉載理由:詳細講解如何使用javap命令分析JVM匯編指令,很有學習價值。

總結

以上是生活随笔為你收集整理的【JVM】通过javap命令分析Java汇编指令的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。