美文网首页
Java虚拟机-如何运行一个程序

Java虚拟机-如何运行一个程序

作者: 贪睡的企鹅 | 来源:发表于2019-08-01 17:46 被阅读0次

示例程序

源程序

public class Bootstrap {

    public static void main(String[] args) {
        String name = "Louis";
        greeting(name);
    }

    public static void greeting(String name)
    {
        System.out.println("Hello,"+name);
    }
}

javap反编译字节码

Classfile /C:/work/project/juc-in-action/target/classes/jvm/Bootstrap.class
  Last modified 2019-8-1; size 837 bytes
  MD5 checksum 1349bd234c0cad3d90c9236312301d58
  Compiled from "Bootstrap.java"
public class jvm.Bootstrap
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #12.#30        // java/lang/Object."<init>":()V
   #2 = String             #31            // Louis
   #3 = Methodref          #11.#32        // jvm/Bootstrap.greeting:(Ljava/lang/String;)V
   #4 = Fieldref           #33.#34        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = Class              #35            // java/lang/StringBuilder
   #6 = Methodref          #5.#30         // java/lang/StringBuilder."<init>":()V
   #7 = String             #36            // Hello,
   #8 = Methodref          #5.#37         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #9 = Methodref          #5.#38         // java/lang/StringBuilder.toString:()Ljava/lang/String;
  #10 = Methodref          #39.#40        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #11 = Class              #41            // jvm/Bootstrap
  #12 = Class              #42            // java/lang/Object
  #13 = Utf8               <init>
  #14 = Utf8               ()V
  #15 = Utf8               Code
  #16 = Utf8               LineNumberTable
  #17 = Utf8               LocalVariableTable
  #18 = Utf8               this
  #19 = Utf8               Ljvm/Bootstrap;
  #20 = Utf8               main
  #21 = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               args
  #23 = Utf8               [Ljava/lang/String;
  #24 = Utf8               name
  #25 = Utf8               Ljava/lang/String;
  #26 = Utf8               greeting
  #27 = Utf8               (Ljava/lang/String;)V
  #28 = Utf8               SourceFile
  #29 = Utf8               Bootstrap.java
  #30 = NameAndType        #13:#14        // "<init>":()V
  #31 = Utf8               Louis
  #32 = NameAndType        #26:#27        // greeting:(Ljava/lang/String;)V
  #33 = Class              #43            // java/lang/System
  #34 = NameAndType        #44:#45        // out:Ljava/io/PrintStream;
  #35 = Utf8               java/lang/StringBuilder
  #36 = Utf8               Hello,
  #37 = NameAndType        #46:#47        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #38 = NameAndType        #48:#49        // toString:()Ljava/lang/String;
  #39 = Class              #50            // java/io/PrintStream
  #40 = NameAndType        #51:#27        // println:(Ljava/lang/String;)V
  #41 = Utf8               jvm/Bootstrap
  #42 = Utf8               java/lang/Object
  #43 = Utf8               java/lang/System
  #44 = Utf8               out
  #45 = Utf8               Ljava/io/PrintStream;
  #46 = Utf8               append
  #47 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #48 = Utf8               toString
  #49 = Utf8               ()Ljava/lang/String;
  #50 = Utf8               java/io/PrintStream
  #51 = Utf8               println
{
  public jvm.Bootstrap();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Ljvm/Bootstrap;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String Louis
         2: astore_1
         3: aload_1
         4: invokestatic  #3                  // Method greeting:(Ljava/lang/String;)V
         7: return
      LineNumberTable:
        line 6: 0
        line 7: 3
        line 8: 7
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       8     0  args   [Ljava/lang/String;
            3       5     1  name   Ljava/lang/String;

  public static void greeting(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #5                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        10: ldc           #7                  // String Hello,
        12: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return
      LineNumberTable:
        line 12: 0
        line 13: 25
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      26     0  name   Ljava/lang/String;
}
SourceFile: "Bootstrap.java"

2 JVM如何运行程序

如果想运行Bootstrap.java 这个程序,首先需要通过javac编译器将其编译成Bootstrap.classJava字节码文件,之后交给Java虚拟机来运行,具体如下几个步骤:

  • 1 JVM读取Bootstrap.class文件加载到方法区(Method Area)中。 方法区中数据是所有的线程的。

  • 2 当使用Bootstrap.class时,JVM通过ClassLoader(类加载器)加载方法区中Bootstrap.class文件,并在Heap堆上创建一个Bootstrap Class对象(类加载机制)

       Class<?> aClass = ClassLoader.getSystemClassLoader().loadClass("jvm.Bootstrap");
  • 3 当使用Bootstrap.class.main函数时,会将main函数符号引用转换为直接引用,通过直接引用获取main方法字节码指令的地址,同时在当前线程栈中为main方法创建一个栈帧,压入当前主线程函数栈中。

  • 4 main方法在执行的过程之中,调用了greeting静态方法,JVM将greeting函数符号引用转换为直接引用,从方法区获取greeting方法字节码指令的地址,同时为greeting方法创建一个栈帧,压入当前主线程函数栈中。

  • 5 当greeting方法运行完成后,则greeting方法出栈,JVM会获取main方法中程序计数器中保存main函数执行的位置,继续运行greeting方法返回后main字节码指定。

image

3 方法运行原理

JVM中线程方法的调用返回对应着方法栈帧在虚拟机栈中的入栈和出栈的过程。一个栈帧描述了方法执行全部流程。

3.1 栈帧的结构

栈帧的结构中主要包含了局部变量,操作数栈动态链接方法返回地址

  • 局部变量表(Local Variable Table)是一组变量值存储空间(类似于数组),用于存放方法参数和方法内部定义的局部变量。局部变量表存储结构和数组类型,一个数据块的大小用一个单位变量槽表示。在Class文件字节码的方法Code属性中定义了方法局部变量表中变量的最大值(locals),参数个数(args_size)

  • 操作数栈是用来存储Java字节码指令计算过程中的参数和结果的一块数据存储结构。在Class文件字节码的方法Code属性中定义了操作数栈的最大深度(stacks)

Code:
      stack=1, locals=2, args_size=1
  • 方法返回地址,用于记录方法调用者的指令在内存中地址。

  • 动态链接就是在运行期间将符号引用转换为直接引用的过程。在方法的执行过程中,最重要的符号引用就是方法的符号引用。JVM会在程序运行中将当前方法Java字节码通过直接引用内存地址从方法区中读取过来执行。

image
3.2 JVM运行main方法的过程

创建main函数栈帧

JVM为main方法创建一个栈帧(VM Stack),并将其加入虚拟机栈中。

栈帧初始化

  • 初始化PC值。PC 是指令计数器,其内部的值决定了JVM虚拟机下一步应该执行哪一行Java字节码指令,而Java字节码指令存放在方法区,我们需要让PC的值指向方法区main方法要执行Java字节码指令的位置。初始化 PC = main方法在方法区Java字节码指令的地址+偏移(0);
image
  • 局部变量表的初始化。在执行main方法字节码指令前,需要对栈帧中局部变量表中变量做初始化。例如当前main方法存在入参(String[] args) ,JVM会将args 的引用值赋值给局部变量表中第一个变量。

main函数字节码执行

 public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=2, args_size=1
         0: ldc           #2                  // String Louis
         2: astore_1
         3: aload_1
         4: invokestatic  #3                  // Method greeting:(Ljava/lang/String;)V
         7: return

//对应Java源码
public static void main(String[] args) {
        String name = "Louis";
        greeting(name);
}         

给局部变量赋值

0: ldc           #2                  // String Louis
2: astore_1

//对应Java源码
String name = "Louis";
  • 1 ldc:ldc指令用来将常量池中指定的常量放入操作数栈中,这里指定的常量是#2,常量类型是String,其值#31指向一个类型为utf-8的常量,对应值为"Louis"
image
  • 2 astore_1:astore_1指令用来将操作数栈的栈顶元素出栈,并赋给局部变量表下标为1元素。这里等价于:name = “Louis”.
image

调用greeting静态方法

 3: aload_1
 4: invokestatic  #3                  // Method greeting:(Ljava/lang/String;)V
 
 //对应Java源码
 greeting(name);
  • 3 aload_1:aload_1指令用来将局部变量表下标为1的元素的值推到操作数栈栈顶【这里是为了给下一步做参数】
image
image

JVM调用greeting静态方法,会将greeting方法符号引用常量转化为直接引用。直接引用内保存了greeting方法在方法区中内存地址。这里对应到字节码中 greeting。接下来会Code属性第一个要执行的字节码指令位置,开始执行。

3.3 JVM运行greeting方法的过程

创建greeting函数栈帧

JVM为greeting方法创建一个栈帧(VM Stack),并将其加入虚拟机栈中。用以表示对greeting方法的调用。

栈帧初始化

  • 初始化PC值。PC 是指令计数器,其内部的值决定了JVM虚拟机下一步应该执行哪一行Java字节码指令,而Java字节码指令存放在方法区,我们需要让PC的值指向方法区main方法要执行Java字节码指令的位置。初始化 PC = greeting方法在方法区Java字节码指令的地址+偏移(0);
image
  • 局部变量表的初始化。在执行main方法字节码指令前,需要对栈帧中局部变量表中变量进行初始化。例如当前greeting方法存在入参(String name) ,JVM会将name 值赋值给局部变量表中第一个变量。
image

字节码指令执行

 public static void greeting(java.lang.String);
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: new           #5                  // class java/lang/StringBuilder
         6: dup
         7: invokespecial #6                  // Method java/lang/StringBuilder."<init>":()V
        10: ldc           #7                  // String Hello,
        12: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        15: aload_0
        16: invokevirtual #8                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #9                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        25: return

public static void greeting(String name)
{
        System.out.println("Hello,"+name);
}        

打印"Hello,"+name

  • 1 getstatic:getstatic指令用来表示获取一个类的静态变量引用,并将其引用推到操作数栈中。指令获取静态变量用常量池中第#4常量来表示,该常量类型是一个字段的符号引用。这里获取的是System类中PrintStream字段引用放入栈中。

  • 2 new:new指令用来创建一个对象并为对象分配内存,并将其引用推到操作数栈中。创建对象用常量池中第#5常量来表示,该常量类型是一个类的符号引用。这里创建一个java/lang/StringBuilder对象,并将引用放入栈中。【这里需要注意的是JVM中new指令只是创建了一个对象分配内存,却并没有对对象做初始化,也就是说还没有对成员变量赋值,以及调用对象构造函数】

  • 3 dup:dup指令复制操作数栈栈顶的值,并插入到栈顶。当前的栈顶元素为新建的StringBuilder对象的引用。【调用方法指令前,需要将调用方法的对象加入到操作数栈中。此步骤是调用构造函数的一个中间步骤】

image
  • 4 invokespecial:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里符号引用常量#6描述的是类对象的初始化函数<init>。由于<init>的初始化函数不存在参数和返回值,只会将当前栈顶元素(步骤3入栈对象)作为方法调用的对象并出栈。
image
  • Java中一个New Object在JVM中对应着三个指令(new,dup,invokespecial)

  • 5 ldc:将常量池中#7常量引用推入操作数栈,该常量是一个String类型的常量。其值#31指向utf-8字面量对应为字符串"hello"

image
  • 6 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里符号引用常量#8描述的是父类StringBuilder类append方法。由于方法中存在一个参数,此时会将栈顶的元素(5步骤入栈元素)作为参数,其值指向常量池中一个UTF-8类型常量,值为"hello",并从操作数中出栈。接着将新的栈顶元素作为方法调用对象(2步骤入栈元素),并从操作数中出栈。在方法执行完毕会将返回的StringBuilder对象的引用入栈.
image
  • 7 aload_0:将第一个局部变量的引用推到栈顶。当前局部变量表的第一个局部变量时一个引用类型,指向常量池中UTF-8类型的常量,值为“Louis”。
image
  • 8 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里符号引用常量#8描述的是父类StringBuilder类append方法。由于方法中存在一个参数,此时会将栈顶的元素(7步骤入栈元素)作为参数,其值指向常量池中一个UTF-8类型常量,值为"Louis",并从操作数中出栈。接着将新的栈顶元素作为方法调用对象(6步骤入栈元素),并从操作数中出栈。在方法执行完毕会将返回的StringBuilder对象的引用入栈.
image
  • 9 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里符号引用常量#9描述的是父类StringBuilder类toString方法。由于方法不存在参数,会将的栈顶元素(步骤8入栈对象)作为方法调用对象,并从操作数中出栈。在方法执行完毕会将返回的String对象的引用入栈.
image
  • 10 invokevirtual:invokespecial指令用来调用超类构造方法,实例初始化方法<init>,私有方法。和invokespecial不同的是invokespecial指令支持动态链接。如果调用的方法描述中需要传入参数,则会将当前操作数栈顶元素作为方法的参数,并从操作数中出栈。当参数出栈完毕或方法不需要传入参数,会将当前栈顶元素作为方法调用的对象,并从操作数栈中出栈。调用的具体方法用常量池中第#5常量来表示,该常量类型是一个方法的符号引用。方法执行完毕会将返回值推到操作数栈中。这里含义是调用PrintStream对象println方法,由于方法中存在一个参数,此时会将栈顶的元素(9步骤入栈元素)作为参数,其值指向对象field成员变量引用,并从操作数中出栈,接着将新的栈顶元素作为方法调用对象(1步骤入栈元素),并从操作数中出栈。由于println方法不存在返回值,因而不存在入栈操作。
image
  • 10 return:结束返回.

相关文章

网友评论

      本文标题:Java虚拟机-如何运行一个程序

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