59 - ASM之JVM Execution Model

2022-03-16  本文已影响0人  舍是境界

Execution Model

什么是Execution Model

Execution Model就是指Stack Frame简化之后的模型。如何“简化”呢?也就是,我们不需要去考虑Stack Frame的技术实现细节,把它想像一个理想的模型就可以了。

针对Execution Model或Stack Frame,我们可以理解成它由local variable和operand stack两个部分组成,或者说理解成它由local variable、operand stack和frame data三个部分组成。换句话说,local variable和operand stack是两个必不可少的部分,而frame data是一个相对来说不那么重要的部分。在一般的描述当中,都是将Stack Frame描述成local variable和operand stack两个部分;但是,如果我们为了知识的完整性,就可以考虑添加上frame data这个部分。

Execution Model

另外,方法的执行与Stack Frame之间有一个非常紧密的关系:

方法的初始状态

在方法进入的时候,会生成相应的Stack Frame内存空间。那么,Stack Frame的初始状态是什么样的呢?
在Stack Frame当中,operand stack是空的,而local variables则需要考虑三方面的因素:

The Java Virtual Machine uses local variables to pass parameters on method invocation. On class method invocation, any parameters are passed in consecutive local variables starting from local variable 0. On instance method invocation, local variable 0 is always used to pass a reference to the object on which the instance method is being invoked (this in the Java programming language). Any parameters are subsequently passed in consecutive local variables starting from local variable 1

The operand stack is empty when the frame that contains it is created.

方法的后续变化

方法的后续变化,就是在方法初始状态的基础上,随着instruction的执行而对local variable和operand stack的状态产生影响。

当方法执行时,就是将instruction一条一条的执行:

Ignoring exceptions, the inner loop of a Java Virtual Machine interpreter is effectively:

do {
    atomically calculate pc and fetch opcode at pc;
    if (operands) fetch operands;
    execute the action for the opcode;
} while (there is more to do);

需要注意的是,虽然local variable和operand stack是Stack Frame当中两个最重要的结构,两者是处于一个平等的地位上,缺少任何一个都无法正常工作;但是,从使用频率的角度来说,两者还是有很大的差别。先举个生活当中的例子,operand stack类似于“公司”,local variables类似于“临时租的房子”,虽然说“公司”和“临时租的房子”是我们经常待的场所,是两个非常重要的地方,但是我们工作的时间大部是在“公司”进行的,少部分工作时间是在“家”里进行。也就是说,大多数情况下,都是先把数据加载到operand stack上,在operand stack上进行运算,最后可能将数据存储到local variables当中。只有少部分的操作(例如iinc),只需要在local variable上就能完成。所以从使用频率的角度来说,operand stack是进行工作的“主战场”,使用频率就比较高,大多数工作都是在它上面完成;而local variable使用频率就相对较低,它只是提供一个临时的数据存储区域。

查看方法的Stack Frame变化

两个版本

三个部分

我们在执行HelloWorldFrameCore02类之后,输出结果分成三个部分:


在上面的输出结果中,我们会看到local variable和operand stack为{} | {}的情况,这是四种特殊情况。

四种情况

在HelloWorldFrameCore02类当中,会间接使用到AnalyzerAdapter类。在AnalyzerAdapter类的代码中,将locals和stack字段的取值设置为null的情况,就会有上面{} | {}的情况。

在AnalyzerAdapter类的代码中,有四个方法会将locals和stack字段设置为null:

五个视角

在后续内容中,我们介绍代码示例的时候,一般都会从五个视角来学习:

第一个视角,Java语言的视角。假如我们有一个sample.HelloWorld类,代码如下:

package sample;

public class HelloWorld {
    public void test(boolean flag) {
        if (flag) {
            System.out.println("value is true");
        }
        else {
            System.out.println("value is false");
        }
    }
}

第二个视角,Instruction的视角。我们可以通过javap -c sample.HelloWorld命令查看方法包含的instruction内容:

$ javap -c sample.HelloWorld
Compiled from "HelloWorld.java"
public class sample.HelloWorld {
...
  public void test(boolean);
    Code:
       0: iload_1
       1: ifeq          15 (计算之后的值)
       4: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
       7: ldc           #3                  // String value is true
       9: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      12: goto          23 (计算之后的值)
      15: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;
      18: ldc           #5                  // String value is false
      20: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      23: return
}

第三个视角,ASM的视角。运行ASMPrint类,可以查看ASM代码,可以查看某一个opcode具体对应于哪一个MethodVisitor.visitXxxInsn()方法:

Label label0 = new Label();
Label label1 = new Label();

methodVisitor.visitCode();
methodVisitor.visitVarInsn(ILOAD, 1);
methodVisitor.visitJumpInsn(IFEQ, label0);
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("value is true");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);
methodVisitor.visitJumpInsn(GOTO, label1);

methodVisitor.visitLabel(label0);
methodVisitor.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
methodVisitor.visitLdcInsn("value is false");
methodVisitor.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", "(Ljava/lang/String;)V", false);

methodVisitor.visitLabel(label1);
methodVisitor.visitInsn(RETURN);
methodVisitor.visitMaxs(2, 2);
methodVisitor.visitEnd();

第四个视角,Frame的视角。我们可以通过运行HelloWorldFrameCore02类来查看方法对应的Stack Frame的变化:

test:(Z)V
                               // {this, int} | {}
0000: iload_1                  // {this, int} | {int}
0001: ifeq            14(真实值)// {this, int} | {}
0004: getstatic       #2       // {this, int} | {PrintStream}
0007: ldc             #3       // {this, int} | {PrintStream, String}
0009: invokevirtual   #4       // {this, int} | {}
0012: goto            11(真实值)// {} | {}
                               // {this, int} | {}
0015: getstatic       #2       // {this, int} | {PrintStream}
0018: ldc             #5       // {this, int} | {PrintStream, String}
0020: invokevirtual   #4       // {this, int} | {}
                               // {this, int} | {}
0023: return                   // {} | {}

第五个视角,JVM Specification的视角。
Format

mnemonic
operand1
operand2
...

Operand Stack

..., value1, value2 →

..., value3

小结

上一篇 下一篇

猜你喜欢

热点阅读