一、javap命令簡述javap是jdk自帶的反解析工具。它的作用就是根據(jù)class字節(jié)碼文件,反解析出當(dāng)前類對應(yīng)的code區(qū)(匯編指令)、本地變量表、異常表和代碼行偏移量映射表、常量池等等信息。 javap的用法格式: -help --help -? 輸出此用法消息
-version 版本信息,其實是當(dāng)前javap所在jdk的版本信息,不是class在哪個jdk下生成的。
-v -verbose 輸出附加信息(包括行號、本地變量表,反匯編等詳細(xì)信息)
-l 輸出行號和本地變量表
-public 僅顯示公共類和成員
-protected 顯示受保護(hù)的/公共類和成員
-package 顯示程序包/受保護(hù)的/公共類 和成員 (默認(rèn))
-p -private 顯示所有類和成員
-c 對代碼進(jìn)行反匯編
-s 輸出內(nèi)部類型簽名
-sysinfo 顯示正在處理的類的系統(tǒng)信息 (路徑, 大小, 日期, MD5 散列)
-constants 顯示靜態(tài)最終常量
-classpath <path> 指定查找用戶類文件的位置
-bootclasspath <path> 覆蓋引導(dǎo)類文件的位置
一般常用的是-v -l -c三個選項。 二、javap測試及內(nèi)容詳解前面已經(jīng)介紹過javap輸出的內(nèi)容有哪些,東西比較多,這里主要介紹其中code區(qū)(匯編指令)、局部變量表和代碼行偏移映射三個部分。 下面寫段代碼測試一下:
上面代碼通過JAVAC -g 生成class文件,然后通過javap命令對字節(jié)碼進(jìn)行反匯編: Warning: Binary file TestDate contains com.justest.test.TestDate
Compiled from 'TestDate.java'
public class com.justest.test.TestDate {
//默認(rèn)的構(gòu)造方法,在構(gòu)造方法執(zhí)行時主要完成一些初始化操作,包括一些成員變量的初始化賦值等操作
public com.justest.test.TestDate();
Code:
0: aload_0 //從本地變量表中加載索引為0的變量的值,也即this的引用,壓入棧
1: invokespecial #10 //出棧,調(diào)用java/lang/Object.'<init>':()V 初始化對象,就是this指定的對象的init()方法完成初始化
4: aload_0 // 4到6表示,調(diào)用this.count = 0,也即為count復(fù)制為0。這里this引用入棧
5: iconst_0 //將常量0,壓入到操作數(shù)棧
6: putfield //出棧前面壓入的兩個值(this引用,常量值0), 將0取出,并賦值給count
9: return
//指令與代碼行數(shù)的偏移對應(yīng)關(guān)系,每一行第一個數(shù)字對應(yīng)代碼行數(shù),第二個數(shù)字對應(yīng)前面code中指令前面的數(shù)字
LineNumberTable:
line 5: 0
line 7: 4
line 5: 9
//局部變量表,start length表示這個變量在字節(jié)碼中的生命周期起始和結(jié)束的偏移位置(this生命周期從頭0到結(jié)尾10),slot就是這個變量在局部變量表中的槽位(槽位可復(fù)用),name就是變量名稱,Signatur局部變量類型描述
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 this Lcom/justest/test/TestDate;
public static void main(java.lang.String[]);
Code:
// new指令,創(chuàng)建一個class com/justest/test/TestDate對象,new指令并不能完全創(chuàng)建一個對象,對象只有在初,只有在調(diào)用初始化方法完成后(也就是調(diào)用了invokespecial指令之后),對象才創(chuàng)建成功,
0: new //創(chuàng)建對象,并將對象引用壓入棧
3: dup //將操作數(shù)棧定的數(shù)據(jù)復(fù)制一份,并壓入棧,此時棧中有兩個引用值
4: invokespecial #20 //pop出棧引用值,調(diào)用其構(gòu)造函數(shù),完成對象的初始化
7: astore_1 //pop出棧引用值,將其(引用)賦值給局部變量表中的變量testDate
8: aload_1 //將testDate的引用值壓入棧,因為testDate.test1();調(diào)用了testDate,這里使用aload_1從局部變量表中獲得對應(yīng)的變量testDate的值并壓入操作數(shù)棧
9: invokevirtual #21 // Method test1:()V 引用出棧,調(diào)用testDate的test1()方法
12: return //整個main方法結(jié)束返回
LineNumberTable:
line 10: 0
line 11: 8
line 12: 12
//局部變量表,testDate只有在創(chuàng)建完成并賦值后,才開始聲明周期
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 args [Ljava/lang/String;
8 5 1 testDate Lcom/justest/test/TestDate;
public void test1();
Code:
0: new #27 // 0到7創(chuàng)建Date對象,并賦值給date變量
3: dup
4: invokespecial #29 // Method java/util/Date.'<init>':()V
7: astore_1
8: ldc #30 // String wangerbei,將常量“wangerbei”壓入棧
10: astore_2 //將棧中的“wangerbei”pop出,賦值給name1
11: aload_0 //11到14,對應(yīng)test2(date,name1);默認(rèn)前面加this.
12: aload_1 //從局部變量表中取出date變量
13: aload_2 //取出name1變量
14: invokevirtual #32 // Method test2: (Ljava/util/Date;Ljava/lang/String;)V 調(diào)用test2方法
// 17到38對應(yīng)System.out.println(date name1);
17: getstatic #36 // Field java/lang/System.out:Ljava/io/PrintStream;
//20到35是jvm中的優(yōu)化手段,多個字符串變量相加,不會兩兩創(chuàng)建一個字符串對象,而使用StringBuilder來創(chuàng)建一個對象
20: new #42 // class java/lang/StringBuilder
23: dup
24: invokespecial #44 // Method java/lang/StringBuilder.'<init>':()V
27: aload_1
28: invokevirtual #45 // Method java/lang/StringBuilder.append:(Ljava/lang/Object;)Ljava/lang/StringBuilder;
31: aload_2
32: 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指令表示基于類調(diào)用方法
41: return
LineNumberTable:
line 15: 0
line 16: 8
line 17: 11
line 18: 17
line 19: 41
LocalVariableTable:
Start Length Slot Name Signature
0 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賦值給dateP
2: ldc #66 // String zhangsan 從常量池中取出字符串“zhangsan”壓入棧中
4: astore_2 //將字符串賦值給name2
5: return
LineNumberTable:
line 22: 0
line 23: 2
line 24: 5
LocalVariableTable:
Start Length Slot Name Signature
0 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 //復(fù)制操作數(shù)棧棧頂?shù)闹担喝霔?,此時有兩個this對象引用值在操作數(shù)組棧
2: getfield #12// Field count:I this出棧,并獲取其count字段,然后壓入棧,此時棧中有一個this和一個count的值
5: iconst_1 //取出一個int常量1,壓入操作數(shù)棧
6: iadd // 從棧中取出count和1,將count值和1相加,結(jié)果入棧
7: putfield #12 // Field count:I 一次彈出兩個,第一個彈出的是上一步計算值,第二個彈出的this,將值賦值給this的count字段
10: return
LineNumberTable:
line 27: 0
line 28: 10
LocalVariableTable:
Start Length Slot Name Signature
0 11 0 this Lcom/justest/test/TestDate;
public void test4();
Code:
0: iconst_0
1: istore_1
2: iconst_0
3: istore_2
4: iload_1
5: iconst_1
6: iadd
7: istore_2
8: iload_1
9: iconst_1
10: iadd
11: istore_2
12: return
LineNumberTable:
line 33: 0
line 35: 2
line 36: 4
line 38: 8
line 39: 12
//看下面,b和c的槽位slot一樣,這是因為b的作用域就在方法塊中,方法塊結(jié)束,局部變量表中的槽位就被釋放,后面的變量就可以復(fù)用這個槽位
LocalVariableTable:
Start Length Slot Name Signature
0 13 0 this Lcom/justest/test/TestDate;
2 11 1 a I
4 4 2 b I
12 1 2 c I
}
例子2:下面一個例子
然后寫一個操作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文件。
三、總結(jié)1、通過javap命令可以查看一個java類反匯編、常量池、變量表、指令代碼行號表等等信息。 2、平常,我們比較關(guān)注的是java類中每個方法的反匯編中的指令操作過程,這些指令都是順序執(zhí)行的,可以參考官方文檔查看每個指令的含義,很簡單: 3、通過對前面兩個例子代碼反匯編中各個指令操作的分析,可以發(fā)現(xiàn),一個方法的執(zhí)行通常會涉及下面幾塊內(nèi)存的操作: (1)java棧中:局部變量表、操作數(shù)棧。這些操作基本上都值操作。 在做值相關(guān)操作時: |
|