美文网首页
JVM-从字节码到运行时(1)

JVM-从字节码到运行时(1)

作者: 甜甜起司猫_ | 来源:发表于2021-04-17 03:00 被阅读0次

    JVM-从字节码到运行时(1)

    一切从javap -verbose开始

    希望借此文章将Class文件结构和运行时的知识点串联起来


    先来段代码:

    
    public class ByteCodeDemo {
    
        private String s = "abc";
        private int a = 100;
        private final static int b = 200;
    
        public int add(int z) {
            int c = 300;
            return (a + b + c) * z;
        }
    
        public static int getB() {
            return b;
        }
    }
    
    

    使用javap -verbose反编译Class文件得出以下Class文件结构:

    
      Last modified 2021-1-18; size 427 bytes
      MD5 checksum b811f62df11dd920ae644458a9c24459
      Compiled from "ByteCodeDemo.java"
    public class ByteCodeDemo
      minor version: 0
      major version: 52
      flags: ACC_PUBLIC, ACC_SUPER
    Constant pool:
       #1 = Methodref          #6.#24         // java/lang/Object."<init>":()V
       #2 = String             #25            // abc
       #3 = Fieldref           #5.#26         // classone/ByteCodeDemo.s:Ljava/lang/String;
       #4 = Fieldref           #5.#27         // classone/ByteCodeDemo.a:I
       #5 = Class              #28            // classone/ByteCodeDemo
       #6 = Class              #29            // java/lang/Object
       #7 = Utf8               s
       #8 = Utf8               Ljava/lang/String;
       #9 = Utf8               a
      #10 = Utf8               I
      #11 = Utf8               b
      #12 = Utf8               ConstantValue
      #13 = Integer            200
      #14 = Utf8               <init>
      #15 = Utf8               ()V
      #16 = Utf8               Code
      #17 = Utf8               LineNumberTable
      #18 = Utf8               add
      #19 = Utf8               (I)I
      #20 = Utf8               getB
      #21 = Utf8               ()I
      #22 = Utf8               SourceFile
      #23 = Utf8               ByteCodeDemo.java
      #24 = NameAndType        #14:#15        // "<init>":()V
      #25 = Utf8               abc
      #26 = NameAndType        #7:#8          // s:Ljava/lang/String;
      #27 = NameAndType        #9:#10         // a:I
      #28 = Utf8               classone/ByteCodeDemo
      #29 = Utf8               java/lang/Object
    {
      public classone.ByteCodeDemo();
        descriptor: ()V
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: ldc           #2                  // String abc
             7: putfield      #3                  // Field s:Ljava/lang/String;
            10: aload_0
            11: bipush        100
            13: putfield      #4                  // Field a:I
            16: return
          LineNumberTable:
            line 3: 0
            line 5: 4
            line 6: 10
    
      public int add(int);
        descriptor: (I)I
        flags: ACC_PUBLIC
        Code:
          stack=2, locals=3, args_size=2
             0: sipush        300
             3: istore_2
             4: aload_0
             5: getfield      #4                  // Field a:I
             8: sipush        200
            11: iadd
            12: iload_2
            13: iadd
            14: iload_1
            15: imul
            16: ireturn
          LineNumberTable:
            line 10: 0
            line 11: 4
    
      public static int getB();
        descriptor: ()I
        flags: ACC_PUBLIC, ACC_STATIC
        Code:
          stack=1, locals=0, args_size=0
             0: sipush        200
             3: ireturn
          LineNumberTable:
            line 15: 0
    }
    
    
    
    

    Class文件结构

    根据《JAVA虚拟机规范》中定义,Class文件结构采用一种C语言结构体的伪结构来存储数据。这种伪结构中包括两种类型:无符号数和表。

    • 无符号数属于基本数据类型,表示长度:u1为1个字节,u2为2个字节,u4为4个字节,u8为8个字节。
    • 表是用于描述有层次关系的复合数据结构,数据项按严格的结构顺序组成。

    (此处应有图)

    结合图中所示的数据项和例子中反编译的Class文件内容,按顺序进行解析:

      minor version: 0 //次版本号
      major version: 52 //主版本号
    

    主版本号与次版本号组合起来主版本号.次版本号表示编译此Class文件的jdk版本号。在这个例子里,java文件是由版本52.0来编译的,也就是JDK8。

    flags: ACC_PUBLIC, ACC_SUPER //access_flags,访问标识
    

    flags表示该类的访问标志,其中

    • ACC_PUBLIC 表示该类为PUBLIC类型
    • ACC_SUPER 是否允许使用invokespecial字节码指令,JDK1.0.2之后该值都为真
    
    Constant pool:
       #1 = Methodref          #6.#24         // java/lang/Object."<init>":()V
       #2 = String             #25            // abc
       #3 = Fieldref           #5.#26         // classone/ByteCodeDemo.s:Ljava/lang/String;
       #4 = Fieldref           #5.#27         // classone/ByteCodeDemo.a:I
       #5 = Class              #28            // classone/ByteCodeDemo
       #6 = Class              #29            // java/lang/Object
       #7 = Utf8               s
       #8 = Utf8               Ljava/lang/String;
       #9 = Utf8               a
      #10 = Utf8               I
      #11 = Utf8               b
      #12 = Utf8               ConstantValue
      #13 = Integer            200
      #14 = Utf8               <init>
      #15 = Utf8               ()V
      #16 = Utf8               Code
      #17 = Utf8               LineNumberTable
      #18 = Utf8               add
      #19 = Utf8               (I)I
      #20 = Utf8               getB
      #21 = Utf8               ()I
      #22 = Utf8               SourceFile
      #23 = Utf8               ByteCodeDemo.java
      #24 = NameAndType        #14:#15        // "<init>":()V
      #25 = Utf8               abc
      #26 = NameAndType        #7:#8          // s:Ljava/lang/String;
      #27 = NameAndType        #9:#10         // a:I
      #28 = Utf8               classone/ByteCodeDemo
      #29 = Utf8               java/lang/Object
    
    

    Class文件常量池

    Constant pool表示常量池入口。常量池中主要存放两大类常量:字面量符号引用

    • 字面量接近于常量概念,比如字符串、被声明为final的常量值等。
    • 符号引用则属于编译原理方面的概念,主要包括以下几类常量:
    1. 被模块导出或者开放的包
    2. 类和接口的全限定名
       #5 = Class              #23            // ByteCodeDemo
       #6 = Class              #24            // java/lang/Object
    
    1. 字段的名称和描述符
       // 字符串常量
       #2 = String             #25            // abc
       // 字段引用
       #3 = Fieldref           #5.#26         // classone/ByteCodeDemo.s:Ljava/lang/String;
       #4 = Fieldref           #5.#27         // classone/ByteCodeDemo.a:I
    
       // 字段名称
       // 字段类型
       #7 = Utf8               s
       #8 = Utf8               Ljava/lang/String;
       #9 = Utf8               a
      #10 = Utf8               I
    
      // 被final修饰的字段
      #11 = Utf8               b
      #12 = Utf8               ConstantValue
      #13 = Integer            200
    
    1. 方法的名称和描述符

    方法名+返回值类型

      #18 = Utf8               add
      #19 = Utf8               (I)I
      #20 = Utf8               getB
      #21 = Utf8               ()I
    
    1. 方法句柄和方法类型
    2. 动态调用点和动态常量

    上面说到

    Constant pool表示常量池入口。

    这里为什么说是入口呢,因为Class文件里,可以理解为保存的是静态的内容,而各个方法、字段的符号引用要在类加载时才得到真正的内存入口。相对的,动态的内容,就指的是运行时常量池了。

    运行时常量池

    运行时常量池,存放的是Class文件中的常量池经过类加载后的内容,存放这部分内容的地方就是Metaspace。这里提到类加载,先简单和类的加载过程关联起来:类加载过程中的解析阶段,先从Class文件的常量池中获取符号引用,经过解析后替换为直接引用。而直接引用,就是可以直接指向目标的指针,相对偏移量或者是一个能间接定位到目标的句柄,简单点来说,就是对象的访问定位。

    常量池小结

    常量池一般包括两部分:

    1. 静态常量池,指的是Class文件结构中的常量池。
    2. 运行时常量池,指的是由静态常量池经过运行时的加载转变的。

    还可以推断出:

    1. 符号引用属于静态数据,直接引用属于运行时数据
    2. Class文件结构中存放的是符号引用,运行时常量池中存放的是直接引用

    相关文章

      网友评论

          本文标题:JVM-从字节码到运行时(1)

          本文链接:https://www.haomeiwen.com/subject/dstelltx.html