文章内容导航:

1.JVM体系结构以及class文件位置:

2.编写程序、编译、打开class文件:

3.怎么解读class文件中十六进制信息?

  • 3.1.初步理解解读class文件组成图[官方图]:
  • 3.2.继续解读代码示例class文件内容【难点】:
  • 3.2.1 解读注意问题:
  • 4.解读文件下载:
  • 5.使用官方命令反编译class文件:哦!原来是这样的!
  • 6.本文的案例对应的`虚拟机指令解读`请见笔者文章:

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:“再见”。

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文件其它部分咯!!

java 如何优雅的进行class 转换成另外一个class java怎么转成class_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 解读注意问题:
  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个十六进制值转成字符串,怎么转换呢?
}

java 如何优雅的进行class 转换成另外一个class java怎么转成class_jvm_02

  • 对照码表:戳我查看码表,依次搜索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>
	    }
	}
  1. 笔者在解读文件中也有说明:截图表示一下吧

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"