“真正了不起的程序员对自己的程序的每一个字节都了如指掌”
--《程序员的自我修养》
引子:
写了这么久的Java, 也看了一些关于JVM的书籍, 但是总感觉对JVM理解的还不是很够, 不能很清楚的理解书中对常量池, 本地变量表, 操作数栈, 堆等的一些解释, 因为只看文字总是不形象的, 对于程序员来说还是代码来的直观, 所以这次就来造一个大轮子, 自己动手写一个Mini JVM, 而且是用Java来写一个JVM. 这个JVM真的非常Mini, 里面不会实现垃圾回收, 也不会实现jdk中的类库(因为是用Java来实现, 所以直接调用Java的类库就可以了, 其实道理是一样的, 如果是用C++来实现JVM肯定调用的是C++的类库). 这里用Java来实现Java虚拟机有一种自包含或者说递归的感觉, 因为用Java实现的虚拟机是可以跨平台的, 但是运行这个Mini JVM又需要一个Java环境, 也就是Java虚拟机, 这个虚拟机是真正的oracle实现的虚拟机, 所以实际上这个Mini JVM只是在真正的虚拟机上虚拟了一个JVM.
1. Class File的文件结构
1.1 总览
因为实现的是Mini JVM, 所以直接从解析class文件作为起点, 而不是从将java源文件编译成class文件开始(这个就相当于实现一个java编译器, 这个轮子造的就大发了。。。).
首先直观的看一下一个具体的class文件的内容长什么样子
这个class文件对应的java源文件
mini jvm就是要执行这个java程序. (之后的mini jvm会支持条件判断, 循环等功能)
1.2 class文件的组成
这里要注意一下, 因为class文件实际上就是一个紧凑的字节码文件, 所以如果当一个数据大于一个字节的时候, 是使用无符号大端模式进行存储的, 大小端模式的区别请看这里.
2. 解析ClassFile
注: 接下来的解析会以我们要执行的EmployeeV1的class文件为例进行讲解.
2.1 魔数
每个class文件的前四个字节称为魔数, 它的作用是确定这个文件是否是一个能被虚拟机接受的Class文件. 虚拟机会对class文件进行校验, 但是不仅仅是校验一个class文件魔数, 还会校验比如其方法的字节码指令是否是支持的等等. 而如果一个文件是class文件, 那么它的魔数就是CAFEBABE, 图1-1可以看到确实如此.
2.2 Class文件的版本号
接下的四个字节是class文件的版本号, 也就是编译出这个class文件的编译器的版本号, 顺序是前两个字节是次版本号, 后两个字节是版本号.
这里简单介绍一个java的版本号. java的版本号是从45开始的, jdk1.0jdk1.1的版本号比较特殊是45.045.3, 之后每一个大版本发布版本号就加1(也就是jdk1.2的版本号是46), 所以在jdk1.1jdk1.2之间的小版本号可以有65535个(也就是jdk1.1的可以支持的版本范围是45.045.65535), 至于为什么是65535应该很容易就可以知道, 因为存储小版本号的空间是2个字节, 所以2个字节的无符号范围就是0~65535.同时jdk保持了向下兼容性, 也就是高版本的jdk可以兼容也就是执行低版本的class文件.
从图1-1可以看到, 我使用的版本号是0x000034, 转成十进制也就是次版本号是0, 主版本号是52,
所以计算一下就是jdk1.8(jdk1.2是46, 所以+6就是jdk8的版本号是52). 没错, 我就是用jdk1.8编译这个java源文件的.
具体的class文件版本号的对应关系可以看这里
3. 总结
这篇文章是解析class文件的开端, 也是整个class文件中最简单的部分, 后面就会开始解析class文件中几个最重要的部分的其中一个————常量池. 常量池对整个class文件的结构组成至关重要, 要是没有这个常量池, 我们编译出来的class文件的大小将会大很多很多.
4. 代码地址
5. 本系列其他文章
手把手教你撸一个Mini JVM系列(2)之解析Class File -- 常量池
手把手教你撸一个Mini JVM系列(3)之解析Class File -- 字段、方法、属性
手把手教你撸一个Mini JVM系列(4)之执行引擎
手把手教你撸一个Mini JVM系列(5)之源码分析 -- 常量池、访问标志、类索引
手把手教你撸一个Mini JVM系列(6)之控制流 -- 条件判断和循环