前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >手写一个简单的JVM--02.模拟运行JVM

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

作者头像
付威
发布2020-06-11 10:15:51
1.3K0
发布2020-06-11 10:15:51
举报
模拟JVM指令的运行

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

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

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

方法区

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

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

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

虚拟机栈

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

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

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

堆是是存放引用变量真实内存的地方,可以理解成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的栈表示操作数栈

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

  1. iconst _1 将int型1推送至栈顶
  1. istore_1 把栈顶的值赋给第二变量
  1. iconst_2 同iconst_1
  1. istore_2
  1. iload_1
  1. Iload_2
  1. iadd 将栈顶两int型数值相加并将结果压入栈顶
  1. istore_3 将栈顶int型数值存入第四个本地变量
  1. getstatic 获取指定类的静态域, 并将其压入栈顶
  1. Iload_3
  1. invokevirtual 调用实例方法
代码实现
  1. 找到main方法
代码语言:javascript
复制
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;
	}
 
 
  1. 创建Frame Frame 是代表栈帧,在创建方法的时候都需要创建一个栈帧。在Frame中,使用List表示本地变量表,Stack代表操作数栈。
代码语言:javascript
复制
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();
	}
   	
}
 
  1. 创建指令Instruction接口
代码语言:javascript
复制
 public interface Instruction {
	void execute(Frame frame);
}
 
  1. 指令的实现 Const指令的实现:
代码语言:javascript
复制
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());
     }
 }
  1. InstructionFactory 工厂实现
代码语言:javascript
复制
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. 主方法实现:
代码语言:javascript
复制
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

(本文完)

作者:付威

博客地址:http://blog.laofu.online

如果觉得对您有帮助,可以下方的RSS订阅,谢谢合作

如有任何知识产权、版权问题或理论错误,还请指正。

本文是付威的网络博客原创,自由转载-非商用-非衍生-保持署名,请遵循:创意共享3.0许可证

交流请加群113249828: 点击加群   或发我邮件 laofu_online@163.com

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-06-10 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 模拟JVM指令的运行
    • JVM的结构
      • 方法区
        • 虚拟机栈
          • 类的转换过程
            • 字节码含义
            • 运行过程分析实战
            • 代码实现
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档