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

2021-04-17  本文已影响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语言结构体的伪结构来存储数据。这种伪结构中包括两种类型:无符号数和表。

(此处应有图)

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

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

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

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

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


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表示常量池入口。常量池中主要存放两大类常量:字面量符号引用

  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文件结构中存放的是符号引用,运行时常量池中存放的是直接引用
上一篇 下一篇

猜你喜欢

热点阅读