Java虚拟机是一个执行其他程序的程序。这是一个简单的想法,但这也是最伟大的编程想法之一。它打破了当时的编程技术状况,直到今日,仍然支持着程序设计上的革新。
JVM有两个主要功能:允许Java程序运行在任何设备或操作系统(即“一次编写,到处运行”原则),以及管理和优化程序内存。Java在1995年发行时,所有的计算机程序都面向特定的操作系统编写,并且程序的内存由软件开发者管理。因此,JVM的诞生对于开发者是一个启示。
俯瞰JVM
有一个对JVM的专业定义是很有用的,同时在软件开发者的思考中,它也有日常化的概念。让我们做如下定义:
开发者们在谈到JVM时,通常我们谈的是运行在机器上尤其是服务器上的进程,这代表和控制了Java应用的资源分配。与之相比,JVM规范的概念描述的是构建一个执行这些任务的程序需要多少资源。
JVM由谁开发与维护?
JVM由一些非常聪明的开发者,联合企业与开源社区广泛地部署、使用和维护。OpenJDK是Sun微系统公司决定将Java开源的产物,现在由Oracle的Java团队管理,近些年大多数Java的重量级提升都由Oracle工程师们完成。
一个运行的JVM中最常见的交互作用是检查堆和栈中的内存使用情况,最频繁的调整是对JVM内存调度的调整。
垃圾收集
在Java之前,所有程序的内存都由程序员管理,在Java中,JVM接管了这一工作。JVM通过一个叫做垃圾收集的进程来管理内存,它持续地识别出并清除掉Java程序中不再被使用内存。垃圾收集发生在一个运行的JVM中。
早期Java遭到很多批评,因为它不像C++那么接近底层,因此速度不快。尤其是垃圾收集备受争议。自那时起,各种算法和探讨被提出和应用到垃圾收集上。经过持续的发展和优化,垃圾收集的效率已经被大大提升。
“ 接近底层 ”意味着什么?
当程序员说一门编程语言或平台“ 接近底层 ”,意思是指开发者可以以编码的形式管理操作系统的内存。理论上,程序员可以通过规定多少内存被使用以及什么时候销毁它而编写出更高性能的程序。大多数情况下,将内存管理工作委托给像JVM这样更高层的进程产生了更高的开发效率,并且相比自己管理更不容易出错。
可以说JVM拥有三个层面:规范、实现、和实例。让我们一一探讨。
1.JVM规范
首先,JVM是一个软件规范。为了使它的实现发挥尽可能大的创造力,JVM规范用一种有点写意的方式指明它的实现细节并不包含在规范内:
“想要正确地实现Java虚拟机,你只需要阅读类文件设计并且正确地执行里面所指定的操作即可。”
类似地,音乐家巴赫曾经这样描述创造音乐:
“你所需要去做的只是在正确地时间按下正确的琴键。”
因此,JVM所需要去做的就是使Java程序正确地运行。听起来很简单,也许从外部看起来确实很简单,但实际上这是一项大的工程,尤其是你还要赋予给Java语言强大的功能和灵活性。
作为一个虚拟机的 JVM
JVM是一个以便捷的方式运行Java类文件的虚拟机。作为一个虚拟机意味着JVM是底层的实际机器的抽象,比如你程序正运行之上的这些服务器。无论运行在什么操作系统或硬件,JVM都创造出了一个可预知的环境供程序稳定运行。但和真正的虚拟机不同的是,JVM并没有创建出一个虚拟的操作系统。把JVM描述为一个托管的运行时环境或是一个加工过的虚拟机更为贴切。
2.JVM实现
实施JVM的规范开发出一个真实的软件程序,我们称之为JVM实现。事实上,目前已经有很多JVM实现,开源闭源的都有。OpenJDK的HotSpot是一个作为参考的实现,它保留着全世界最全面的尝试和测试代码库。HotSpot同时也是最普遍使用的JVM。
几乎所有获得许可的JVM都是从OpenJDK和HotSpot JVM衍生而来,包括Oracle发行的JDK。开发者们根据OpenJDK创造一个许可的虚拟机通常是因为渴望去获得面向特定操作系统的性能提升。比如,你下载的JVM作为Java运行时环境的一部分就和操作系统密切相关联。
3.JVM实例
在JVM规范被实现和作为一个软件产品发行后, 你可以把它作为一个程序下载和运行它。这个下载的程序就是一个JVM实例(或者称为实例化的版本)。
大多数情况下,当开发者们谈到“JVM”,我们指的是运行在开发过程或生产环境中的JVM实例。你可能会说,“那台服务器上JVM用了多少内存”,或者,“难以置信我创造了一个循环调用导致了栈溢出错误直接把JVM搞崩溃了,这真是个是新手才犯的错误啊!”。
软件规范是什么?
软件规范是一个人类可读的文档,它描述了一个软件系统应该怎样运作。一份文档的目的是为工程师们创造一个编码的清晰描述和需求规范。
我们已经谈论过JVM在运行Java应用中所扮演的角色,但是它如何执行它的功能呢?为了运行Java应用,JVM依赖于Java的类加载器和一个Java执行引擎。
JVM中的Java类加载器
在Java中,一切都是类,所有的应用都由类构建而来。一个应用可以由一个或上千个类构成。为了运行Java应用,JVM必须将编译后的.class文件加载进例如服务器这样的环境中,使它们可以被访问到。JVM依赖类加载器去执行这个功能。
Java类加载器是JVM的一部分,它可以将类加载进内存并且使它们可以被获取到和执行。类加载器使用到了像懒加载和缓存的技术使类的加载能够尽可能地高效率。也就是说,类的加载并不是一个史诗级的难题,只是一个轻便的内存管理过程,因此这个技术相对来说是简单的。
每一个虚拟机都包含一个类加载器。JVM规范描述了在运行时查询和操纵类加载器的标准方法,但是具体还是由JVM实现来负责完成这些功能。从开发者的角度来看,底层的类加载器机制是个典型的黑盒子。
一旦类加载器完成加载类的工作后,JVM开始执行每一个类的代码。执行引擎是JVM处理这个功能的组件,对于JVM来说,它是必不可少的,事实上,对于所有实际应用,它都是JVM实例的一部分。
执行代码包括对系统资源的权限管理。JVM执行引擎位于运行中的程序——满足它们对文件、网络和内存资源的需求——和提供这些资源操作系统之间。
执行引擎如何管理系统资源
系统资源可以被分为两大类:内存和其他。
让我们回顾一下,JVM负责处理不再被使用的内存,垃圾收集是执行处理任务的机制。JVM同时也负责分配和保持开发者授意的参照结构。举个例子,JVM执行引擎负责接收JVM中类似“new”这样的关键字,然后将它转化为向指定系统发出的内存分配请求。
不只是内存,JVM执行引擎还负责管理文件系统的访问以及网络I/O资源。因为JVM可以跨系统交互,所有这些不是很困难的任务。除了管理每个应用的资源需求,执行引擎还必须适应每个系统的环境。这也是JVM可以处理各种各样需求的原因。
1995年,JVM的问世带来了两个革命性的概念,成为了现代软件发展的标准进化方向:“一次编写,到处运行”以及自动的内存管理。软件的互通性在那时是个大胆的概念,但在今天极少的开发者还需要去考虑它。类似地,软件前辈们不得不自己管理内存,而我们这一代是在垃圾收集的概念中成长的。
我想说James Gosling (译者注:Java发明者)和Brendan Eich(译者注:javascript发明者)发明了现代编程,但在后来的几十年,成千上万的后来者在它们的想法基础上,进行了重新定义和构建。尽管JVM最初只是为Java使用,但在今天它已经进化成可以支持众多脚本和编程语言,包括Scala, Groovy, 和Kotlin。往前看,很难看到一个JVM不是重要部分的未来。