引子:
这是本系列的最后一篇文章(后面如果有必要的话会写一篇关于mini jvm代码实现, 类结构的文章, 让大家更好的理解), 介绍一下jvm的执行引擎. 其实一个执行引擎要做的事情就是找到class的main方法, 然后从main方法开始执行整个程序, 直到程序结束. 而执行的代码就在之前解析的方法数据结构中的Code Attribute属性中.
1. 运行时栈帧结构
之前解析的class文件都是一些静态的结构, 当执行引擎运行的时候需要把之前静态的结构转成运行时的结构. 先来看一看JVM的运行时结构
图1-1 RUNTIME_STRUCT因为JVM是一个基于栈的虚拟机, 所以基本上所有的操作都是需要通过对栈的操作完成的. 执行的过程就是从main函数开始(一开始就会为main函数创建一个函数栈帧), 执行main函数的指令(在Code Attribute中), 如果要调用方法就创建一个新的函数栈帧, 如果函数执行完成就弹出第一个函数栈帧.
2. JVM的指令
不管你在java源文件中写了什么函数, 用了什么NB的算法, 经过编译器的编译, 到了class文件中都是一个个的字节, 而Code Attribute中的code[]字段中的字节就是函数翻译过来的字节码指令.
JVM支持的指令大致上可以分成3种没有操作数的, 1个操作数的, 2个操作数的. 因为JVM用一个字节来表示指令, 所以指令的最多只有256个.
JVM指令通用形式如下:
INSTRUCTION所以mini jvm就是要用java来执行JVM的指令来完成功能
3. 几个常用的指令解析
因为jvm的指令太多了, 在这里不可能全部都解析一遍, 所以就选择了几个mini jvm中比较关键的也已经实现了的指令进行解析
3.1 invokespecial
INVOKESPECIAL说明: invokespecial用于调用实例方法, 专门用来处理调用超类方法、私有方法和实例初始化方法.
indexByte1和indexByte2用于组成常量池中的索引((indexbyte1 << 8)|indexbyte2). 所指向的常量项必须是MethodRef Info类型. 同时该条指令还会创建一个函数栈帧, 然后从当前的操作数栈中出栈被调用的方法的参数, 并且将其放到被调用方法的函数栈帧的本地变量表中.
3.2 aload_n
ALOAD_N说明: aload_n从局部变量表加载一个 reference 类型值到操作数栈中. 至于从当前函数栈帧的本地变量表中加载哪个变量是有N的值决定的.
3.3 astore_n
ASTORE_N说明: 将一个 reference 类型数据保存到局部变量表中. 至于保存在局部变量表的哪个位置就由N的值决定.
所以执行引擎要做的工作就是根据每一个指令要执行的功能进行对应的实现.
至于到底是怎么做的就请大家看代码吧, 这里就不详述了.
4. 总结
对于真正的jvm, 现在实现的mini jvm的功能真的是太弱太弱了, 但是即使是实现这么弱的功能也花费了我大概一个月的时间, 可想而知真正的jvm的功能的强大以及其的复杂度.
这次的mini jvm主要是对之前看的一些jvm的文章书籍的一次实战演练, 虽然实现的功能比较弱, 但是更加形象的了解了jvm的组成, java代码的执行流程, 虽然还不能像第一篇文章开头说的那样对自己写的每一个字节都了如指掌, 但是相比较以前也是有了长足的进步.
总之, 这次的mini jvm自己是受益良多. 其中代码中留下的一些todo在后面也会慢慢的补充, 如果有同学感兴趣的话欢迎一起来完善这个"小玩意".
5.
6. 参考
- 深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)
- 深入java虚拟机第二版