博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
大牛程序员用Java手写JVM:刚好够运行 HelloWorld
阅读量:5739 次
发布时间:2019-06-18

本文共 2507 字,大约阅读时间需要 8 分钟。

专注于Java领域优质技术号,欢迎关注

作者:老曹撸代码

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

1. 前言

没错这又是一篇介绍 JVM 的文章,这类文章网上已经很多,不同角度、不同深度、不同广度,也都不乏优秀的。为什么还要来一篇?首先对于我来说,我正在学习 Java,了解JVM的实现对学习Java当然很有必要,但我已经做了多年C++开发,就算我用C++实现一个JVM,我还是个C++码农,而用 Java实现,即能学习 Java 语法,又能理解 JVM,一举两得。其次,作为读者,hotspot或者其他成熟JVM实现的源码读起来并不轻松,特别是对没有C/C++经验的人来说,如果只是想快速了解JVM的工作原理,并且希望运行和调试一下JVM的代码来加深理解,那么这篇文章可能更合适。

我将用Java实现一个JAVA虚拟机(源码在这下载:https://github.com/caoym/jjvm,加 Star 亦可),一开始它会非常简单,实际上简单得只够运行HelloWorld。虽然简单,但是我尽量让其符合 JVM 标准,目前主要参考依据是《Java虚拟机规范 (Java SE 7 中文版)》。

2. 准备

先写一个HelloWorld,代码如下:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

我期望所实现的虚拟机(姑且命名为JJvm吧),可以通过以下命令运行:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

接下来我们开始实现JJvm,下面是其入口代码,后面将逐步介绍:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

3. 加载初始类

我们将包含 main 入口的类称为初始类,JJvm 首先需要根据org.caoym.HelloWorld类名,找到.class 文件,然后加载并解析、校验字节码,这些步骤正是 ClassLoader(类加载器)做的事情。HelloWorld.class内容大致如下:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

没错是紧凑的二进制格式,需要按规范解析,不过我并不打算自己写解析程序,可以直接用com.sun.tools.classfile.ClassFile,这也是用JAVA写好处。下面是HelloWorld.class解析后的内容(通过javap -v HelloWorld.class输出):

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

可以看到HelloWorld.class 文件中主要包含几部分:

  1. 常量池(Constant pool)
  2. 常量池中记录了当前类中用到的常量,包括方法名、类名、字符串常量等,如:#3 = String #23, #3为此常量的索引,字节码执行时通过此索引获取此常量,String为常量类型, 还可以是Methodref (方法引用)、Fieldref(属性引用)等。
  3. 方法定义
  4. 此处定义了方法的访问方式(如 PUBLIC、STATIC)、字节码等,关于字节码的执行方式将在后面介绍。

以下为类加载器的部分代码实现:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

类加载器可以加载两种形式的类:JvmOpcodeClass和 JvmNativeClass,均继承自JvmClass。其中JvmOpcodeClass 表示用户定义的类,通过字节码执行,也就是这个例子中的HelloWorld;JvmNativeClass表示JVM 提供的原生类,可直接调用原生类执行,比如 java.lang.System。这里把所有非项目内的类,都当做原始类处理,以便简化虚拟机的实现。

4. 找到入口方法

JVM规定入口是static public void main(String[]),为了能够查找指定类的方法,JvmOpcodeClass和JvmNativeClass都需要提供getMethod方法, 当然 main 方法肯定存在JvmOpcodeClass中:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

5. 执行非 Native(字节码定义的)方法

下图为以HelloWorld的main()方法的执行过程:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

下面将详细说明。

5.1. 虚拟机栈

每一个虚拟机线程都有自己私有的虚拟机栈(Java Virtual Machine Stack),用于存储栈帧。每一次方法调用,即产生一个新的栈帧,并推入栈顶,函数返回后,此栈帧从栈顶推出。以下为 JJvm中虚拟机栈的部分代码:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

5.2. 栈帧

栈帧用于保存当前函数调用的上下文信息,以下为 JJvm 中栈帧的部分代码:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

说明:

  • 局部变量表
  • 保存当前方法的局部变量、实例的this指针和方法的实参。函数执行过程中,部分字节码会操作或读取局部变量表。局部变量表的长度由编译期决定。
  • 常量池
  • 引用当前类的常量池。
  • 字节码内容
  • 以数组形式保存的当期方法的字节码。
  • 程序计数器
  • 记录当前真在执行的字节码的位置。
  • 操作数栈
  • 操作数栈用来准备字节码调用时的参数并接收其返回结果,操作数栈的长度由编译期决定。

5.3. 方法调用

方法调用的过程大致如下:

  1. 新建栈帧,并推入虚拟机栈。
  2. 将实例的this和当前方法的实参设置到栈帧的局部变量表中。
  3. 解释执行方法的字节码。

以下为 JJvm 中的部分代码:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

5.4. 解释执行字节码

字节码的执行过程如下:

  1. 获取栈顶的第一个栈帧。
  2. 获取当前栈的程序计数器(PC,其默认值为0)指向的字节码,程序计数器+1。
  3. 执行上一步获取的字节码,推出操作数栈的元素,作为其参数,执行字节码。
  4. 字节码返回的值(如果有),重新推入操作数栈。
  5. 如果操作数为return等,则设置栈帧为已返回状态。
  6. 如果操作数为invokevirtual等嵌套调用其他方法,则创建新的栈帧,并回到第一步。
  7. 如果栈帧已设置为返回,则将返回值推入上一个栈帧的操作数栈,并推出当前栈。
  8. 重复执行1~7,直到虚拟机栈为空。

以下为JJvm中解释执行字节码的部分代码:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

6. 执行 Native 方法

Native方法的调用要更简单一些,只需调用已存在的实现即可,代码如下:

大牛程序员用Java手写JVM:刚好够运行 HelloWorld

7. 结束

到目前为止,我们的“刚好够运行 HelloWorld”的 JVM 已经完成,完整代码可在这里下载:https://github.com/caoym/jjvm。当然这个JVM 并不完整,缺少很多内容,如类和实例的初始化、多线程问题、反射、GC 等等。我争取逐步完善JJvm,并奉上更多文章。

来源:https://www.jianshu.com/p/4d81465c2fb8

转载地址:http://xpfzx.baihongyu.com/

你可能感兴趣的文章
windows python3 paramiko安装_Python3.3 Paramiko Windows安装错误
查看>>
ref获取元素 vue 删除子元素_vue 添加删除子元素
查看>>
mysql有回收站吗_mysql 回收站
查看>>
mysql时间戳参数_MySQL 时间戳(Timestamp)函数
查看>>
mysql 分组返回_MySQL-分组和总计,但返回每个分组中的所有行
查看>>
centos6 mysql 同步_centos 6.5设置mysql主从同步过程记录
查看>>
在MySQL中两个实体怎么匹配_如何设计数据库约束,以便两个实体只有其中两个字段值匹配才可以有多对多关系?...
查看>>
mysql索引 回表_记录下mysql索引以及回表
查看>>
php mysql 数组存储过程_php调用MySQL存储过程的方法集合(推荐)
查看>>
cd usr local mysql_不想每次都到: /usr/local/mysql/bin
查看>>
mysql线程保护_mysql事务隔离的问题?多线程并发怎么保证可靠?
查看>>
amoeba mysql exists_使用Amoeba for mysql实现mysql读写分离
查看>>
mysql mmm坑_MySql——MMM部署过程中的各种报错及解决办法
查看>>
mysql中的lgwr_LGWR进程的trace里总是报20多秒的警告信息
查看>>
mysql微服务_go Gojj+Mysql搭建微服务-Go语言中文社区
查看>>
mysql阿里监控sql_阿里云sql监控配置-druid
查看>>
pdo查找mysql语句错误_PDO中捕获SQL语句中的错误——异常模式
查看>>
shell 备份mysql库到远程_shell编程系列25--shell操作数据库实战之备份MySQL数据,并通过FTP将其传输到远端主机...
查看>>
antd table 宽度_react+antd实现Table拖拽调整列宽
查看>>
kettle MySQL blob_kettle demo12 通过JAVA创建trans并保存到数据库资源库
查看>>