文章內容導航:
1.JVM體系結構以及class文件位置:
- 簡述:衆所周知,我們編寫的java文件會被編譯成class文件,然後交給JVM來處理。如圖:
那麼,我們需要知道,class文件裏有什麼東西呢?(這不是廢話麼,當然是我們寫的java程序),話雖如此,他的本質是什麼呢?下面我們一起去探索!!!
2.編寫程序、編譯、打開class文件:
-
編寫一段程序代碼:
TopicString.java
public class TopicString { public static void main(String[] args) { String s1 = "1"; String s2 = new String("1"); String s3 = "1" + "2" + "3"; String s4 = "123"; String s5 = "1" + "3" + new String("1") + "4"; System.out.println(s1 == s2); System.out.println(s1.equals(s2)); System.out.println(s3 == s4); System.out.println(s3.equals(s4)); } }
-
編譯代碼:
cmd
命令輸入javac TopicString.java
得到一個TopicString.class
文件
注意:如果編寫的代碼中有中文,防止編譯亂碼需要:javac -encoding utf-8 xxx.java
-
打開class文件:
編譯後的class內容是十六進制,使用winhex工具打開
讀者也可以用其它的工具打開- 方式一.安裝nodepad++32位或者64位戳我下載,提取碼: ptbr 在
插件管理
安裝Hex-Editor
,打開class文件,選擇:插件 –HEX-Editor
–View in HEX
- 方式二.下載
winhex
戳我下載, 提取碼: 2nuk - 打開文件,筆者截取部分字節碼信息
這是什麼玩意?這就是十六進制碼唄,怎麼看?我能看的懂麼?當然可以,它就像解讀摩斯電碼
好玩,例如下面:發生在電臺甲(s1)和電臺乙(s2)之間的通訊s1:CQ CQ CQ de s1 K s2:s1 de s2 K s1:SK s2:SK 他們在說什麼呢? CQ == 呼叫任何人,de == 這是,k = 結束,SK == 再見; 所以第一句就是: s1:“呼叫,呼叫,呼叫,這裏是s1,結束!”。同理: s2:“s1,這裏是s2,結束!”。 s1:“再見”。 s2:“再見”。
- 方式一.安裝nodepad++32位或者64位戳我下載,提取碼: ptbr 在
3.怎麼解讀class文件中十六進制信息?
3.1.初步理解解讀class文件組成圖[官方圖]:
-
解讀class文件的規則:官方地址這裏給個截圖,官方對每個屬性都有解讀,but!對新手不太友好:
截圖中內容什麼意思呢?又該怎麼看呢?u4 magic; // u4代表4個字節,magic是魔數值;魔數值理解,例如:寫信以‘親愛的xxx’開頭 對於class文件來說,文件內容的開頭須是【CA FE BA BE】 開頭,這不是 咖啡寶貝?對的,要不java怎麼會是冒熱氣的咖啡杯?? u2 minor_version // u2代表兩2個字節,minor_version是JDK次要版本;意思就是: 往下數2個字節【00 00】,這2個字節表示的是JDK次要版本 u2 major_version // u2代表兩2個字節,major_version是JDK主要版本;意思就是: 再往下數2個字節【00 34】,這2個字節表示的是JDK主要版本。 十六進制的【00 34】轉爲十進制值等於:52。根據下圖得知52對應JDK8 同理...... 下面就繼續解讀class文件其它部分咯!!
3.2.繼續解讀代碼示例class文件內容【難點】:
-
解讀class文件需要有耐心哦,咬牙堅持吧
u2 constant_pool_count; // u2代表兩2個字節,constant_pool_count常量池數據總計數 往下數2字節【00 3C】轉爲十進制值等於:60。 【理解難點】: cp_info constant_pool[constant_pool_count-1]; // 常量池‘表結構’總數,怎麼理解? // 因爲constant_pool_count = 60;所以這裏等價:cp_info constant_pool[59]; // 怎麼理解?官方的ClassFile結構其實就好像一個JSON對象一樣: { "u4": "magic", // 魔數值 "u2": "minor_version", // JDK次要版本 "u2": "major_version", // JDK主要版本 "u2": "constant_pool_count", // 常量池數據總計數 "cp_info": [{ // cp_info表結構對象,裏面有59個表結構對象 "cp_info1":[ ], ...... ...... "cp_info59":[ ] }], "u2":"access_flags", ... ... 省略其它... }
-
官方cp_info表結構對象長這樣戳我直達
因此ClassFile現在變成這樣:{ "u4": "magic", // 魔數值 "u2": "minor_version", // JDK次要版本 "u2": "major_version", // JDK主要版本 "u2": "constant_pool_count", // 常量池數據總計數 "cp_info": [{ // cp_info表結構對象,裏面有59個表結構對象 "cp_info1":[{ "u1":"tag", "u1":"info[]" }], ...... ...... "cp_info59":[{ "u1":"tag", "u1":"info[]" }], }], "u2":"access_flags", ... ... 省略其它... }
-
理解
cp_info
結構內容:u1 tag:標籤;不同的標籤對應的不同表結構
u1 info[]:表結構;這個表結構需要根據tag值去查看對應表結構;鏈接:查看標籤值對應的表結構常量池中表結構都以tag開頭,佔1個字節,解讀第一個tag:
u1 tag; // 常量池表結構標籤值,往下數1個字節,得到: 十六進制【0a】對應十進制值等於:10
-
根據
tag
標籤值,找到對應的常量池表結構,戳我查看官方tag值對應的表結構:有以下表類型
-
查看
tag標籤值=10
對應的CONSTANT_Methodref_info表類型結構
鏈接:戳我查看CONSTANT_Methodref_info { // 方法引用表結構 u1 tag; // 這個tag等價上面的tag u2 class_index; // 所屬類下標索引 u2 name_and_type_index; // 這裏指初始化方法類型索引(見官方解釋) } 得知第一個tag後,ClassFile變成如下: { "u4": "magic", // 魔數值 [ca fe ba be] "u2": "minor_version", // JDK次要版本 [00 00] "u2": "major_version", // JDK主要版本 [00 34] "u2": "constant_pool_count", // 常量池數據總計數 [00 3c] "cp_info": [{ // cp_info表結構對象,裏面有59個表結構對象 "cp_info1":[{ "u1":"tag", // 標籤值 [0a] "u2":"class_index", // 所屬類下標索引 [00 10] "u2":"name_and_type_index" // 初始化方法類型索引 [00 1d] }], ...... ...... "cp_info59":[{ "u1":"tag", "u1":"info[]" }], }], "u2":"access_flags", ... ... 省略其它... }
3.2.1 解讀注意問題:
-
常量池
CONSTANT_Utf8_info
類型的表結構:CONSTANT_Utf8_info { u1 tag; // 常量池表結構標籤 u2 length; // 往下數length個字節,十六進制[00 06] 對應十進制值:6 往下數6個字節,得到:[3c 69 6e 69 74 3e] u1 bytes[length]; // bytes[6],將則6個十六進制值轉成字符串,怎麼轉換呢? }
- 對照碼錶:戳我查看碼錶,依次搜索
3c、69、6e、69、74、3e
組裝結果爲:<init>
- java程序轉換:將十六進制複製運行即可:
public class EncodeConversionUtils { /** * 字符UTF8串轉16進制字符串 * * @param strPart 字符 * @return 16進制字符串 */ public static String string2Hexit8(String strPart) { return string2HexString(strPart, "UTF-8"); } public static String string2HexString(String strPart, String teletype) { try { return bytes2HexString(strPart.getBytes(teletype)); } catch (Exception e) { return ""; } } /** * 字節處理 * * @param b 字節信息 * @return 字符串 */ public static String bytes2HexString(byte[] b) { StringBuilder result = new StringBuilder(); for (byte value : b) { result.append(String.format("%02X", value)); } return result.toString(); } /** * @param src 16進制字符串 * @return 字節數組 */ public static byte[] hexString2Bytes(String src) { int l = src.length() / 2; byte[] ret = new byte[l]; for (int i = 0; i < l; i++) { ret[i] = Integer.valueOf(src.substring(i * 2, i * 2 + 2), 16).byteValue(); } return ret; } /** * 16進制字符串轉字符串 * * @param src 16進制字符串 * @return 字節數組 */ public static String hexString2String(String src, String oldCharType, String charType) { byte[] bts = hexString2Bytes(src); try { if (oldCharType.equals(charType)) { return new String(bts, oldCharType); } else { return new String(new String(bts, oldCharType).getBytes(), charType); } } catch (Exception e) { return ""; } } /** * 16進制UTF-8字符串轉字符串 * * @param src 16進制字符串 * @return 字節數組 */ public static String hexConvertUtf8(String src) { // 去除中間的空格 return hexString2String(src.replaceAll(" ", ""), "UTF-8", "UTF-8"); } public static void main(String[] args) { System.out.println(EncodeConversionUtils.hexConvertUtf8("3c 69 6e 69 74 3e")); // 輸出結果:<init> } }
- 對照碼錶:戳我查看碼錶,依次搜索
-
筆者在解讀文件中也有說明:截圖表示一下吧
4.解讀文件下載:
- 百度網盤:戳我下載class解讀明細提取碼: 5fpw;給個截圖:
5.使用官方命令反編譯class文件:哦!原來是這樣的!
-
他哥的!解讀熱情過去了,也知道怎麼解讀了,看看官方給的反編譯:
-
命令
javap -c
或者javap -v
讀者自己輸出看看結果 -
小編結果展示:
Classfile /C:/Users/Administrator/Desktop/TopicString.class Last modified 2020-5-9; size 947 bytes MD5 checksum 23a3a4ca6f73d56aa128c3713038f48f Compiled from "TopicString.java" public class TopicString minor version: 0 major version: 52 flags: ACC_PUBLIC, ACC_SUPER Constant pool: #1 = Methodref #16.#29 // java/lang/Object."<init>":()V #2 = String #30 // 1 #3 = Class #31 // java/lang/String #4 = Methodref #3.#32 // java/lang/String."<init>":(Ljava/lang/String;)V #5 = String #33 // 123 #6 = Class #34 // java/lang/StringBuilder #7 = Methodref #6.#29 // java/lang/StringBuilder."<init>":()V #8 = String #35 // 13 #9 = Methodref #6.#36 // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #10 = String #37 // 4 #11 = Methodref #6.#38 // java/lang/StringBuilder.toString:()Ljava/lang/String; #12 = Fieldref #39.#40 // java/lang/System.out:Ljava/io/PrintStream; #13 = Methodref #41.#42 // java/io/PrintStream.println:(Z)V #14 = Methodref #3.#43 // java/lang/String.equals:(Ljava/lang/Object;)Z #15 = Class #44 // TopicString #16 = Class #45 // java/lang/Object #17 = Utf8 <init> #18 = Utf8 ()V #19 = Utf8 Code #20 = Utf8 LineNumberTable #21 = Utf8 main #22 = Utf8 ([Ljava/lang/String;)V #23 = Utf8 StackMapTable #24 = Class #46 // "[Ljava/lang/String;" #25 = Class #31 // java/lang/String #26 = Class #47 // java/io/PrintStream #27 = Utf8 SourceFile #28 = Utf8 TopicString.java #29 = NameAndType #17:#18 // "<init>":()V #30 = Utf8 1 #31 = Utf8 java/lang/String #32 = NameAndType #17:#48 // "<init>":(Ljava/lang/String;)V #33 = Utf8 123 #34 = Utf8 java/lang/StringBuilder #35 = Utf8 13 #36 = NameAndType #49:#50 // append:(Ljava/lang/String;)Ljava/lang/StringBuilder; #37 = Utf8 4 #38 = NameAndType #51:#52 // toString:()Ljava/lang/String; #39 = Class #53 // java/lang/System #40 = NameAndType #54:#55 // out:Ljava/io/PrintStream; #41 = Class #47 // java/io/PrintStream #42 = NameAndType #56:#57 // println:(Z)V #43 = NameAndType #58:#59 // equals:(Ljava/lang/Object;)Z #44 = Utf8 TopicString #45 = Utf8 java/lang/Object #46 = Utf8 [Ljava/lang/String; #47 = Utf8 java/io/PrintStream #48 = Utf8 (Ljava/lang/String;)V #49 = Utf8 append #50 = Utf8 (Ljava/lang/String;)Ljava/lang/StringBuilder; #51 = Utf8 toString #52 = Utf8 ()Ljava/lang/String; #53 = Utf8 java/lang/System #54 = Utf8 out #55 = Utf8 Ljava/io/PrintStream; #56 = Utf8 println #57 = Utf8 (Z)V #58 = Utf8 equals #59 = Utf8 (Ljava/lang/Object;)Z { public TopicString(); 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 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=4, locals=6, args_size=1 0: ldc #2 // String 1 2: astore_1 3: new #3 // class java/lang/String 6: dup 7: ldc #2 // String 1 9: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 12: astore_2 13: ldc #5 // String 123 15: astore_3 16: ldc #5 // String 123 18: astore 4 20: new #6 // class java/lang/StringBuilder 23: dup 24: invokespecial #7 // Method java/lang/StringBuilder."<init>":()V 27: ldc #8 // String 13 29: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 32: new #3 // class java/lang/String 35: dup 36: ldc #2 // String 1 38: invokespecial #4 // Method java/lang/String."<init>":(Ljava/lang/String;)V 41: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 44: ldc #10 // String 4 46: invokevirtual #9 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 49: invokevirtual #11 // Method java/lang/StringBuilder.toString:()Ljava/lang/String; 52: astore 5 54: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 57: aload_1 58: aload_2 59: if_acmpne 66 62: iconst_1 63: goto 67 66: iconst_0 67: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 70: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 73: aload_1 74: aload_2 75: invokevirtual #14 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 78: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 81: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 84: aload_3 85: aload 4 87: if_acmpne 94 90: iconst_1 91: goto 95 94: iconst_0 95: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 98: getstatic #12 // Field java/lang/System.out:Ljava/io/PrintStream; 101: aload_3 102: aload 4 104: invokevirtual #14 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 107: invokevirtual #13 // Method java/io/PrintStream.println:(Z)V 110: return LineNumberTable: line 3: 0 line 4: 3 line 5: 13 line 6: 16 line 7: 20 line 9: 54 line 10: 70 line 11: 81 line 12: 98 line 13: 110 StackMapTable: number_of_entries = 4 frame_type = 255 /* full_frame */ offset_delta = 66 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream, int ] frame_type = 90 /* same_locals_1_stack_item */ stack = [ class java/io/PrintStream ] frame_type = 255 /* full_frame */ offset_delta = 0 locals = [ class "[Ljava/lang/String;", class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String, class java/lang/String ] stack = [ class java/io/PrintStream, int ] } SourceFile: "TopicString.java"
6.本文的案例對應的虛擬機指令解讀
請見筆者文章:
一定,一定,一定要學會自己解讀class文件,很重要!
祝生活愉快!