凡是过往 皆为序章html
0x00 解释器复习0x01 指令从哪里来?class 文件指令解释解析 class 文件, 获取 main 方法字节码指令class 文件格式上代码0x02 指令有了, 而后呢0x03 方法调用怎么玩invokevirtual 的简单实现.0x04 Hello World 成就达成图解执行流程.0x05 更复杂一点的例子源码及使用0x06 小结0x07 预告0x08 FAQ0x09 相关连接0x10 尾记java
以前两篇算是开端, 对解释器有个基本印象, 可是如何与 Java 世界关联起来, 彷佛又有些模糊, 此篇正式进入 Java 世界.
按照惯例, 天然是要写个 HelloWorld , 对于构建一个简单的 JVM 来讲, HelloWorld 会是个样子呢? git
HelloWorld.javagithub
public class HelloWorld {
public static void main(String[] args) {
int val = 1;
System.out.println(val);
}
}
复制代码
案例如上图, 在控制台输出数字 1 . web
[[ 为何输出 1, 按照惯例不是应该输出 Hello World ? => 涉及到字符串的话, 程序就会复杂许多, 精简起见, 输出 1 已然足够 ]]bash
如果写 JVM , 那指令天然指的是 字节码指令, 天然是从 class 文件中解析而来. oracle
如何生成 class 文件? 针对上面的案例, 可以使用 javac 编译获得.
针对案例. app
javac HelloWorld.java
复制代码
当前目录会生成 HelloWorld.class 文件. jvm
class 文件本质上是一个更为紧凑的源码, 以便于机器解析. 工具
如何查看 class 文件内容, 可使用 JDK 自带工具 javap, javap 有不少选项, 暂时只关注 -c (对代码进行反汇编).
javap -c HelloWorld
复制代码
输出以下, 行号是额外添加的.
1 Compiled from "HelloWorld.java"
2 public class HelloWorld {
3 public HelloWorld();
4 Code:
5 0: aload_0
6 1: invokespecial #1 // Method java/lang/Object."<init>":()V
7 4: return
8
9 public static void main(java.lang.String[]);
10 Code:
11 0: iconst_1
12 1: istore_1
13 2: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
14 5: iload_1
15 6: invokevirtual #3 // Method java/io/PrintStream.println:(I)V
16 9: return
17 }
复制代码
连蒙带猜, 想必也能想到上方 11-16 行对应着源代码的 main 方法体内容. 其余内容可暂时忽略.
具体指令可参阅 官方说明
格式说明, [指令位置]: [指令] [指令参数]
e.g
0: iconst_1 , 指令位置 0, 指令为 iconst_1 , 无参数.
案例涉及到的指令说明
本质上是提供一个方法, 参数为 class 文件名, 结果为 解析后的指令集合.
public static List<Inst> parseInst(String classfilePath) {
// 实现
}
复制代码
官方 The ClassFile Structure
就案例而言, 了解便可.
List<Inst> insts = new ArrayList<>();
Inst inst = null;
while (len > 0) {
int code = is.readUnsignedByte();
switch (code) {
case 0x03:
inst = new IConst0();
break;
case 0x04:
inst = new IConst1();
break;
case 0x3c:
inst = new IStore1();
break;
case 0x3d:
inst = new IStore2();
break;
case 0x10:
inst = new Bipush(is.readByte());
break;
case 0xa3:
inst = new IfIcmpGt(is.readShort());
break;
case 0x60:
inst = new Iadd();
break;
case 0x84:
inst = new Iinc(is.readUnsignedByte(), is.readByte());
break;
case 0xa7:
inst = new Goto(is.readShort());
break;
case 0x1b:
inst = new ILoad1();
break;
case 0x1c:
inst = new ILoad2();
break;
case 0xb1:
inst = new Return();
break;
case 0xb2:
is.readUnsignedShort();
inst = new Getstatic();
break;
case 0xb6:
is.readUnsignedShort();
inst = new Invokevirtual();
break;
default:
throw new UnsupportedOperationException();
}
len -= inst.offset();
insts.add(inst);
}
复制代码
核心代码如上, 主要是根据不一样的状况解析出不一样的指令. 并不复杂, 体力活. 对照着官方文档解析便可获得.
与解释器联动起来, 解释上一步解析获得的指令, 因为解释器上篇已实现, 此处就不过多解释, 核心代码以下.
List<Inst> insts = parseInst(path + ".class");
// 因为 jvm 指令有步长的概念, 此处须要转为map.
Map<Integer, Inst> instructions = genInstructions(insts);
// 10 是临时写死, 实际应从 class 文件中解析获得.
Frame frame = new Frame(10, 10);
while (true) {
int pc = frame.pc;
Inst inst = instructions.get(pc);
if (inst == null) {
break;
}
inst.execute(frame);
if (pc == frame.pc) {
frame.pc += inst.offset();
}
}
复制代码
与上篇提到的三个解释器大致相仿.
简单来说, 就是个交换的问题, 用入参(即当前操做数栈的对象), 换一个返回值(放到当前栈顶)或者反作用(好比输入信息).
就案例来说, 就是消耗掉栈的两个对象, 产生反作用(输出到控制台).
更复杂方法调用, 核心依然是上方的交换, 暂不讨论.
public void execute(Frame frame) {
Object val = frame.operandStack.pop(); // 操做数栈顶, 即为要输出的值
Object thisObj = frame.operandStack.pop(); // 其次是 System.out 这个静态变量, 暂时忽略实现.
System.out.println(val); // 利用宿主 JVM 输出.
}
复制代码
$ java -cp target/interpreter-demo.jar com.gxk.demo.jvm.JvmDemo HelloWorld
=> 1
复制代码
求 1,2,3..100 的和, 并输出.
Sum100.java
public class Sum100 {
public static void main(String[] args) {
int sum = 0;
for (int i = 1; i <= 100; i++) {
sum += i;
}
System.out.println(sum);
}
}
复制代码
编译并解释
$ javac Sum100.java
$ java -cp target/interpreter-demo.jar com.gxk.demo.jvm.JvmDemo Sum100
=> 5050
复制代码
源码托管于 github, commit 传送门
git clone https://github.com/guxingke/demo.git
cd demo/interpreter-demo
mvn package
# HelloWorld
java HelloWorld.java
java -cp target/interpreter-demo.jar com.gxk.demo.jvm.JvmDemo Sum100
javac Sum100.java
java -cp target/interpreter-demo.jar com.gxk.demo.jvm.JvmDemo Sum100
复制代码
承接上篇, 使用单文件(300行代码)实现了一个简单的 JVM, 把 Java 世界 class 文件内的字节码指令解析出来, 并解释. 对于有兴趣入坑的同窗来说, 应该是个不错的案例.
若是想了解更多, 能够关注 mini-jvm 项目, 以上文提到的解释器为核心, Java 的一些语言特性基本实现.
系列还会有下一篇? 暂时不会有了, 解释器的核心已经就位, 一些语言特性就是逐步迭代了.
系列告一段落, 暂时不会更新了,我的杂记,不免会有疏漏错误, 若有兴趣, 有问题, 请反馈于我(评论,issue,邮件均欢迎). 再次感谢阅读.