1、引言
1.1 虚拟机
? 所谓虚拟机(Virtual Machine),就是一台虚拟的计算机。它是一款软件,用来执行一系列虚拟计算机指令。大体上,虚拟机可以分为系统虚拟机和程序虚拟机。
- Visual Box,Mware就属于系统虚拟机,它们完全是对物理计算机的仿真,提供了一个可运行完整操作系统的软件平台。
- 程序虚拟机的典型代表就是Java虚拟机,它专门为执行单个计算机程序而设计,在Java虚拟机中执行的指令我们称为Java字节码指令。
无论是系统虚拟机还是程序虚拟机,在上面运行的软件都被限制于虚拟机提供的资源中。
1.2 java虚拟机(JVM)
? Java虚拟机是一台执行Java字节码的虚拟计算机,它拥有独立的运行机制,其运行的Java字节码未必都是由Java语言编译而成。Java技术的核心就是Java虚拟机(JVM,Java Virtual Machine),因为所有的Java程序都运行在Java虚拟机内部。Java虚拟机就是二进制字节码的运行环境,负责装载字节码到其内部,解释/编译为对应平台上的机器指令执行。每一条Java指令,Java虚拟机规范中都有详细定义,如怎么取操作数,怎么处理操作数,处理结果放在哪里。
JVM平台的各种语言可以共享Java虚拟机带来的跨平台性、优秀的垃圾回器,以及可靠的即时编译器。
特点:
- 一次编译,任何平台运行
- 自动内存管理
- 自动垃圾回收功能
1.3 JVM的位置
? JVM是运行在操作系统之上的,它与硬件没有直接的交互。
1.4 JVM整体结构(简化版的)
JVM主要由三部分组成:
- 类加载系统 (Class Loader Subsystem)
- 运行时数据区(Runtime Data Area)
- 执行引擎(Execution Engine)
详细结构图
1.5 JVM生命周期
1.5.1 虚拟机的启动
Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。
1.5.2 虚拟机的执行
- 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。
- 程序开始执行时他才运行,程序结束时他就停止。
- 执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程。
1.5.3 虚拟机的退出
有如下的几种情况:
2、JVM各个组成部分
2.1 类加载系统 (Class Loader Subsystem)
? 类加载系统负责动态加载类,在运行时(而非编译时),当一个类初次被引用的时候,它将被加载、链接、装入(初始化)。
2.1.1 加载(Load)
类加载子系统主要 ?三个具体的类加载器,包括引导加载器(BootStrap ClassLoader), 扩展加载器(Extension ClassLoader),应用加载器(Application ClassLoader),自定义类加载器(CustomClassLoader)
-
自定义类加载器(CustomClassLoader):这类加载器有用户自己定义,并可以加载指定路径下的class文件。 -
应用程序类加载器(App ClassLoader):加载应用程序classpath目录下的所指定的类库,继承URLClassLoader。 -
扩展类加载器(Extension ClassLoader):加载JRE/lib/ext下面指定要用的jar包,继承URLClassLoader。 -
启动(根)类加载器(Bootstrap ClassLoader):非常的特殊,它不是java类,该类加载器由C++实现,它嵌套在JVM内核里,当JVM启动时该类加载器就启动了。负责加载Java基础类,对应加载的文件是JRE/lib/ 目录下的rt.jar、resources.jar、charsets.jar等。
以上类加载器通过双亲委派模型执行类加载:
? 当一个Class文件要被加载时,先不看自己的加载器,首先委派给应用程序加载器(App ClassLoader),此时会判断是否加载此类,如果加载过,就直接直接加载,否则继续向上委派,此时委派给扩展类加载器(Extention ClassLoad),此时会判断是否加载此类,如果加载过,就直接直接加载,否则继续向上委派,此时委派给启动类加载器(BoootStrap ClassLoad),时会判断是否加载此类,如果加载过,就直接直接加载,否则返回给自定义加载器进行加载,如果此时定义加载器还不能加载,就会抛出ClassNotFond。
2.1.2 链接(Link)
- 校验(Verify) – 字节码验证器会验证生成的字节码是否正确,如果校验失败,会返回校验错误。
- 准备(Prepare) – 所有的静态变量会被分配内存并且赋默认值。
- 解析(Resolve) – 所有的符号内存引用 symbolic memory references 将被方法区的原始引用替代。
2.1.3 初始化
初始化是类加载的最后一步,在这里静态变量会被赋予初值,静态方法区会被执行。
2.2 运行时数据区(Runtime Data Area)
运行时数据区可以划分为五个区域
- 方法区(Method Area) – 所有的类级别的数据都会存储到这里,包括静态变量。方法区在JVM中是唯一的,共享资源。
- 堆区(Heap Area) – 所有的对象和她对应的实例变量和数组都被存储在这个区域。堆区也是唯一的、线程共享的资源,所以存储在该区域的数据并不是线程安全的。
- 堆栈区(Stack Area):对于每个线程,将创建单独的运行时堆栈。对于每个方法调用,将在堆栈存储器中产生一个条目,称为堆栈帧。所有局部变量将在堆栈内存中创建。堆栈区域是线程安全的,因为它不共享资源。堆栈框架分为三个子元素:
- 局部变量数组(Local Variable Array):与方法相关,涉及局部变量,并在此存储相应的值
- 操作数堆栈(Operand stack):如果需要执行任何中间操作,操作数堆栈将充当运行时工作空间来执行操作
- 帧数据(Frame Data):对应于方法的所有符号存储在此处。在任何异常的情况下,捕获的区块信息将被保持在帧数据中;
-
PC寄存器(PC Registers):每个线程都有单独的PC寄存器,用于保存当前执行指令的地址。一旦执行指令,PC寄存器将被下一条指令更新; -
本地方法堆栈(Native Method stacks):本地方法堆栈保存本地方法信息。对于每个线程,将创建一个单独的本地方法堆栈。
2.3 执行引擎
分配给运行时数据区的字节码将由执行引擎执行,执行引擎读取字节码并逐个执行。
3.1 解释器:解释器更快地解释字节码,但执行缓慢。解释器的缺点是当一个方法被调用多次时,每次都需要一个新的解释;
3.2 JIT编译器:JIT编译器消除了解释器的缺点。执行引擎将在转换字节码时使用解释器的帮助,但是当它发现重复的代码时,将使用JIT编译器,它编译整个字节码并将其更改为本地代码。这个本地代码将直接用于重复的方法调用,这提高了系统的性能。JIT的构成组件为:
- 中间代码生成器(Intermediate Code Generator):生成中间代码
- 代码优化器(Code Optimizer):负责优化上面生成的中间代码
- 目标代码生成器(Target Code Generator):负责生成机器代码或本地代码
- 分析器(Profiler):一个特殊组件,负责查找热点,即该方法是否被多次调用;
3.3 垃圾收集器(Garbage Collector):收集和删除未引用的对象。可以通过调用“System.gc()”触发垃圾收集,但不能保证执行。JVM的垃圾回收对象是已创建的对象。
Java本机接口(JNI):JNI将与本机方法库进行交互,并提供执行引擎所需的本机库。
本地方法库(Native Method Libraries):它是执行引擎所需的本机库的集合。
原文连接
|