深入理解Java虚拟机(第三版)
2020-03-17 17:35:07 18 举报
AI智能生成
登录查看完整内容
深入理解Java虚拟机-第三版,最新版本一发售,本人立马购买精读了2遍. 书中讲解了很多新知识点, 包括: JVM编译器接口,最新的收集器 Garbage First , ZGC, 最新及时编译器graal等等. 本图尽可能详尽的把原著的每个概念列清楚, 并致力于把每个重要的知识点都记录清楚.
作者其他创作
大纲/内容
七. 虚拟机类加载机制
类的生命周期
6种情况必须立即对类进行初始化
1). 使用如下字节码指令的时候
对应的Java代码中的使用场景
使用new关键字实例化对象的时候
调用一个类型的静态方法的时候
遇到下面的字节码指令的时候
2). 使用java.lang.reflect包的方法的对类型进行反射调用的时候
类加载的过程
1. 加载
1). 通过一个类的全限定名来获取定义此类的二进制字节流
2). 通过字节流代表的静态存储结构转化为方法区的运行时数据结构
注意
该阶段用户程序可以通过自定义类加载器的方式进行局部参与
2. 验证
确保Class文件的字节流中包含的信息符合Java虚拟机规范的约束要求
4个阶段
1). 文件格式验证
验证点
是否以魔数开头: 0xCAFEBABE
常量池中的常量是否有不被支持的类型(检查常量的tag)
...
2). 元数据验证
对字节码描述信息进行语义分析并对元数据信息进行语义校验
这个类是否有父类
这个类是否继承了不允许被继承的类
如果这个类是抽象类是否实现了其父类或接口中所要求的所有的抽象方法
3). 字节码验证
保证在任何时刻操作数栈的数据类型与质量代码序列都能配合工作
保证任何跳转指令都不会跳转到方法体以外的字节码指令上
4). 符号引用验证
符号引用中通过自费重描述的全限定名是否能找到对应的类
在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段
3. 准备
类的静态变量(static)被分配内存并设置初始值的过程
4. 解析
Java虚拟机将常量池内的符号引用替换为直接引用的过程
5. 初始化
<clinit>()是编译器自动收集类中的所有类变量赋值动作和静态语句块中的语句的合并
类加载器
类加载阶段\"通过一个类的全限定名来获取描述该类的二进制字节流\
类与类加载器
类加载器用于实现类的加载动作
双亲委派模型
好处: Java中类随着他的类加载器仪器具备了一种带有优先级层次的关系
3类系统类加载器
启动类加载器(Bootstrap ClassLoader)
扩展类加载器(Exension ClassLoader)
应用程序类加载器(Application ClassLoader)
负责加载用户类路径(ClassPath)上所有的类库
八. 虚拟机字节码执行引擎
运行时栈帧结构
概念
局部变量表
变量槽
操作数栈
操作数栈的最大深度也在编译期就确定了最大深度 写入了Code属性的max_stacks数据项中
动态连接
方法返回地址
附加信息
方法的调用
解析
分派
分派的调用过程将会揭示一些多态的最基本的体现
静态类型
实际类型
变化的结果只有在运行期才可确定
静态分派
选择哪个重载版本
动态分派
在运行期间根据实际类型确定方法执行版本的分派过程称为动态分派
选择哪个重写版本
单分派和多分配
单分派: 根据一个宗量对目标方法进行选择
多分派: 根据多于一个宗量对目标方法进行选择
动态类型语言
定义
java.lang.invoke
invokedynamic指令
每个含有invkedynamic指令的位置都被称为\"动态调用点\
可以获得3项信息
引导方法
方法类型
名称
字节码解释执行
传统编译过程: 编写的源码程序-> 词法分析-> 语法分析-> 抽象语法树->中间代码-> 生成器-> 目标代码
解释执行过程: 编写的源码程序-> 词法分析-> 语法分析-> 抽象语法树->指令流-> 解释器-> 解释执行
九. 类加载及子系统案例
Tomcat 正统的类家在架构
解决的问题
部署在同一个服务器上的两个web应用程序所使用的Java类库可实现相互隔离
部署在同一个服务器上的两个web应用程序所使用的Java类库可实现相互共享
服务器需要保证自身的安全不受部署的web应用程序的影响
支持jsp的web服务器
目录结构和类加载器
OSGi
基于Java语言的动态模块化规范
字节码生成技术
Backport工具
把高版本的JDK编写的代码放到低版本JDK环境中部署运行
十. 前端编译与优化
代表性编译器产品
Javac编译器
编译过程
1). 准备阶段: 初始化初始化插入式注解处理器
2). 解析与填充符号表的过程
3). 插入式注解处理器的注解处理过程
4). 分析与字节码生成的过程
数据流和控制流分析
解语法糖
字节码生成
Java语法糖
泛型
类型擦除
缺陷
运行期间无法获取泛型的类型信息
结论
值类型与未来的泛型
提供\"值类型\"的语言层面的支持
自动拆装箱
\"==\" 运算在遇到算术运算符时会自动拆箱
条件编译
条件为常量的if语句可以实现条件编译
十一. 后端编译与优化
即时编译器
解释器与编译器
热点代码
被多次调用的方法
被多次执行的循环体
热点探测判定
基于采样的热点探测
基于计数器的热点探测
提前编译器
编译器优化技术
方法内联
把目标方法的代码原封不动的的\"复制\
方法内联的条件
被调用方法是否是热点代码
被调用法法是否大小合适
运行时方法是否可唯一确定
逃逸分析
代码优化
栈上分配
标量替换
标量
聚合量
同步消除
公共子表达式消除
数组边界检查消除
Graal编译器
代码中间层表示
代码优化与生成
生成理想图
字节码生成理想图: 可以按照字节码解释器的思路去理解它
理想图的操作
理想图转化为机器码
十二. Java内存模型与线程
Java内存模型
主内存和工作内存
Java内存模型规定了所有的变量都存在主内存中
内存间的交互操作
lock 锁定
unlock 解锁
read 读取
load 载入
use 使用
assign 赋值
store 存储
write 写入
针对8中操作的规则
不允许一个线程丢弃它最近的assign操作
不允许一个线程没有经过assign操作的值同步会主线程
一个新的变量只能在主内存中产生
一个变量同一个时刻值运行一条线程对其进行lock操作
volatile型变量的特殊规则
两项特性
禁止指令重排序
volatile规则
原子性
可见性
有序性
先行发生原则
程序次序规则
管程锁规则
一个unlock操作先行发生于后面对同一个锁的lock操作
volatile变量规则
对一个volatile变量的写操作先行发生于后面对这个变量的读操作
线程启动规则
Thread对象的start()方法先行发生于此线程的每个动作
线程终止规则
线程中所有发生的操作都先行发生于线程的终止操作
线程中断规则
对线程的interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生
对象的直接规则
一个对象的初始化完成先行发生于它的finalize()方法的开始
传递性
Java的线程
Java的每一个线程都直接映射到一个操作系统原生线程上来实现的
线程的状态
新建 New
新建: 创建后尚未启动
运行 Running
无限等待 Waiting
限期等待 Timed Waiting
阻塞 Blocked
结束 Terminated
已经终止的线程状态
十三. 线程安全与锁优化
线程安全
线程安全的定义
Java语言中的线程安全
共享操作数据分为5类
不可变
绝对线程安全
相对线程安全
通常意义上讲的线程安全
线程兼容
线程对立
线程安全的实现方法
互斥同步(悲观锁)
同步
缺点
互斥
实现同步的一种手段
synchrionized
被synchronized修饰的同步块对同一条线程来说是可重入的
Lock接口
ReentrantLock 可重入锁
等待可中断
公平锁
锁绑定多个条件
指一个ReentrantLock对象可以同时绑定多个Condition对象
ReentrantReadRriteLock
非阻塞同步(乐观锁)
ABA问题
无同步方案
锁优化
自旋锁
锁消除
锁粗化
轻量级锁
加锁工作过程
JVM使用CAS尝试把对象的Mark Wrod更新为锁记录的指针
偏向锁
深入理解Java虚拟机第三版
一. 走进Java
Java技术体系
组成部分
Java程序设计语言
不同硬件平台的Java虚拟机
Class文件格式
Java类库API
第三发Java类库
产品线
Java Card
Java小程序Applets小内存平台
Java ME
支持Java程序运行在移动终端上的平台
Java SE
支持桌面级应用的Java平台
Java EE
支持使用多层架构的企业应用的Java平台
Java发展史
Java虚拟机
使用范围最广的JVM: HotSpot
面向移动和嵌入式市场: Mobile VM
BEA System 公司的 JRockit
IBM 的 IBM J9
......
展望Java技术的未来
无语言倾向
Graal VM
先进的即时编译器
向Native迈进
Java虚拟机不断优化增强
Java体系提供更多的接口
语言语法持续增强
二. 内存区域和内存溢出
运行时数据区域
程序计数器
Java虚拟机栈
会出现两类异常
本地方法栈
Java堆
组成
方法区
变化
HostSpot虚拟机对象
对象的创建
过程
在常量池中检查类的符号引用
堆中分配内存
指针碰撞
空闲列表
并发问题
JVM采用CAS保证更新操作的原子性; 另一种方式: 把内存分配动作按照线程划分在不同的空间中进行
对象头设置
执行实例构造函数<init>()方法
此时该对象才算完全构造出来
对象的内存布局
对象头
运行时数据
官方称之为 \"Mark Word\
指向类型元数据的指针
通过这个指针确定该对象是哪个类的实例
实例数据
对齐填充
对象的访问定位
通过栈上的reference数据来操作堆上的具体对象
访问方式
使用句柄访问
优势
使用直接指针访问
OutOfMemoryError异常
Java堆溢出
-Xmx 最大堆内存; -Xms初始堆内存
虚拟机栈和本地方法栈溢出
-Xss 设置栈内存容量
字符串常量池
String::intern()
方法区溢出
三. 垃圾收集区与内存分配策略
GC要解决的问题
哪些内存需要回收
什么时候回收
如何回收
哪些对象可以回收
引用计数法
优点
无法解决对象之间互相引用的问题
可达性分析算法
通过一系类称之为\"GC Roots\
GC Roots
方法区中静态属性引用的对象
方法区中常量引用的对象
本地方法栈中JNI引用的对象
被同步锁持有的对象
引用的分类
强引用
软引用
弱引用
虚引用
finalize()方法
任何对象的finalize()方法都只能被JVM自动调用一次
方法区的GC
不再使用的类型
该类所有的实例都被回收
加载该类的类加载器已被回收
该类对应的Class对象没有被其他地方引用
垃圾收集算法
分代收集理论
弱分代假说
绝大多数对象是朝生夕灭
强分代假说
熬过多次GC过程的对象就越难消亡
跨代引用假说
跨代引用相对于同代引用来说仅占极少数
标记-清除算法
实现简单
执行效率不稳定
内存空间碎片化问题
标记-复制算法
浪费内存空间
标记-整理算法
算法相对复杂
HotSpot算法细节
并发和并行
根节点枚举
OopMap
安全点
用户线程达到安全点并暂停的方案
抢先式中断
主动式中断
安全区域
记忆集和卡表
卡表: 针对\"记忆集\" 这种定义的一个具体实现
写屏障
为了解决 如何维护卡表元素状态
这里的写屏障可以看做JVM层的\"引用类型字段赋值\
并发的可达性分析
按照\"是否被GC收集器访问过\"这个条件标记为三种颜色
白色: 未被收集器访问过
赋值器插入至少一条黑色对象到白色对象的引用
赋值器删除了全部从灰色对象到白色对象的直接或者间接引用
增量更新
原始快照(SATB)
垃圾收集器
Serial 收集器
ParNew 收集器
目前仅有他能跟CMS收集器配合
Parallel Scavenge 收集器
吞吐量 = 运行用户代码时间/ (运行用户代码时间+运行垃圾收集的时间)
Serial Old 收集器
Parallel Old 收集器
CMS 收集器
Concurrent Mark Sweep 以获取最短回收停顿时间为目标的收集器
步骤
并发标记: 多线程并发从GC Roots的直接关联的对象遍历整个对象图的过程
并发清除: 并发清理掉已经被标记的对象
无法处理\"浮动垃圾\" 只能等到下次GC时被回收
采用\"标记-清除\
G1 收集器
G1 会跟踪统计每个Region的回收的\"价值\" 和 \"收益\
待解决的问题
跨Region引用对象的处理 : 记忆集和卡表 解决
并发标记阶段如何保证收集线程和用户线程互不干扰: 采用 原始快照算法实现
4个步骤
可指定最大停顿时间
G1 从整体看是基于\"标记-整理\
Shenandoah 收集器
目标是能在任何堆大小下都能把垃圾收集的停顿时间限制在十毫秒以内
9个步骤
并发清理: 清理掉那些整个区域中都不存在一个存活对象的Region
并发更新引用: 多线程并发执行 真正把堆中所有指向旧对象的引用修正到复制后的新地址
ZGC 收集器
4大步骤
并发重映射: 修正整个堆中指向重分配集中旧对象的所有引用
如何选择合适的收集器
收集器的权衡
运行应用的基础设施佮? 例如: 硬件规格
使用JDK的版本发行商?
JVM收集器日志
查看GC基本信息
< JDK9 使用 -XX:+PrintGC
>= JDK9 使用 -Xlog:gc
查看GC详细信息
< JDK9 使用 -XX:+PrintGCDetails
>= JDK9 使用 -Xlog:gc*
< JDK9 使用 -XX:+PrintHeapAtGC
>= JDK9 使用 -Xlog:gc+heap=debuge
内存分配与回售策略
自动管理内存的目标
自动给对象分配内存
自动回收掉无用对象的内存
对象的生命旅程
对象优先分配在Eden区域
大对象直接进入老年代
长期存活的对象进入老年代
动态对象年龄判定
空间担保
四. JVM性能分析工具
jps: JVM进程状况工具
jstat: JVM统计信息监控工具
jinfo: Java配置信息工具
jmap: Java内存映像工具
jhat: 堆内存快照分析工具
jstack: Java堆栈跟踪工具
生成当前时刻JVM的线程快照
五. 调优案例分析域实战
Java虚拟机管理大内存
回收大块堆内存导致的长时间停顿
大内存必须要有64位Java虚拟机的支持
必须保证应用程序的足够稳定
相同的程序再64位虚拟机中消耗的内存比32位要大
若干虚拟机独立部署应用
节点竞争全局资源
很难高效率利用某些资源池
32位Java虚拟机收到系统的内存限制
六. 类文件结构
两种基础数据类型
无符号数
表
Class文件内容剖析
魔数与Class文件的版本
每个Class文件的前4个字节称为魔数
次版本号
主版本号
WinHex 可以打开16进制的Class文件
常量池
常量池容量计数值
把0项空出来的目的在于若果后面某些指向常量池的索引值的输在在特定的情况下需要达到\"不引用任何一个常量池项目\
可以比喻为Class文件的资源仓库
字面量
符号引用
被模块导出或者开放的包
类和接口的全限定名
字段的名称和描述符
方法的名称和描述符
方法句柄和方法类型
动态调用点和动态常量
项目类型
图示
访问标志
ACC_PUBLIC
0x0001 : 是否为public类型
ACC_FINAL
0x0010: 是否被声明final
ACC_SUPER
ACC_INTERFACE
0x0200: 标识是个接口
ACC_ABSTRACT
0x0400: 是否为abstract类型
ACC_SYNTHETIC
0x1000: 标识这个类并非用户代码生成
ACC_ACCOTATION
0x200: 标识这是一个注解
ACC_ENUM
0x4000: 标识是个枚举
ACC_MODULE
0x8000: 标识是个模块
类索引
用于确定这个类的全限定名
父类索引
接口索引集合
字段表
字段表结构
方发表集合
用于描述类中的方法的相关描述
属性表集合
Code属性
Exceptions属性
LineNumberTable属性
描述Java源代码行号与字节码行号(字节码偏移量)之间的关系
LocalVariableTable属性
描述栈帧中局部变量表的变量和Java源码中定义的变量之间的关系
SourceFile属性
用于记录生成这个Class文件的源码文件名称
SourceDebugExtension属性
用于存储额外的代码调试信息
ConstantValue属性
通知虚拟机自动给static的变量进行赋值
InnerClasses属性
记录内部类和宿主类之间的关联
Deprecated属性
Synthetic属性
StackMapTable属性
Signature属性
Bootstrapmethods属性
MethodParameters属性
字节码指令
字节码与数据类型
大多数的指令都包含其操作对应的数据类型信息
例如: iload指令用于从局部遍历表中加载int型的数据到操作数栈中
fload指令则是把float类型的数据加载到操作数栈
操作码助记符中都有特殊的字符
9大类
加载和存储指令
加载和存储指令用于把数据在栈帧中的局部遍历表和操作数栈之间传递
将一个局部变量加载到操作数栈
将一个数值从操作数栈存储到局部变量表
将一个常量加载到操作数栈
扩充局部变量表的访问索引指令
wide
说明
运算符指令
加减乘除指令
求余取反位移
按位或
按位与
按位异或
局部变量自增
iinc
比较指令
类型转换指令
可以把两个不同类型的数值类型进行互相转化
宽化类型转换
float -> double
窄化处理转换
必须显示地使用转化指令来完成
对象创建与访问指令
创建类实例指令
new
创建数组的指令
把一个数组元素加载到操作数栈的指令
把一个操作数栈的值存储到数组元素中的指令
取数组的长度的指令
arraylength
检查类实例类型的指令
操作数栈管理命令
栈顶一个或两个元素出栈
将栈顶的两个元素互换
swap
控制转移指令
有条件或无条件地从指定位置指令的已下条指令开始执行
条件分支
复合条件分支
无条件分支
方法调用和返回指令
invokevirtual
invokeinterface
invokespecial
invokestatic
调用类的静态方法
invokedynamic
用于在运行时动态解析出调用点限定符所引用的方法
异常处理指令
同步指令
0 条评论
回复 删除
下一页