手写一个简单的 JVM(二):模拟运行 JVM
模拟JVM指令的运行
模拟JVM的运行,需要有两个个方面的知识准备
- JVM的结构
- 字节码的含义
JVM的结构
对于JVM的结构,在很多地方都有描述,此处不再赘述,具体结构如下:

方法区
重点说下方法区,方法区有两个实现
- 永久代(1.7以前),永久代分配到堆上
- 元空间(1.8),元空间在直接内存上面(分配的快,回收的慢,元空间加载后不会被回收)
我们上面说的class加载后的内存都在当前这个内存中。
虚拟机栈
虚拟机栈是栈帧的集合的统称,栈帧是虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素,每一个方法对应了一个栈帧。
栈帧的组成分为4个部分: 局部变量表,操作数栈,动态链接和返回地址。
- 局部变量表 方法内部的变量列表,第一个是当前类的指针
- 操作数栈 是变量操作的临时存储空间,通过入栈和出栈来操作数据
- 动态链接 java的运行过程不存在link的过程,所以在一个类中调用另外一个类的方法时候,需要动态寻找的类的地址。
- 返回地址 当前方法返回的地址。
堆
堆是是存放引用变量真实内存的地方,可以理解成JVM的后方"仓库",负责存储和获取真实内存的地方。
类的转换过程
从上面的分析可以得到一个变量的转换过程:
Java class文件 --> 类解析产生常量池 --> JVM指令转为局部变量表和操作数栈。
通过上面的分析,可以简单的把运行时数据去归纳成如下方式:

字节码含义
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的栈表示操作数栈。
下面尝试分析下方法的执行步骤:
iconst _1 将int型1推送至栈顶

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

iconst_2 同
iconst_1
istore_2

iload_1

Iload_2

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

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

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

Iload_3

- invokevirtual 调用实例方法

代码实现
找到main方法
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; }创建Frame
Frame 是代表栈帧,在创建方法的时候都需要创建一个栈帧。在Frame中,使用List表示本地变量表,Stack代表操作数栈。
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(); } }创建指令Instruction接口
在上面的分析过程中,每一个指令都会对
Frame进行操作,我们定义一个接口Instruction.public interface Instruction { void execute(Frame frame); }指令的实现
- Const指令的实现:
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的实现
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指令实现:
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的静态变量入栈。
public class GetStatic implements Instruction { @Override public void execute(Frame frame) { frame.pushVar(new Slot(System.out)); } }在InvokeVirtual的方法中,直接转换成对应的方法调用:
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()); } }
InstructionFactory 工厂实现
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; } } } }主方法实现:
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