JVM字节码解析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/singgel/article/details/83268918

代码不想写,我拿什么拯救你,copy吧,少年:https://github.com/singgel/eight-sorting-algorithms/tree/master/src/test/java/com/hks/eightsortingalgorithms/script

Write Once,Run Anywhere——byteCode
byteCode 平台无关 
java通过存储编译后的字节码,并将字码加载到JVM中,实现了java语言的跨平台。字节码是平台中立的代码存储格式,任意一个平台只要安装了JRE(跟平台有关),那么程序就是可以运行的。 


byteCode 语言无关 
除此之外,更值得注意的是字节码不仅是平台无关的,也同样是语言无关的,并不是说只有java语言才能生成byteCode,byteCode比java语言有着更强的描述性,而且JVM不与语言进行绑定,所以其他语言(Jython,groovy等),也是可以通过编译为byteCode运行在JVM中的。 

è¿éåå¾çæè¿°

1.Class文件基础


(1)文件格式

Class文件的结构不像XML等描述语言那样松散自由。由于它没有任何分隔符号,
所以,以上数据项无论是顺序还是数量都是被严格限定的。哪个字节代表什么
含义,长度是多少,先后顺序如何,都不允许改变。

(2)数据类型

仔细观察上面的Class文件格式,可以看出Class文件格式采用一种类似于C语言
结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表。
无符号数就是u1、u2、u4、u8来分别代表1个、2个、4个、8个字节。表是由
多个无符号数或其他表构成的复合数据类型,以“_info”结尾。在表开始位置,
通常会使用一个前置的容量计数器,因为表通常要描述数量不定的多个数据。

下图表示的就是Class文件格式中按顺序各个数据项的类型:

(3)兼容性

高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,
即使文件格式未发生任何变化。举例来说,JDK 1.7中的JRE能够执行JDK 1.5编译
出的Class文件,但是JDK 1.7编译出来的Class文件不能被JDK 1.5使用。这就是
target参数的用处,可以在使用JDK 1.7编译时指定-target 1.5。


2.一个简单的例子

package com.hks.eightsortingalgorithms.asm;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class Hello {
    public static final String FLAG = "我是常量";

    public Hello() {
    }

    public void display() {
        for(int var1 = 0; var1 < 8; ++var1) {
            System.out.println(">>>>>>>>>>我是常量");
        }

    }

    public List<String> testList() {
        ArrayList var1 = new ArrayList();
        var1.add("Tome");
        var1.add("Jack");
        var1.add("Lily");
        System.out.println(">>>>>>>>>>testList > list.size = " + var1.size());
        return var1;
    }

    public List<Map<String, String>> testMapList(boolean var1, Map... var2) {
        ArrayList var3 = new ArrayList();
        if (var1) {
            Map[] var7 = var2;
            int var6 = var2.length;

            for(int var5 = 0; var5 < var6; ++var5) {
                Map var4 = var7[var5];
                var3.add(var4);
            }
        }

        System.out.println(">>>>>>>>>>testMapList > list.size = " + var3.size());
        return var3;
    }
}


编译成Class文件后的样子:


3.逐个字节分析

(1)魔数和版本号

前四个字节(u4)cafebabe就是Class文件的魔数,第5、6字节(u2)是Class文件的
次版本号,第7、8字节(u2)是主版本号。十六进制0和32,也就是版本号为50.0,
即JDK 1.6。之前介绍的target参数会影响这四个字节的值,从而使Class文件兼容不同
的JDK版本。

(2)常量池

常量池是一个表结构,并且就像之前介绍过的,在表的内容前有一个u2类型的计数器,
表示常量池的长度。十六进制23的十进制值为35,表示常量池里有下标为1~34的表项。
下标从1开始而不是0,是因为第0个表项表示“不引用常量池中的任意一项”。每个表项
的第一个字节是一个u1类型,表示12中数据类型。具体含义如下:

以第一项07 00 02为例,07表示该常量是个CONSTANT_Class_info类型,紧接着一个u2
类型的索引执行第2项常量。再看第二项01 00 24 63 6f 6d 2f ... 65表示的就是字符串
类型,长度为36(十六进制00 24),紧接着就是UTF-8编码的字符串"com/hks/eightsortingalgorithms/asm/hello/Hello"。很容易读懂吧?常量池主要是为后面的字段表和方法表服务的。

下面是通过javap解析后常量池的全貌(执行javap -c -l -s -verbose Hello)

Classfile /Users/yiche/github/eight-sorting-algorithms/src/test/java/com/hks/eightsortingalgorithms/asm/hello/Hello.class
  Last modified Oct 19, 2018; size 1422 bytes
  MD5 checksum 060a2add21cb5569a11f8949de66a50a
  Compiled from "Hello.java"
public class com.hks.eightsortingalgorithms.asm.Hello
  minor version: 0
  major version: 51
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Utf8               com/hks/eightsortingalgorithms/asm/Hello
   #2 = Class              #1             // com/hks/eightsortingalgorithms/asm/Hello
   #3 = Utf8               java/lang/Object
   #4 = Class              #3             // java/lang/Object
   #5 = Utf8               Hello.java
   #6 = Utf8               FLAG
   #7 = Utf8               Ljava/lang/String;
   #8 = Utf8               我是常量
   #9 = String             #8             // 我是常量
  #10 = Utf8               <init>
  #11 = Utf8               ()V
  #12 = NameAndType        #10:#11        // "<init>":()V
  #13 = Methodref          #4.#12         // java/lang/Object."<init>":()V
  #14 = Utf8               display
  #15 = Utf8               java/lang/System
  #16 = Class              #15            // java/lang/System
  #17 = Utf8               out
  #18 = Utf8               Ljava/io/PrintStream;
  #19 = NameAndType        #17:#18        // out:Ljava/io/PrintStream;
  #20 = Fieldref           #16.#19        // java/lang/System.out:Ljava/io/PrintStream;
  #21 = Utf8               >>>>>>>>>>我是常量
  #22 = String             #21            // >>>>>>>>>>我是常量
  #23 = Utf8               java/io/PrintStream
  #24 = Class              #23            // java/io/PrintStream
  #25 = Utf8               println
  #26 = Utf8               (Ljava/lang/String;)V
  #27 = NameAndType        #25:#26        // println:(Ljava/lang/String;)V
  #28 = Methodref          #24.#27        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #29 = Utf8               testList
  #30 = Utf8               ()Ljava/util/List;
  #31 = Utf8               ()Ljava/util/List<Ljava/lang/String;>;
  #32 = Utf8               java/util/ArrayList
  #33 = Class              #32            // java/util/ArrayList
  #34 = Methodref          #33.#12        // java/util/ArrayList."<init>":()V
  #35 = Utf8               Tome
  #36 = String             #35            // Tome
  #37 = Utf8               java/util/List
  #38 = Class              #37            // java/util/List
  #39 = Utf8               add
  #40 = Utf8               (Ljava/lang/Object;)Z
  #41 = NameAndType        #39:#40        // add:(Ljava/lang/Object;)Z
  #42 = InterfaceMethodref #38.#41        // java/util/List.add:(Ljava/lang/Object;)Z
  #43 = Utf8               Jack
  #44 = String             #43            // Jack
  #45 = Utf8               Lily
  #46 = String             #45            // Lily
  #47 = Utf8               java/lang/StringBuilder
  #48 = Class              #47            // java/lang/StringBuilder
  #49 = Utf8               >>>>>>>>>>testList > list.size =
  #50 = String             #49            // >>>>>>>>>>testList > list.size =
  #51 = NameAndType        #10:#26        // "<init>":(Ljava/lang/String;)V
  #52 = Methodref          #48.#51        // java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
  #53 = Utf8               size
  #54 = Utf8               ()I
  #55 = NameAndType        #53:#54        // size:()I
  #56 = InterfaceMethodref #38.#55        // java/util/List.size:()I
  #57 = Utf8               append
  #58 = Utf8               (I)Ljava/lang/StringBuilder;
  #59 = NameAndType        #57:#58        // append:(I)Ljava/lang/StringBuilder;
  #60 = Methodref          #48.#59        // java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
  #61 = Utf8               toString
  #62 = Utf8               ()Ljava/lang/String;
  #63 = NameAndType        #61:#62        // toString:()Ljava/lang/String;
  #64 = Methodref          #48.#63        // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #65 = Utf8               testMapList
  #66 = Utf8               (Z[Ljava/util/Map;)Ljava/util/List;
  #67 = Utf8               (Z[Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;)Ljava/util/List<Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;>;
  #68 = Utf8               [Ljava/util/Map;
  #69 = Class              #68            // "[Ljava/util/Map;"
  #70 = Utf8               >>>>>>>>>>testMapList > list.size =
  #71 = String             #70            // >>>>>>>>>>testMapList > list.size =
  #72 = Utf8               ConstantValue
  #73 = Utf8               Code
  #74 = Utf8               StackMapTable
  #75 = Utf8               Signature
  #76 = Utf8               SourceFile
{
  public static final java.lang.String FLAG;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String 我是常量

  public com.hks.eightsortingalgorithms.asm.Hello();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #13                 // Method java/lang/Object."<init>":()V
         4: return

  public void display();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: iconst_0
         1: istore_1
         2: goto          16
         5: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
         8: ldc           #22                 // String >>>>>>>>>>我是常量
        10: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        13: iinc          1, 1
        16: iload_1
        17: bipush        8
        19: if_icmplt     5
        22: return
      StackMapTable: number_of_entries = 2
        frame_type = 252 /* append */
          offset_delta = 5
          locals = [ int ]
        frame_type = 10 /* same */

  public java.util.List<java.lang.String> testList();
    descriptor: ()Ljava/util/List;
    flags: ACC_PUBLIC
    Code:
      stack=4, locals=2, args_size=1
         0: new           #33                 // class java/util/ArrayList
         3: dup
         4: invokespecial #34                 // Method java/util/ArrayList."<init>":()V
         7: astore_1
         8: aload_1
         9: ldc           #36                 // String Tome
        11: invokeinterface #42,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        16: pop
        17: aload_1
        18: ldc           #44                 // String Jack
        20: invokeinterface #42,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        25: pop
        26: aload_1
        27: ldc           #46                 // String Lily
        29: invokeinterface #42,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        34: pop
        35: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
        38: new           #48                 // class java/lang/StringBuilder
        41: dup
        42: ldc           #50                 // String >>>>>>>>>>testList > list.size =
        44: invokespecial #52                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        47: aload_1
        48: invokeinterface #56,  1           // InterfaceMethod java/util/List.size:()I
        53: invokevirtual #60                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        56: invokevirtual #64                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        59: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        62: aload_1
        63: areturn
    Signature: #31                          // ()Ljava/util/List<Ljava/lang/String;>;

  public java.util.List<java.util.Map<java.lang.String, java.lang.String>> testMapList(boolean, java.util.Map<java.lang.String, java.lang.String>...);
    descriptor: (Z[Ljava/util/Map;)Ljava/util/List;
    flags: ACC_PUBLIC, ACC_VARARGS
    Code:
      stack=4, locals=8, args_size=3
         0: new           #33                 // class java/util/ArrayList
         3: dup
         4: invokespecial #34                 // Method java/util/ArrayList."<init>":()V
         7: astore_3
         8: iload_1
         9: ifeq          51
        12: aload_2
        13: dup
        14: astore        7
        16: arraylength
        17: istore        6
        19: iconst_0
        20: istore        5
        22: goto          44
        25: aload         7
        27: iload         5
        29: aaload
        30: astore        4
        32: aload_3
        33: aload         4
        35: invokeinterface #42,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
        40: pop
        41: iinc          5, 1
        44: iload         5
        46: iload         6
        48: if_icmplt     25
        51: getstatic     #20                 // Field java/lang/System.out:Ljava/io/PrintStream;
        54: new           #48                 // class java/lang/StringBuilder
        57: dup
        58: ldc           #71                 // String >>>>>>>>>>testMapList > list.size =
        60: invokespecial #52                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/String;)V
        63: aload_3
        64: invokeinterface #56,  1           // InterfaceMethod java/util/List.size:()I
        69: invokevirtual #60                 // Method java/lang/StringBuilder.append:(I)Ljava/lang/StringBuilder;
        72: invokevirtual #64                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        75: invokevirtual #28                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        78: aload_3
        79: areturn
      StackMapTable: number_of_entries = 3
        frame_type = 255 /* full_frame */
          offset_delta = 25
          locals = [ class com/hks/eightsortingalgorithms/asm/Hello, int, class "[Ljava/util/Map;", class java/util/List, top, int, int, class "[Ljava/util/Map;" ]
          stack = []
        frame_type = 18 /* same */
        frame_type = 255 /* full_frame */
          offset_delta = 6
          locals = [ class com/hks/eightsortingalgorithms/asm/Hello, int, class "[Ljava/util/Map;", class java/util/List ]
          stack = []
    Signature: #67                          // (Z[Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;)Ljava/util/List<Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;>;
}
SourceFile: "Hello.java"

         

(3)访问标志

显然,00 21表示的就是公有的类。

(4)类、父类、接口

这三个u2类型的值分别表示类索引1、父类索引3、接口索引集合0。查看之前的常量池,
第1项为"com/cdai/jvm/bytecode/ByteCodeSample",第3项为"java/lang/Object"。第0项
表示此类没有实现任何接口,这也就是常量池第0项的作用!

(5)字段表

00 01表示有1个字段。00 02是字段的访问标志,表示private权限的。00 05是字段的名称
索引,指向常量池里第5项"msg"。00 06是字段的描述符索引,指向常量池里的第6项
"Ljava/lang/String"。最后的00 00表示该字段没有其他属性表了。

描述符的作用就是用来描述字段的数据类型、方法的参数列表和返回值。而属性表就是为
字段表和方法表提供额外信息的表结构。对于字段来说,此处如果将字段声明为一个static
final msg = "aaa"的常量,则字段后就会跟着一个属性表,其中存在一项名为ConstantValue,
指向常量池中的一个常量,值为的"aaa"。

属性表不像Class文件中的其他数据项那样具有严格的顺序、长度和内容,任何人实现的编译器
都可以向属性表中写入自己定义的属性信息,JVM会忽略掉它不认识的属性。后面的方法表中
还要用到属性表的Code属性,来保存方法的字节码。

(6)方法表

00 02表示有两个方法。00 01是方法的访问标志,表示公有方法。00 07和00 08与字段表中的名称
和描述符索引相同,在这里分别表示"<init>"和"()V"。00 01表示该方法有属性表,属性名称为00 09
即我们前面提到的Code属性。

要注意的是:Code属性表也可以有自己的属性,如后面的LocalVariableTable和LineNumberTable。
它们分别为JVM提供方法的栈信息和调试信息。

猜你喜欢

转载自blog.csdn.net/singgel/article/details/83268918