美文网首页
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)

    JVM-从字节码到运行时(1) 一切从javap -verbose开始 希望借此文章将Class文件结构和运行时的...

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

    JVM-从字节码到运行时(2) 基于栈的解释器执行过程 这是例子ByteCodeDemo类中的add(int z)...

  • DVM执行 java 程序的工具

    jvm 执行字节码原理:java 程序运行时,是由一个 java 虚拟机来解释 java 字节码的,它将这些字节码...

  • JVM

    1 JVM 运行时内存区域划分ans: (1)程序计数器:这部分空间用于保存当前线程所执行的字节码的行号,字节码解...

  • Java面试零碎知识点

    1.Java文件经过JVM编译成字节码文件,即.class文件,将字节码文件在不同的操作系统中运行时,操作系统再将...

  • Java面试零碎知识点

    1.Java文件经过JVM编译成字节码文件,即.class文件,将字节码文件在不同的操作系统中运行时,操作系统再将...

  • JVM系列:Java内存区域与内存溢出异常

    运行时数据区域 程序计数器 作用记录当前线程所执行到的字节码的行号。字节码解释器工作的时候就是通过改变这个计数器的...

  • 【JAVA】JAVA中的小知识(补充中...)

    Java文件经过JVM编译成字节码文件,即.class文件,将字节码文件在不同的操作系统中运行时,操作系统再将字节...

  • jvm内存划分概览

    JVM运行时数据区内存分布: 1 程序计数器: 很小的内存,记录当前线程所执行的字节码的行号指示器,字节码的解释器...

  • jvm

    1、Java运行时的内存划分 程序计数器 记录当前线程所执行的字节码行号,用于获取下一条执行的字节码。 当多线程运...

网友评论

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

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