文章内容导航:
1.JVM体系结构以及class文件位置:
- 简述:众所周知,我们编写的java文件会被编译成class文件,然后交给JVM来处理。如图:
- 参考官方JVM文档:The Java® Virtual Machine Specification
那么,我们需要知道,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文件,很重要!
祝生活愉快!