[toc]
JDK、JRE、JVM
JVM整体结构
- 整个结构分为上、中、下层。
- 第一层类装载器子系统负责将class文件从文件系统加载到内存中成为一个JVM的class对象结构,分为加载、链接、初始化。
- 方法区、堆所有线程共享;虚拟机栈(Java栈)、本地方法栈、程序计数器线程私有。
- 生成Class文件的过程是前端编译,执行引擎中涉及的翻译字节码解释执行和JIT中的即使编译执行为后端编译
Java代码执行流程
JVM的架构模型
Java编译器输入的指令流基本上是一种基于栈的指令集架构,另外一种指令集架构则是基于寄存器的指令架构。
具体来说,这两种架构之间的区别:
- 基于栈式架构的特点:
- 设计和实现更简单,适用于资源受限的系统
- 避开了寄存器的分配难题:使用零地址指令方式分配
- 指令流中的指令大部分是零地址指令,其执行过程依赖于操作栈。指令集更小,编译器容易实现。
- 不需要硬件支持,可移植性好,更好实现跨平台
- 基于寄存器架构的特点
- 典型的应用是x86的二进制指令集:比如传统的PC以及Android的Davlik虚拟机。
- 指令集架构则完全依赖硬件,可移植性差
- 性能优秀和执行更高效
- 花费更少的指令去完成一项操作
- 在大部分情况下,基于寄存器架构的指令集往往都以一地址指令、二地址指令和三地址指令为主,而基于栈式架构的指令集却是以零地址指令为主。
栈式架构即函数(方法)的实现是将函数中每一行代码要操作的所有变量不断压栈或者弹栈,使得待操作的变量永远存在栈顶,而操作指令则永远操作栈顶即可,所以无需在指令中包含操作数的地址,而是仅仅包含操作指令即可。
而寄存器架构则是将方法中的变量按照语言自己设计的一套规则分配到不同的寄存器中,这样函数中访问变量的代码也要按照这套规则去获取具体变量的寄存器地址进行访问。
很明显,前者确实更通用(不依赖具体硬件下可能不同的寄存器组)、容易实现(无需额外设计一套寄存器分配规则)。但是因为要将变量压栈入栈使得待操作数永远存在栈顶也使得增添了一些额外操作使得效率没有后者高。
- 栈式架构的指令集中指令为8位指令;寄存器架构的指令集中指令大多为16位指令。
¶Java字节码指令架构和x86指令架构区别示例
¶示例一
¶示例二
¶总结
由于跨平台性的设计,Java的指令都是根据栈来设计的。不同平台CPU架构不同,所以不能设计为基于寄存器的。优点是跨平台,指令集小,编译器容易实现,缺点是性能下降,实现同样的功能需要更多的指令。
JVM生命周期
¶虚拟机的启动
Java虚拟机的启动是通过引导类加载器(bootstrap class loader)创建一个初始类(initial class)来完成的,这个类是由虚拟机的具体实现指定的。
¶虚拟机的执行
- 一个运行中的Java虚拟机有着一个清晰的任务:执行Java程序。
- 程序开始执行时他才运行,程序结束时他就停止。
- 执行一个所谓的Java程序的时候,真真正正在执行的是一个叫做Java虚拟机的进程。
¶虚拟机的退出
- 程序正常执行结束
- 程序在执行过程中遇到了异常或错误而异常终止
- 由于操作系统出现错误而导致Java虚拟机进程终止
- 某线程调用Runtime类或System类的exit方法,或Runtime类的halt方法,并且Java安全管理器也允许这次exit或halt操作
- 除此之外,JNI(Java Native Interface)规范描述了用JNI Invocation API来加载或卸载Java虚拟机时,Java虚拟机的退出情况。
JVM发展历程
¶Sun Classic VM
¶Exact VM
¶Hotspot VM
¶JRockit
被Oracle收购,合并到Hotspot中。
¶J9
¶KVM、CDC/CLDC Hotspot
¶Azul VM
¶Liquid VM
¶Apache Harmony
¶Microsoft JVM
¶TaobaoJVM
¶DalvikVM
¶其他JVM
¶Graal VM
JVM规范主要内容
- 字节码指令集(相当于中央处理器CPU)
- Class文件的格式
- 数据类型和值(范围、实现方式)
- 运行时数据区
- 栈帧
- 特殊方法的实现
-
<init>
:这个不是我们定义的构造方法,是虚拟机内置的一个创建实例的方法,如果我们有给一个类定义实例成员变量并且赋值,那么这些实例成员变量将会在这个方法里面进行真正的赋值。通过JVM的invokespecial
指令来调用。虚拟机会在我们写的构造方法中将对
<init>
方法的调用代码以及构造代码块中的代码合并到构造方法的最前面,<init>
作为这个特殊方法的符号引用存在。所以new一个对象的过程为:- 为对象分配内存
- 调用
<init>
方法初始化我们赋值的成员变量 - 执行构造代码块中代码
- 执行构造函数中代码
-
<clinit>
:类或者接口的初始化方法,不包含参数,返回void,不是我们写的静态代码块,虚拟机会根据我们是否定义了静态代码块或者对静态变量进行了赋值而构造一个该方法进行执行。
-
- 需要支持一些类库
- 反射
- 加载和创建类或接口,如ClassLoader
- 连接和初始化类和接口的类
- 安全,如security
- 多线程
- 弱引用
- 异常处理
- 虚拟机的启动、加载、链接和初始化,包括字节码的执行引擎