网站首页 > java教程 正文
专注于Java领域优质技术号,欢迎关注
作者:老曹撸代码
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,代码如下:
我期望所实现的虚拟机(姑且命名为JJvm吧),可以通过以下命令运行:
接下来我们开始实现JJvm,下面是其入口代码,后面将逐步介绍:
3. 加载初始类
我们将包含 main 入口的类称为初始类,JJvm 首先需要根据org.caoym.HelloWorld类名,找到.class 文件,然后加载并解析、校验字节码,这些步骤正是 ClassLoader(类加载器)做的事情。HelloWorld.class内容大致如下:
没错是紧凑的二进制格式,需要按规范解析,不过我并不打算自己写解析程序,可以直接用com.sun.tools.classfile.ClassFile,这也是用JAVA写好处。下面是HelloWorld.class解析后的内容(通过javap -v HelloWorld.class输出):
可以看到HelloWorld.class 文件中主要包含几部分:
- 常量池(Constant pool)
- 常量池中记录了当前类中用到的常量,包括方法名、类名、字符串常量等,如:#3 = String #23, #3为此常量的索引,字节码执行时通过此索引获取此常量,String为常量类型, 还可以是Methodref (方法引用)、Fieldref(属性引用)等。
- 方法定义
- 此处定义了方法的访问方式(如 PUBLIC、STATIC)、字节码等,关于字节码的执行方式将在后面介绍。
以下为类加载器的部分代码实现:
类加载器可以加载两种形式的类:JvmOpcodeClass和 JvmNativeClass,均继承自JvmClass。其中JvmOpcodeClass 表示用户定义的类,通过字节码执行,也就是这个例子中的HelloWorld;JvmNativeClass表示JVM 提供的原生类,可直接调用原生类执行,比如 java.lang.System。这里把所有非项目内的类,都当做原始类处理,以便简化虚拟机的实现。
4. 找到入口方法
JVM规定入口是static public void main(String[]),为了能够查找指定类的方法,JvmOpcodeClass和JvmNativeClass都需要提供getMethod方法, 当然 main 方法肯定存在JvmOpcodeClass中:
5. 执行非 Native(字节码定义的)方法
下图为以HelloWorld的main()方法的执行过程:
下面将详细说明。
5.1. 虚拟机栈
每一个虚拟机线程都有自己私有的虚拟机栈(Java Virtual Machine Stack),用于存储栈帧。每一次方法调用,即产生一个新的栈帧,并推入栈顶,函数返回后,此栈帧从栈顶推出。以下为 JJvm中虚拟机栈的部分代码:
5.2. 栈帧
栈帧用于保存当前函数调用的上下文信息,以下为 JJvm 中栈帧的部分代码:
说明:
- 局部变量表
- 保存当前方法的局部变量、实例的this指针和方法的实参。函数执行过程中,部分字节码会操作或读取局部变量表。局部变量表的长度由编译期决定。
- 常量池
- 引用当前类的常量池。
- 字节码内容
- 以数组形式保存的当期方法的字节码。
- 程序计数器
- 记录当前真在执行的字节码的位置。
- 操作数栈
- 操作数栈用来准备字节码调用时的参数并接收其返回结果,操作数栈的长度由编译期决定。
5.3. 方法调用
方法调用的过程大致如下:
- 新建栈帧,并推入虚拟机栈。
- 将实例的this和当前方法的实参设置到栈帧的局部变量表中。
- 解释执行方法的字节码。
以下为 JJvm 中的部分代码:
5.4. 解释执行字节码
字节码的执行过程如下:
- 获取栈顶的第一个栈帧。
- 获取当前栈的程序计数器(PC,其默认值为0)指向的字节码,程序计数器+1。
- 执行上一步获取的字节码,推出操作数栈的元素,作为其参数,执行字节码。
- 字节码返回的值(如果有),重新推入操作数栈。
- 如果操作数为return等,则设置栈帧为已返回状态。
- 如果操作数为invokevirtual等嵌套调用其他方法,则创建新的栈帧,并回到第一步。
- 如果栈帧已设置为返回,则将返回值推入上一个栈帧的操作数栈,并推出当前栈。
- 重复执行1~7,直到虚拟机栈为空。
以下为JJvm中解释执行字节码的部分代码:
6. 执行 Native 方法
Native方法的调用要更简单一些,只需调用已存在的实现即可,代码如下:
7. 结束
到目前为止,我们的“刚好够运行 HelloWorld”的 JVM 已经完成,完整代码可在这里下载:https://github.com/caoym/jjvm。当然这个JVM 并不完整,缺少很多内容,如类和实例的初始化、多线程问题、反射、GC 等等。我争取逐步完善JJvm,并奉上更多文章。
来源:https://www.jianshu.com/p/4d81465c2fb8
猜你喜欢
- 2024-09-30 java运行过程(java运行过程运行时数据取过程)
- 2024-09-30 Java代码是如何在机器上运行的?(java程序运行代码)
- 2024-09-30 怎么使用 eclipse 开发和运行 Java 程序呢?
- 2024-09-30 Java运行方式、程序结构以及notepad++
- 2024-09-30 运行Java程序(运行java程序需要什么命令)
- 2024-09-30 Java的加载与执行(java 加载过程)
- 2024-09-30 JVM之java程序是怎么运行的(java实现的jvm)
你 发表评论:
欢迎- 最近发表
- 标签列表
-
- java反编译工具 (77)
- java反射 (57)
- java接口 (61)
- java随机数 (63)
- java7下载 (59)
- java数据结构 (61)
- java 三目运算符 (65)
- java对象转map (63)
- Java继承 (69)
- java字符串替换 (60)
- 快速排序java (59)
- java并发编程 (58)
- java api文档 (60)
- centos安装java (57)
- java调用webservice接口 (61)
- java深拷贝 (61)
- 工厂模式java (59)
- java代理模式 (59)
- java.lang (57)
- java连接mysql数据库 (67)
- java重载 (68)
- java 循环语句 (66)
- java反序列化 (58)
- java时间函数 (60)
- java是值传递还是引用传递 (62)
本文暂时没有评论,来添加一个吧(●'◡'●)