原文地址:《The JVM Architecture Explained 》
每个Java开发者都知道字节码是被JRE(Java运行时环境)所执行的。但是许多人并不知道实际上JRE是Java虚拟机(JVM)的实现。它分析字节码,解释代码并执行它。作为一个开发者,了解JVM的体系结构是非常重要的,因为它使我们能更有效的编写代码。在这篇文章中,我们将更深入的了解Java中的JVM体系结构和JVM的不同组件。
什么是JVM?
虚拟机是物理机器的软件实现。Java语言当时是基于编写一次到处运行的理念被开发出来的,其中到处运行指的是运行在虚拟机上。编译器将java文件编译成.class文件,然后.class文件被输入到JVM中,JVM会加载并运行这些.class文件。下图展示了JVM的体系结构。
JVM体系结构
JVM是如何工作的?
正如上图展示的,JVM被分为以下三个主要的子系统:
- 类加载器
- 运行时数据区域
- 执行引擎
1.类加载器
Java的动态类加载功能是由类加载子系统处理。它加载,链接,并初始化类(当在运行时第一次引用该类时)。
1.1加载
类都通过该组件进行加载。BootStrap ClassLoader、 Extension ClassLoader、Application ClassLoader这三个类加载器协同完成这个目标。
- BootStrap ClassLoader:负责从引导类路径加载类,除了rt.jar。将给予此加载程序最高优先级。
- Extension ClassLoader:负责加载ext文件夹中的类。
- Application ClassLoader:负责从应用程序类路径加载类。
以上这些类加载器将遵循双亲委托机制装载类文件。(即当一个类需要加载时,先从Bootstrap加载,没有的话,再从Extension加载,还是没有的话,才从Application加载。)
1.2链接
- 验证:字节码验证器将会验证生成的字节码正确与否。如果验证失败,我们将得到验证错误信息。
- 准备:为所有静态变量开辟内存空间并赋默认值。
- 解析:将所有符号引用替换为方法区中的原始引用。
1.3初始化
这是类加载的最后阶段。所有静态变量将会被赋予初始值并且静态构造块将会被执行。
2.运行时数据区
运行时数据区被分为五个主要的区域。
- 方法区:所有类级别的数据将会被存于此处。包括静态变量。一个虚拟机仅有一个方法区。它属于共享资源。
- 堆区:所有对象及对象中的实例变量和数组都将存于此处。一个虚拟机也仅有一个堆区。因为方法区和堆区在多线程情况下是共享内存的,所以存于其中的数据并非线程安全的。
- 虚拟机栈:对于每一个线程,都将创建一个单独的运行时方法栈。对于每一个方法调用,栈中都会生成一个栈帧。所有本地变量都将在栈中创建。栈区域是线程安全的,因为它并非共享资源。栈帧又被分为三个主要组成:
- 局部变量数组:与方法相关,涉及多少局部变量以及相应的值将存在这里。
- 操作数栈:如果需要执行任何中间操作,操作数栈充当运行时工作区来执行操作。
- 帧数据:与该方法对应的所有符号都存储在这里。在任何异常的情况下,catch块信息将会保存在帧数据中。
- 程序计数器:每个线程都拥有独立的程序计数器。它保存当前执行指令的地址,一旦指令执行,程序计数器将更新为下一条指令。
- 本地方法栈:本地方法栈保存了本地方法调用信息。对于每一个线程,都会创建独立的本地方法栈。
3.执行引擎
分配到运行时数据区的字节码将会被执行引擎执行。执行引擎将会读取字节码并逐个执行。
-
解释器:解释器解释字节码快,但执行慢。并且在多次调用同一个方法时,都需要重新解释。
-
JIT编译器:JIT编译器弥补了解释器的缺点。执行引擎会借助解释器去转换字节码。但是当它发现有重复的代码时,会使用JIT去编译整份字节码并将它改为本地代码。本地代码将会被直接使用在重复的方法调用,从而提高系统的性能。
- 中间代码生成器:生成中间代码
- 代码优化器:负责优化中间代码
- 目标代码生成器:负责生成机器码或本地代码
- 分析器:特殊组件,负责发现热点代码,即方法是否被多次调用。
-
垃圾回收器:回收和删除未被引用的对象。垃圾回收可被System.gc()方法触发,但是不保证立马执行。