手把手教你实现 Java 虚拟机—虚拟机是什么?

又隔了几天没写文章了,感觉又都生疏了,最近温习了下《 Java 虚拟机规范》以及《深入理解 Java 虚拟机》,几乎每次回看都有不同的感受,理解技术最好的方式就是造轮子,刨去性能等因素,把核心的东西用简单的方式实现一遍,这样更能深刻理解其中的原理。

​今天我们要做的是用 Java 语言去实现一个 Java 虚拟机(基于解释器的方式),当然不会是完整的去实现,更多的是去理解一些核心的东西,今天这篇就当做是开篇吧。

在正式去实现一个 Java 虚拟机之前,我们要先准备一些基础知识,可能会比较单调一些,首先要理解什么是虚拟机,它做了些什么事情以及其运行结构是怎么样的?

一、Java的技术体系

从广义上讲,JRbuy、Groovy 等运行在 Java 虚拟机上的语言以及相关的程序都属于 Java 技术体系中的一员。官方所定义的 Java 技术体系主要包括以下几个部分:

1、Java 程序设计语言

2、Class 文件格式

3、Java API 类库

4、Java 虚拟机

我们可以把 Java 程序设计语言、Java 虚拟机以及 Java API 类库统称为 JDK, JDK 是用于支持 Java 程序开发的最小环境。

当编写并运行一个 Java 程序时,就同时体验了四种技术,用 Java 语言编写代码并调用Java API,编译成 Class 文件,并在 Java 虚拟机中运行 Class 文件。

Java 虚拟机是整个 Java 平台的基石,是 Java 技术用以实现硬件无关与操作系统无关的关键部分,是 Java 语言生成出极小体积的编译代码的运行平台,是保障用户机器免于恶意代码损害的保护屏障。

二、Java 虚拟机运行时数据区域

Java 虚拟机可以看作是一台抽象的计算机。如同真实的计算机那样,它有自己的指令集以及各种运行时内存区域。

根据《 Java 虚拟机规范(Java SE7)》的规定,Java 虚拟机所管理的内存将会包含以下几个运行时数据区域。

image

2.1 程序计数器

程序计数器(PC) 是一块较小的内存空间,它可以看做是当前线程所执行的字节码的行号指示器。字节码解释器工作时通过改变这个计数器的值来选取下一条要执行的字节码指令,分支、循环、跳转、异常处理以及线程恢复等基础功能都需要依赖这个计数器来完成。

由于 Java 虚拟机的多线程是通过线程轮流切换并分配处理器执行时间的方式来实现的,在任何一个确定时刻,一个处理器(对于多核处理器来说是一个内核)都只会执行一条线程中的指令。因此,为了线程切换后能恢复到正确的执行位置,每个线程都需要有一个独立的程序计数器,各线程之间计数器互不影响,独立存储。

如果线程正在执行的是一个 Java 方法,这个计数器记录的是正在执行的虚拟机字节码指令的地址;如果正在执行的是 Native 方法,那么这个计数器则为空 (undefined)。

2.2 Java虚拟机栈

与程序计数器一样,Java 虚拟机栈也是线程私有的,虚拟机栈描述的是 Java 方法执行的内存模型,每个方法在执行的同时都会创建一个栈桢(Stack Frame), 用于存储局部变量表操作数栈动态链接方法出口等信息。每一个方法从调用开始直至执行完成的过程,都对应的一个栈帧在虚拟机栈里入栈和出栈的过程。

局部变量表存放了编译期可知的各种基本类型(boolean、byte、char、short、int、float、long、double)、对象引用(reference类型,它不等同于对象本身,可能只是一个指向对象起始地址的引用指针,也可能是指向一个代表对象的句柄或其他与此对象相关的位置)。

其中 64 位长度的 long 和 double 类型的数据会占用 2 个局部变量空间 (slot ),其余的类型只占用 1 个。在编译程序代码的时候,栈帧中需要多大的局部变量表,多深的操作数栈都已经完全确定了,并且写入到方法表的Code属性中,因此一个栈帧需要多大的内存,不会受到程序运行期变量数据的影响。

在 Java 虚拟机规范中,对这个区域规定了两种异常状况:

1) 如果线程请求分配的栈容量超过 Java 虚拟机栈允许的最大容量时,Java 虚拟机将会抛出一个 StackOverflowError 异常。

2) 如果J ava 虚拟机栈可以动态扩展,并且扩展的动作已经尝试过,但是目前无法申请到足够的内存去完成扩展,或者在建立新的线程时没有足够的内存去创建对应的虚拟机栈,那 Java 虚拟机将会抛出一个 OutOfMemoryError 异常。

2.3 本地方法栈

本地方法栈与虚拟机栈所发挥的作用是非常相似的,它们的区别不过是虚拟机栈为虚拟机执行 Java 方法服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。

在虚拟机规范中对本地方法栈中方法使用的语言、使用的方式与数据结构并没有强制规定,因此,具体的虚拟机可以自由实现它。与虚拟机栈一样,本地方法栈区域也会跑出 StackOverflowError 和 OutOfMemoryError 异常。

2.4 Java 堆

对于大多数引用来说, Java 堆 (Java Heap) 是 Java 虚拟机所管理的内存中最大的一块。Java 堆是被所有线程共享的一块内存区域,在虚拟机启动时创建。此内存区域的唯一目的就是存放对象实例,几乎所有的对象实例都在这里分配内存。Java 虚拟机规范中的描述是:所有的对象实例以及数组都要在堆上分配,但随着 JIT 技术的发展,栈上分配等优化技术会使用这一规范渐渐变得不那么“绝对”了。

Java 堆是垃圾回收的主战场,但 Java 虚拟机规范并没有强制规定垃圾收集器,它只要求虚拟机实现必须“以某种方式”管理自己的堆空间。

根据 Java 虚拟机规范的规定, Java 堆可以处于物理上不连续的内存空间中,只要逻辑上是连续的即可。在实现时,即可实现成固定大小的,也可以是可扩展的。如果在堆中没有内存完成实例分配,并且堆也无法再扩展时,将会抛出 OutOfMemoryError 异常

2.5 方法区

方法区(Method Area)与 Java 堆一样,是各个线程共享的内存区域,它用于存储已被虚拟机加载的类信息、常量、静态常量、即时编译器编译后的代码等数据。

Java 虚拟机规范对方法区的限制非常宽松,除了和 Java 堆一样不需要连续的内存和可以选择固定大小或者可扩展外,还可以选择不实现垃圾回收。相对而言,垃圾收集行为在这个区域是比较少出现的,主要原因是这个区域的回收“成绩”比较难以令人满意,尤其是类型的卸载,条件相当苛刻。

由于所有线程都共享方法区,因此它们对方法区数据的访问必须设计为是线程安全的。比如,假设同时有两个线程都企图访问一个名为 Lava 的类,而这个类还没有被装载入虚拟机,那么,这时只应该有一个线程去装载它,而另一个线程只能等待。

根据 Java 虚拟机规范,当方法区无法满足内存分配需求时,将抛 OutOfMemoryError 异常。

2.6 运行时常量池

运行时常量池是方法区的一部分。Class 文件中除了有类的版本,字段、方法、接口等描述信息外,还有一项信息是常量池(Constant Pool Table),用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后进入方法区的运行时常量池中存放。

Java 虚拟机对 Class 文件每一部分的格式都有严格规定,每一个字节用于存储哪种数据都必须符合规范上的要求才会被虚拟机认可、装载和执行,但对于运行时常量池,Java 虚拟机规范没有做任何细节的要求。不过,一般来说,除了保存 Class 文件中描述的符号引用外,还会把翻译出来的直接引用也存储在运行时常量池中。

运行时常量池相对 Class 文件常量池的另外一个重要特征是具备动态性,Java 语言并不要求常量一定在编译期才能产生,也就是并非预置入 Class 文件中常量池的内容才能进入方法区运行时常量池,运行期间也可能将新的常量池放入池中,这种特性被开发人员利用的比较多的便是 String 类的 intern() 方法。

既然运行时常量池是方法区的一部分,自然受到方法区内存的限制,当常量池无法再申请到内存时会抛出 OutOfMemoryError 异常。

小结

作为手把手教你实现 Java 虚拟机 的开篇,前面的几篇文章可能都会以虚拟机规范为主,后面,我们将会进入实际编码阶段。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,376评论 6 491
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,126评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,966评论 0 347
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,432评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,519评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,792评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,933评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,701评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,143评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,488评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,626评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,292评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,896评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,742评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,977评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,324评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,494评论 2 348