模拟JVM的运行,需要有两个个方面的知识准备
对于JVM的结构,在很多地方都有描述,此处不再赘述,具体结构如下:
重点说下方法区,方法区有两个实现
我们上面说的class加载后的内存都在当前这个内存中。
虚拟机栈是栈帧的集合的统称,栈帧是虚拟机执行时方法调用和方法执行时的数据结构,它是虚拟栈数据区的组成元素,每一个方法对应了一个栈帧。
栈帧的组成分为4个部分: 局部变量表,操作数栈,动态链接和返回地址。
堆是是存放引用变量真实内存的地方,可以理解成JVM的后方”仓库
“,负责存储和获取真实内存的地方。
从上面的分析可以得到一个变量的转换过程:
Java class文件
–> 类解析产生常量池
–> JVM指令转为局部变量表和操作数栈
。
通过上面的分析,可以简单的把运行时数据去归纳成如下方式:
</img src=”/img/classReader/11.png” alt=”JVMRunArea” style=”zoom:50%;” />
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
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;
}
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();
}
}
public interface Instruction {
void execute(Frame frame);
}
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());
}
}
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
(本文完)
作者:付威
如果觉得对您有帮助,可以下方的RSS订阅,谢谢合作
如有任何知识产权、版权问题或理论错误,还请指正。
本文是付威的网络博客原创,自由转载-非商用-非衍生-保持署名,请遵循:创意共享3.0许可证
交流请加群113249828: 点击加群 或发我邮件 laofu_online@163.com