手写一个简单的JVM--02.模拟运行JVM

模拟JVM指令的运行

模拟JVM的运行,需要有两个个方面的知识准备

  1. JVM的结构
  2. 字节码的含义
JVM的结构

对于JVM的结构,在很多地方都有描述,此处不再赘述,具体结构如下:

JVM

方法区

重点说下方法区,方法区有两个实现

  1. 永久代(1.7以前),永久代分配到堆上
  2. 元空间(1.8),元空间在直接内存上面(分配的快,回收的慢,元空间加载后不会被回收)

我们上面说的class加载后的内存都在当前这个内存中。

虚拟机栈

虚拟机栈是栈帧的集合的统称,栈帧是虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素,每一个方法对应了一个栈帧。

栈帧的组成分为4个部分: 局部变量表,操作数栈,动态链接和返回地址。

  • 局部变量表 方法内部的变量列表,第一个是当前类的指针
  • 操作数栈 是变量操作的临时存储空间,通过入栈和出栈来操作数据
  • 动态链接 java的运行过程不存在link的过程,所以在一个类中调用另外一个类的方法时候,需要动态寻找的类的地址。
  • 返回地址 当前方法返回的地址。

堆是是存放引用变量真实内存的地方,可以理解成JVM的后方”仓库“,负责存储和获取真实内存的地方。


类的转换过程

从上面的分析可以得到一个变量的转换过程:

Java class文件 —> 类解析产生常量池 —> JVM指令转为局部变量表和操作数栈

通过上面的分析,可以简单的把运行时数据去归纳成如下方式:

JVMRunArea

字节码含义

JVM的指令对应的操作,可以查看JVM的指令表,或者查看笔者的这篇文章:JVM指令的速记

这里我仅列出当前所使用的的指令的含义:

指令 含义
iconst_1 将int型1推送至栈顶
istore_1 将栈顶int型数值存入第二个本地变量
iconst_2 将int型2推送至栈顶
istore_2 将栈顶int型数值存入第三个本地变量
iload_1 将第二个int型本地变量推送至栈顶
iload_2 将第三个int型本地变量推送至栈顶
iadd 将栈顶两int型数值相加并将结果压入栈顶
istore_3 将栈顶int型数值存入第四个本地变量
getstatic 获取指定类的静态域, 并将其压入栈顶
iload_3 将第四个int型本地变量推送至栈顶
invokevirtual 调用实例方法
return 从当前方法返回void

运行过程分析实战

根据我们上面的分析,我们尝试手工模拟下指令对于运行区数据的影响。

从上面的分析中,每一个方法都会创建一个栈帧。在栈帧中,定义一个长度为4的数组来表示局部变量表,用一个长度为2的栈表示操作数栈

下面尝试分析下方法的执行步骤:

  1. iconst _1 将int型1推送至栈顶

    IConst_1

  2. istore_1 把栈顶的值赋给第二变量

    istore_1

  3. iconst_2 同iconst_1

    IConst_2

  4. istore_2

    istore_2

  5. iload_1

    iload

  6. Iload_2

    iload_2

  7. iadd 将栈顶两int型数值相加并将结果压入栈顶

    iadd

  8. istore_3 将栈顶int型数值存入第四个本地变量

    istore_3

  9. getstatic 获取指定类的静态域, 并将其压入栈顶

    getstatic

  10. Iload_3

    iload3

  11. invokevirtual 调用实例方法

    invokevitual

代码实现

  1. 找到main方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    static Method getMainMethod(ClassParseInfo classParseInfo){
    Method[] methods = classParseInfo.methods;
    for (int i = 0; i < methods.length; i++) {
    int name_index = methods[i].getName_index();
    CONSTANT_Utf8_info utf8Constant = classParseInfo.constant_pool.getUtf8Constant(name_index);
    if(utf8Constant.getText().equalsIgnoreCase("main")){
    return methods[i];
    }
    }
    return null;
    }
  2. 创建Frame

    Frame 是代表栈帧,在创建方法的时候都需要创建一个栈帧。在Frame中,使用List表示本地变量表,Stack代表操作数栈。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34

    public class Frame {

    private List<Slot> localVars;
    private Stack<Slot> operateStack;
    private int maxLocalVars;
    private int maxStack;

    public Frame(int maxLocalVars, int maxStack) {
    this.maxLocalVars = maxLocalVars;
    this.maxStack = maxStack;
    localVars = new ArrayList<>(maxLocalVars);
    operateStack = new Stack<>();
    }

    public void setVars(int index,Slot slot) {
    this.localVars.add(slot);
    }
    public Slot getVar(int index) {
    return localVars.get(index - 1);
    }

    public void pushVar(Slot slot) {
    operateStack.push(slot);
    }

    public Slot pop() {
    if (this.operateStack.isEmpty())
    return null;
    return this.operateStack.pop();
    }

    }

  3. 创建指令Instruction接口

    在上面的分析过程中,每一个指令都会对Frame进行操作,我们定义一个接口Instruction.

    1
    2
    3
    public interface Instruction {
    void execute(Frame frame);
    }
  4. 指令的实现

    • Const指令的实现:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    public class IConst implements Instruction {
    private int value;

    public IConst(int value) {
    this.value = value;
    }

    @Override
    public void execute(Frame frame) {
    pushValue(frame,value);
    }

    public void pushValue(Frame frame, int value) {
    frame.pushVar(new Slot(value));
    }

    }
  • ILoad的实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class Load implements Instruction {
    public Load(int index) {
    this.index = index;
    }

    private final int index;

    @Override
    public void execute(Frame frame) {
    Slot var = frame.getVar(index);
    frame.pushVar(var);
    }

    }
  • Store指令实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    public class Store implements Instruction {
    final int index;

    public Store(int index) {
    this.index = index;
    }

    @Override
    public void execute(Frame frame) {
    setVar(frame,this.index);
    }

    public void setVar(Frame frame, int index) {
    Slot pop = frame.pop();
    frame.setVars(index, pop);
    }
    }
  • GetStatic和InvokeVirtual的mock:

    为了降低代码的复杂度,我们对GetStatic和InvokVirtual方法进行Mock。

    在GetStatic是获得一个静态变量入栈。在当前的代码中,静态变量就是`System.out。在GetStatic实现的时候,我们直接采用System.out的静态变量入栈。

    1
    2
    3
    4
    5
    6
    public class GetStatic implements Instruction {
    @Override
    public void execute(Frame frame) {
    frame.pushVar(new Slot(System.out));
    }
    }

    在InvokeVirtual的方法中,直接转换成对应的方法调用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    public class InvokeVirtual implements Instruction {
    //mock
    @Override
    public void execute(Frame frame) {
    Slot pop = frame.pop();
    Slot out = frame.pop();
    PrintStream sysOut = (PrintStream) out.getRef();
    sysOut.println(pop.getValue());
    }
    }

  1. InstructionFactory 工厂实现

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    public class InstructionFactory {
    public static Instruction getInstruction(Opcode opcode, Object... obj) {
    switch (opcode) {
    case iconst_1:
    return new IConst(1);
    case iconst_2:
    return new IConst(2);
    case iload_1:
    return new Load(1);
    case iload_2:
    return new Load(2);
    case iload_3:
    return new Load(3);
    case istore_1:
    return new Store(1);
    case istore_2:
    return new Store(2);
    case istore_3:
    return new Store(3);
    case iadd:
    return new Add();
    case _return:
    return new Return();
    case getstatic:
    return new GetStatic();
    case invokevirtual:
    return new InvokeVirtual();
    default: {
    System.out.println(opcode + "未实现");
    return null;
    }
    }
    }
    }

  1. 主方法实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    Code_attribute code = (Code_attribute) Arrays.stream(mainMethod.getAttributes()).filter(x -> x.tagName.equalsIgnoreCase("code")).findFirst().orElse(null);

    Frame frame = new Frame(code.getMax_locals(), code.getMax_stack());
    byte[] paraCodeArr = code.getParaCode();
    for (Opcode op : code.getOpcodeArr()) {
    if (op == null)
    continue;
    byte[] bytes = new byte[0];
    if (op.operandCount > 0) {
    bytes = Arrays.copyOf(paraCodeArr, op.operandCount);
    paraCodeArr = Arrays.copyOfRange(paraCodeArr, op.operandCount - 1, paraCodeArr.length - 1);
    }
    Instruction instruction = InstructionFactory.getInstruction(op);
    instruction.execute(frame);
    }

最终执行结果:3

手写一个简单的JVM--02.模拟运行JVM

http://blog.laofu.online/2020/06/10/2020-06-10-jvm-run/

作者

付威

发布于

2020-06-10

更新于

2020-08-11

许可协议

评论