一、基础
1. 与 Java 语言
JVM 与 Java 语言没有必然的联系,只与 class 文件格式有关联。
2. 与 .class 文件
- class 文件包含了 Java 虚拟机指令集(字节码)和符号表,以及其他辅助信息。
- JVM 在 class 文件中施加了许多强制性语法和结构化约束。
- 凡是能用 class 文件正确表达出来的编程语言都可以在 JVM 中执行。
3. 数据类型
- 原始类型(基本类型),如 int、double、long <---> 原始值
- 引用类型,如 reference <---> 引用值
JVM 希望类型检查能在程序运行前(编译阶段)完成。
(1)基本类型
① 整数类型
- byte 8位有符号二进制补码整数,默认值为0(下面3个也是)
- short 16位有...
- int 32位有...
- long 64位有...
- char 16位无符号整数表示的、指向基本多文种平面(BMP)的 Unicode 码点,UTF-16 编码,默认为 null('u0000')
② 浮点数类型
- float 32位单精度(IEEE 754标准)
- double 64位单精度(IEEE 754标准)
- 五个特殊数值:正数0、负数0、正无穷大、负无穷大、NaN
- 除了 NaN 之外,其他值都是 有序的
- 有且仅有一个值 NaN 与自身比较返回 false
- 任何数字与 NaN 比较都会返回 true
③ returnAddress
值指向虚拟机一个地址
④ boolean
在编译之后一般使用 int 数据类型代替
(2)引用类型
- class type
- array type
- interface type
数据最外维是 组件类型,最里面维度称为 元素类型。如List,其中List是组件类型,Integer是元素类型。
顺便一提,Integer具有一个缓冲池 -128~127,默认情况下直接从池中取值(取的值相等则变量地址相同),除非 new Integer(10) 或者取超出范围的值。
4. 运行时数据区
(1)pc 寄存器
- 每个 JVM 线程都有自己的 pc 寄存器
- 每个线程只执行一个方法
- 容量至少应当能保存一个 returnAddress 类型的数据或者一个与平台相关的本地指针的值
(2)虚拟机栈
- StackOverflowError 线程请求分配的栈容量超过 JVM 栈允许的最大容量
- OutOfMemoryError 栈可以动态扩展,在尝试扩展时无法申请到足够的内存
(3)堆
- 是可供各个线程共享的 运行时内存区域
- 是供所有类实例和数组对象 分配内存 的区域
- 所使用的内存,不需要 保证是 连续 的
- 抛出OutOfMemoryError
(4)方法区
- 是可供各个线程共享的 运行时内存区域
- 包括【 运行时常量池,字段和方法数据,构造函数和普通方法的字节码内容,在类、实例、接口初始化时用到的特殊方法】
- 抛出OutOfMemoryError
(5)运行时常量池
- class 文件中每一个类或接口的常量池表
- 抛出OutOfMemoryError
(6)本地方法栈
- 支持 native 的执行(其他语言编写的方法)
- 抛出StackOverflowError
- 抛出OutOfMemoreyError
5. 帧栈
- 存储数据和部分过程结果的数据结构
- 也用来处理动态链接、方法返回值和异常分派
- 随着方法调用而创建,随着方法结束而销毁
- 为程序提供调试功能
- 当前栈帧 - 当前类 - 当前方法
(1)局部变量表
- 索引访问
- long 和 double 的值占用两个连续的局部变量
(2)操作数栈
- long 和 double 占用两个单位的栈深度
(3)动态链接
- 每个栈帧都包含一个指向当前方法所在类型的运行时常量池的引用
- 晚期绑定?
6. 浮点运算
(1)JVM 的浮点操作
- 遇到被0除、上下限溢出和非精确时,不会抛出错误
- 不支持 IEEE 754 的信号浮点比较
- 舍入操作:向最接近数舍入模式,如果无法精确,则舍入到最低有效位为 0 的那个值
- 浮点数值 -> 整型数值:向零舍入
- 不支持 IEEE 754 的单精度扩展和双精度扩展格式
(2)浮点模式
- 每个方法都有这项属性
- 分为 FP-strict 模式、非FP-strict模式
- 体现在 class 文件的方法 method_info 结构的访问标志 access_flags 中的 ACC_STRICT 标志位
- JDk 1.1以及之前版本的编译器是 非FP-strict模式
(3)数值集合转换
- 支持扩展指数集合的 JVM 实现数值在标准浮点数集合与扩展指数集合之间饿映射关系是允许或必要的
- float => 单精度浮点数集合中的元素
- double => 双精度浮点数集合中的元素
7. 特殊方法
(1) 构造方法(初始化方法)
(2)签名多态性
- 由 java.lang.invoke.MethodHandle 类进行声明
- 只有一个类型为 Object[] 的形参
- 返回值为 Object
- ACC_VARARGS 和 ACC_NATIVE 标志被设置
8. 异常
(1)同步异常
- athrow 字节码指令被执行
- 虚拟机同步检测到程序发生非正常的执行情况
(2)异步异常
- 调用了 Thread 或者 ThreadGroup 的 stop 方法
- JVM 实现发生了内部错误
- 每个执行的方法都配备0~多个异常处理器
9. 类库
需要JVM特殊支持的类
- 反射:java.lang.reflect、Class
- 加载和创建类或接口的类:ClassLoader
- 连接和初始化类或接口:ClassLoader
- 安全:java.security
- 多线程:Thread
- 弱引用:java.lang.ref
10. 公有设计、私有实现
- == 统一设计、各自实现
二、垃圾收集器与内存分配策略
1. 判断对象存活情况
(1)引用计数算法
- 优点:判定效率高,实现简单
- 缺点:难以解决对象之间相互引用的问题
- JVM 不使用
(2)可达性分析算法
- 特点:GC Roots
2. 垃圾收集算法
(1)标记 - 清除算法
- 缺点:效率不高,产生大量不连续的碎片
(2)复制算法
- 特点:内存空间划分成两块
- 缺点:内存空间缩小一半,对象存活率较高时效率变低;不适用于老年代。
(3)标记 - 整理算法
- 特点:向一端移动,直接清理端边界以外的内存
(4)分代回收算法
- 新生代:复制算法
- 老年代:标记 - 清理/整理
3. HotSpot 算法实现
- 核心:根节点枚举,数据结构采用 OopMap
- 在特定位置(安全点 SafePoint)记录
- 安全区域(SafeRegion):在一端代码片段之中,引用关系不会发生变化。在这个区域中的任一地方开始 GC 都是安全的
4. 垃圾回收器
- 新生代:Serial、ParNew、Parallel Scavenge
- 老年代:CMS、Serial Old(MSC)、Parallel Old
- 任意代:G1
- Serial 单线程
- ParNew 多线程版本的 Serial
5. CMS 收集器
以获取最短回收停顿时间为目标的收集器;常用在互联网或者 B/S 系统的服务端上。【注重服务端的响应速度,希望系统停顿时间最短】
过程
- 初始标记 stw
- 并发标记
- 重新标记 stw
- 并发清除
缺点一:对 CPU 资源敏感
i-CMS 增量式并发收集器,适用于CPU数量过少时,收集器线程资源占用率高的情况(已被 deprecated,效率低,不提倡使用)
缺点二:无法处理浮动垃圾
缺点三:使用标记 - 清除算法
6. G1 收集器
面向服务端
- 并行、并发
- 分代收集
- 空间整合
- 可预测的停顿,建立可预测的停顿时间模型
过程
- 初始标记
- 并发标记
- 最终标记
- 筛选回收
7. 内存分配与回收策略
- 对象优先在新生代 Eden区分配,无空间时发起一次 Minor GC
- 大对象直接进入老年代
- 长期存活的对象将进入老年代(年龄计数器)
- 动态对象年龄判断(年龄相对较大直接进入老年代)
- 空间分配担保(老年代有足够空间容纳新生代)
8. 关于GC
- 新生代:Minor GC
- 老年代:Major GC 或 Full GC
三、内存模型与线程
优化
- 指令重排序
内存间交互操作
- lock
- unlock
- use
- assign
- load
- store
- read
- write
volatile
- 所有线程可见
- 禁止指令重排序优化(机器指令增加 lock)
先行发生关系
- 程序次序规则
- 管程锁定规则
- volatile 变量规则
- 线程启动规则
- 线程终止规则
- 线程中断规则
- 对象终结规则
- 传递性
线程实现
- 内核线程(需要系统调用:系统态<->用户态)
- 用户线程
- 用户线程 + 轻量级进程
- 抢占式调度,而不是协同式调度变量