编写一段程序代码: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工具打开
读者也可以用其它的工具打开
插件管理
安装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:“再见”。
解读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文件其它部分咯!!
解读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标签值=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",
...
...
省略其它...
}
常量池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
组装结果为:
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"));
// 输出结果:
}
}
他哥的!解读热情过去了,也知道怎么解读了,看看官方给的反编译:
命令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."":()V
#2 = String #30 // 1
#3 = Class #31 // java/lang/String
#4 = Methodref #3.#32 // java/lang/String."":(Ljava/lang/String;)V
#5 = String #33 // 123
#6 = Class #34 // java/lang/StringBuilder
#7 = Methodref #6.#29 // java/lang/StringBuilder."":()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 // "":()V
#30 = Utf8 1
#31 = Utf8 java/lang/String
#32 = NameAndType #17:#48 // "":(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."":()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."":(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."":()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."":(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"
虚拟机指令解读
请见笔者文章:一定,一定,一定要学会自己解读class文件,很重要!
祝生活愉快!