JVM-理解字节码执行引擎

485 阅读5分钟

基础知识

一张图理解字节码结构

执行模式

  • 解释执行:是使用解释器会将我们的一句句代码解释成机器可以识别的二进制代码来执行,如Dalvik
  • 编译执行:源程序先通过编译器(负责将源程序翻译成目标机器指令)翻译成机器指令,通过编译-->链接-->目标可执行文件,然后执行;即提前将所有源代码一次性转换成二进制指令,也就是生成一个可执行程序,如旧JIT,C++
  • 两者兼备:新JIT 无论什么执行模式,统一的一点是字节码是基于栈的执行引擎,后面的例子可以说明这一点。下面就看看其数据结构

执行数据结构

栈帧:栈中的元素

  • 局部变量表:用于存储局部变量
  • 操作数栈:用于执行函数以及入参
  • 动态链接:支持运行时的方法引用转化
  • 返回地址:支持方法返回

基本指令

  • astore_n: 栈顶引用型数值存入第n个本地变量
  • invokespecial:
  • invokevirtual: 普通方法调用
  • dup: 复制栈顶数值并将复制值压入栈顶
  • ldc: 推送常量到栈顶
  • aload_n:将第n个引用类型的变量推送到栈顶

实例分析

Java源码
package jvm;
public class Language {
    public int count = 1;
    public String getContent() {
        return "hello world";
    }
    public String getContent2() {
        return "hello world 2";
    }
}

public class Person {
    private final String name = "person";
    private final Language mLanguage = new Language();

    public void say() {
        System.out.println("Person say " +  mLanguage.getContent());
    }

    public static void main(String[] args) {
        Person person = new Person();
        person.say();
    }
}

javap -v Person 输出
public class jvm.Person
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #18.#33        // java/lang/Object."<init>":()V
   #2 = String             #34            // person
   #3 = Fieldref           #15.#35        // jvm/Person.name:Ljava/lang/String;
   #4 = Class              #36            // jvm/Language
   #5 = Methodref          #4.#33         // jvm/Language."<init>":()V
   #6 = Fieldref           #15.#37        // jvm/Person.mLanguage:Ljvm/Language;
   #7 = Fieldref           #38.#39        // java/lang/System.out:Ljava/io/PrintStream;
   #8 = Class              #40            // java/lang/StringBuilder
   #9 = Methodref          #8.#33         // java/lang/StringBuilder."<init>":()V
  #10 = String             #41            // Person say
  #11 = Methodref          #8.#42         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #12 = Methodref          #4.#43         // jvm/Language.getContent:()Ljava/lang/String;
  #13 = Methodref          #8.#44         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #14 = Methodref          #45.#46        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #15 = Class              #47            // jvm/Person
  #16 = Methodref          #15.#33        // jvm/Person."<init>":()V
  #17 = Methodref          #15.#48        // jvm/Person.say:()V
  #18 = Class              #49            // java/lang/Object
  #19 = Utf8               name
  #20 = Utf8               Ljava/lang/String;
  #21 = Utf8               ConstantValue
  #22 = Utf8               mLanguage
  #23 = Utf8               Ljvm/Language;
  #24 = Utf8               <init>
  #25 = Utf8               ()V
  #26 = Utf8               Code
  #27 = Utf8               LineNumberTable
  #28 = Utf8               say
  #29 = Utf8               main
  #30 = Utf8               ([Ljava/lang/String;)V
  #31 = Utf8               SourceFile
  #32 = Utf8               Person.java
  #33 = NameAndType        #24:#25        // "<init>":()V
  #34 = Utf8               person
  #35 = NameAndType        #19:#20        // name:Ljava/lang/String;
  #36 = Utf8               jvm/Language
  #37 = NameAndType        #22:#23        // mLanguage:Ljvm/Language;
  #38 = Class              #50            // java/lang/System
  #39 = NameAndType        #51:#52        // out:Ljava/io/PrintStream;
  #40 = Utf8               java/lang/StringBuilder
  #41 = Utf8               Person say
  #42 = NameAndType        #53:#54        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #43 = NameAndType        #55:#56        // getContent:()Ljava/lang/String;
  #44 = NameAndType        #57:#56        // toString:()Ljava/lang/String;
  #45 = Class              #58            // java/io/PrintStream
  #46 = NameAndType        #59:#60        // println:(Ljava/lang/String;)V
  #47 = Utf8               jvm/Person
  #48 = NameAndType        #28:#25        // say:()V
  #49 = Utf8               java/lang/Object
  #50 = Utf8               java/lang/System
  #51 = Utf8               out
  #52 = Utf8               Ljava/io/PrintStream;
  #53 = Utf8               append
  #54 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #55 = Utf8               getContent
  #56 = Utf8               ()Ljava/lang/String;
  #57 = Utf8               toString
  #58 = Utf8               java/io/PrintStream
  #59 = Utf8               println
  #60 = Utf8               (Ljava/lang/String;)V
{
  private final java.lang.String name;
    descriptor: Ljava/lang/String;
    flags: ACC_PRIVATE, ACC_FINAL
    ConstantValue: String person

  private final jvm.Language mLanguage;
    descriptor: Ljvm/Language;
    flags: ACC_PRIVATE, ACC_FINAL

  public jvm.Person();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: aload_0
         5: ldc           #2                  // String person
         7: putfield      #3                  // Field name:Ljava/lang/String;
        10: aload_0
        11: new           #4                  // class jvm/Language
        14: dup
        15: invokespecial #5                  // Method jvm/Language."<init>":()V
        18: putfield      #6                  // Field mLanguage:Ljvm/Language;
        21: return
      LineNumberTable:
        line 3: 0
        line 5: 4
        line 6: 10

  public void say();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=3, locals=1, args_size=1
        // 拿到一个静态对象
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         // new三兄弟又出现了
         3: new           #8                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #9                  // Method java/lang/StringBuilder."<init>":()V
        // push常量池中的常量#10('Person Say')到栈顶
        10: ldc           #10                 // String Person say
        // 调用方法StringBuilder.append()
        12: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        // push本地变量表的第0个即this到栈顶,为了调用getfield
        15: aload_0
        // 从字段集合中拿到#6(mLanguage)到栈顶
        16: getfield      #6                  // Field mLanguage:Ljvm/Language;
        // 调用getContent方法,#12调用链路为(#4.#55):#56 -> mLanguage.getContent(): String
        19: invokevirtual #12                 // Method jvm/Language.getContent:()Ljava/lang/String;
        // 同上注释,最终解析为 builder.append(): StringBuilder。其实默认的英文注释已经写得很明白了
        22: invokevirtual #11                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        25: invokevirtual #13                 // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        28: invokevirtual #14                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        31: return
      LineNumberTable:
        line 9: 0
        line 10: 31

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=2, args_size=1
        // new, dup, invokespecial是一个经典组合,其说明了new在本质上不是一个原子操作
        // 创建一个对象引用(可以通过常量池表查看#15对应的对象),并入栈
         0: new           #15                 // class jvm/Person
         // 复制该对象引用并入栈
         3: dup
         // 调用Person的init方法(此处也可以通过常量池表查看)
         4: invokespecial #16                 // Method "<init>":()V
         // 存入栈顶(此时为Person的引用)到第一个本地变量表,这里其实是赋值给本地变量person
         7: astore_1
         // 加载本地变量表索引为1的值入栈顶(索引为0的是this),这里是准备进行方法调用了
         8: aload_1
         // 调用#17所指定的方法,这里的方法是say()
         9: invokevirtual #17                 // Method say:()V
        12: return
      LineNumberTable:
        line 13: 0
        line 14: 8
        line 15: 12
}
SourceFile: "Person.java"