JVM(Java Virtual Machine),Java虚拟机是为了加载Java字节码文件,并对其进行分析、解释和执行。了解JVM对掌握Java语言基础知识很有必要,而了解JVM的架构是理解JVM的第一步。
JVM架构
JVM的架构可以主要分为三个部分:Class Loader Subsystem(类加载器子系统)、Runtime Data Areas(运行时数据区)和Execution Engine(执行引擎)。这里引用一张图来表示:
Class Loader Subsystem
类加载器子系统负责在运行时首次引用类的时候的加载、连接和初始化工作。
- 类加载器有三种:
1. Bootstrap Class Loader,这是默认的类加载器,主要加载jre核心库以及class文件;
2. Extension Class Loader,主要加载扩展包里面的jar;
3. Application Class Loader,主要加载应用程序级类路径(猜测为加载环境变量)。
- 连接分为三步:
1. verify 验证字节码文件的正确性;
2. prepare 准备为所有的静态变量分配内存和设置默认值;
3. resolve 所有的符号存储器引用都将替换为来自Method Area的原始引用(不太理解)。
- 初始化即为所有的静态变量分配内存、设置初始值以及执行静态块。
Runtime Data Areas
运行时数据区是JVM非常重要的一个部分,该区域分为方法区、堆区、栈区、PC计数器和本地方法栈5个部分。其中方法区和堆区是数据共享区域,其余三个部分都为线程私有。
Method Area
方法区主要存储类信息、常量、静态变量和JIT(即时编译器)编译编译后的代码等数据。方法区中还包含了一个非常重要的区域,叫做常量池。在字节码文件(Class文件)中,除了有类的版本、字段、方法、接口等相关信息描述外,还有常量池(Constant Pool Table)信息,用于存储编译器产生的字面量和符号引用。这些信息包括直接常量和对其他类型、方法、字段的符号引用。即常量池中存储着一个类中所有的对其他类、方法、字段的符号引用。(方法区里还存放着用于重写实现的方法表)
**重点注意:很多文章说方法区的数据是线程安全的,对于静态变量这种存在与方法区的变量,我很不能理解,因为根本就不是线程安全的。可能其他文章里说的线程安全是针对类的加载。
Heap
堆是JVM用于存放对象和数组的区域。每当new一个对象,就会在该区域分配一块内存,存在该对象的成员变量以及属性和该对象本身的类型等。JVM能够动态改变堆的大小,也可以直接指定初始大小。若剩余的内存不足以存在创建的对象,则抛出OutOfMemoryError。该区域是垃圾回收发生的主要区域。
Stacks
了解JVM栈之前,先了解一下栈帧(Stack Frame):一个栈帧随着一个方法的执行或者调用而被创建,该方法执行完成,则销毁。栈帧保存着该方法的局部变量、操作数栈、常量池的引用、返回地址和一些附加信息。需要注意的是,保存的数据除了基本数据类型的,其余都是引用。
JVM栈会为每一个线程开辟一个线程栈,一个线程每调用一个方法,就创建一个栈帧并将其入栈,执行完就出栈并销毁。正因为如此,所以JVM栈不存在垃圾回收。线程栈为该线程私有,其他线程不能访问。
JVM栈的大小可以设置或者动态增长,当内存不够用时,设置为固定值的方式会抛出StackOverflowError,而动态增长的方式会抛出OutOfMemoryError。
Program Counter Register
每个线程都有其单独的PC寄存器,用于存放当前执行指令的地址,执行完则指向下一条指令的地址。若调用的方法为本地方法,则为undefined。总的来说,PC寄存器是控制方法内代码执行顺序的。
Native Method Stack
本地方法栈就是调用本地方法时的数据区,这里的本地方法是指由非Java语言实现的,一般指C或者C++。支持本地方法调用的JVM才有该区域。本地方法栈也是线程私有的,与JVM栈类似。
Execution Engine
分配到运行时数据区的字节码将由执行引擎解释并执行。执行引擎包括解释器、JIT编译器、垃圾回收器、Java本机接口(JNI)和本地方法库(如果支持本地方法)。(下面的内容基本抄的)
-
解释器 能快速解释字节码,但是执行较慢。解释器的缺点是当一个方法被多次调用时,每次都需要新的解析。
-
JIT编译器 负责将重复的代码的字节码编译为本地代码,放入方法区中。这个本地代码将直接用于重复的方法调用,从而提高系统性能。
1. 中间代码生成器——生成中间代码
2. 代码优化器——负责优化上面生成的中间代码
3. 目标代码生成器——负责生成机器代码或本地代码
4. 分析器——一个特殊组件,负责查找热点,即该方法是否被多次调用
-
垃圾收集器:收集和删除未引用的对象。可以通过调用“System.gc()”触发垃圾收集器,但不能保证执行。JVM的垃圾回收收集创建的对象。
-
Java本机接口(JNI):JNI将与本地方法库进行交互,并提供执行引擎所需的本地库。
-
本地方法库:它是执行引擎所需的本地库的集合。
总结
运行时数据区是JVM中最重要的部分,也是加深理解Java中数据存储、传值的基础。总结为几点:
-
方法区中存放了所有的静态变量,堆存放了所有的对象和其成员变量以及数组,栈中只存放了基础数据类型和对象引用(地址)。因此,在方法中进行传值或者修改时,只要分清该变量的值是基础数据类型还是对象引用就不会出错。
-
方法区和堆的数据是线程共享的,因此数据不是线程安全的。