java之解读class字节码文件,打开JAVA虚拟机(JVM)大门

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

    在这里插入图片描述

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个十六进制值转成字符串,怎么转换呢?
    }
    

    在这里插入图片描述

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

4.解读文件下载:

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文件,很重要!
祝生活愉快!

原创文章 19 获赞 109 访问量 19万+

猜你喜欢

转载自blog.csdn.net/qq_40670946/article/details/105970527