导图社区 《深入理解Java虚拟机3.0》最全思维导图
这是一篇关于《深入理解Java虚拟机3.0》的思维导图,主要内容有走近Java、自动内存管理、虚拟机执行子系统、程序编译与代码优化等。
编辑于2022-11-19 00:57:10《深入理解Java虚拟机 -JVM高级特性与最佳实践》
走近Java
第1章 走进Java
这个章节放到最后 过一下,不是重点
1.1 概述
面向对象的编程语言
一次编译到处运行
1.2 Java技术体系
java Card
java ME
java SE
java EE
1.3 Java发展史
1991年Janes Gosling Oak(橡树)
1995年java1.0发布
......
1.4 Java 虚拟机发展史
1.4.1 Sun Classic/Exact Vm
Sun Classic VM 世界上第一款商用Java虚拟机
Exact VM 即使编译器、编译器与解释器缓和工作
1.4.2 Sun HotSpot VM
OpenJDK
Sun JDK
1.4.3 Sun Mobile-Embedded VM /Meta-Circular VM
KVM
手机平台
CDC/CLDC HotSpot Implementation
Java ME支柱
Squawk VM
java Card
JavaInJava
不能即时编译
用Java实现的
Maxine VM
有先进的JIT编译器
有垃圾回收器
效率高接近HotSpot Client VM水平
1.4.4 BEA JRockit/IBM J9 VM
BEA JRockit VM
即使编译器编译后执行
JRockit收集器和MissionControl服务套件处于领先水平
IBM J9 VM
更名为K8
IBM AIX和z/OS 部署java应用
1.4.5 Azul VM/BEA Liquid VM
Azul VM
HotSpot 基础上大量改进
BEA Liquid VM
不需要操作系统
自身实现操作系统的必要功能
最大限度地发挥硬件的能力
1.4.6 Apache Harmoney/Goole Andriod Dalvik VM
Apache Harmoney
Apcahe 基金会旗下的
兼容与JDK1.5、JDK1.6
Goole Andriod Dalvik VM
即时编译性能很高
1.4.7 Mircrosotf JVM 以及其他
微软有windows版本的JVM
其他JVM
Jam VM
cacaovm
SableVm
Kaffe
Jelatine JVM
NanoVm
MRP
Moxie JVM
Jikes RVM
1.5 展望Java技术的未来
1.5.1模块化
java模块化系统
java SE 动态组件支持
java模块化已经成为一项无法阻挡的变革潮流
1.5.2 混合语言
Clojure
JRuby
Groovy
等编程语言
多语言虚拟机的方向发展
JVM支持多语言
1.5.3 多核并行
JDK1.5加入java.util.concurrent包实现粗粒度并行框架
JDK1.7加入java.util.concurrent,forkjoin是对框架的一个扩充
1.5.4 进一步丰富语法
JDK5 加入自动装箱、泛型、动态注释、可变长参数、遍历循环等语法
JDK8 添加Lambda表达式
函数方式编程可能会成为主流
1.5.5 64位虚拟机
支持64位虚拟机
1.6 实战:自己编译JDK
此章节是实战章节,一定要手动去编译一把
自动内存管理
第2章 Java内存区域和内存溢出异常
2.1 概述
与C++的人工管理内存和垃圾不同,Java的内存分配和垃圾管理均由JVM自动完成
不容易出现内存泄漏和溢出,但存在内存问题排查不易的
2.2 运行时数据区域
总述:内存区域划分为1.线程共享的内存区域和2.线程隔离的内存区域

2.2.1 程序计数器(Program Counter Register)
较小内存空间
当前线程执行的字节码的行号指示器
字节码解释器根据行号选取下一条字节码指令,进行程序分支、循环、跳转、异常、线程恢复等控制
Java多线程通过线程轮流切换和分配处理器时间进行,每个线程都需PCS,因此线程私有
Java方法:虚拟机字节码指令的地址 本地方法:Undefined
唯一一个不会报OutOfMemoryError错误的区
2.2.2 虚拟机栈(Java Virtual Machine Stack)
线程私有、生命周期和线程相同、描述Java方法执行的线程内存模型
方法执行时JVM同步创建栈帧
记录:局部变量表、操作数栈、动态链接、方法出口等
局部变量表:存编译期可知的基本数据类型(boolean、byte、char、short、int、long、float、double)、对象引用(reference类型:引用指针or句柄or地址信息)、returnAddress类型(字节码指令的地址)
局部变量表的存储空间以局部变量槽来表示、long和double占用两个。虚拟机实现不同,槽的位数不同。
局部变量表所需空间在编译期间完成分配和确定
通常说的栈即指虚拟机栈中的局部变量表部分
操作数栈、动态链接、方法出口等是什么意思??
方法执行开始和结束对应栈帧的入栈和出栈
异常类型(待补充实际例子,以加深理解)
OOM
栈扩展无法申请到足够空间
SO
栈请求深度超过虚拟机运行最大深度
HotSpot虚拟机栈容量不可动态扩展,不存在因为虚拟机栈无法扩展导致的OOM
2.2.3 本地方法栈(Native Methods Stacks)
服务于本地(Native)方法栈,虚拟机栈服务Java方法(字节码)
会抛出OOM和Stack OverflowError
HotSpot虚拟机直接把本地方法栈和虚拟机栈合二为一
线程隔离的数据区
2.2.4 Java堆(Java Heap)
内存中最大的一块区域
唯一目的:存放对象实例
规范对堆的描述:所有的对象实例及数组都应在堆上分配
几乎所有对象都在这里分配(栈上分配、标量替换)
Hotspot G1之前的基于分代设计:新生代和老年代、永久代、Eden、From Survivor、To Survivor。非虚拟机规范。细分堆内存各个区域为了更好分配、回收内存
分配内存角度看,线程共享的堆可以划分线程私有的TLAB,提升对象分配策略
物理空间可不连续、逻辑连续,大对象一般连续
-Xmx -Xms来设置堆内存大小,内存不足报OOM
new 关键字创建的对象实例都是分配到堆内存中
会抛出Stack OverflowError???
2.2.5 方法区(Methods Area)
存储:被虚拟机加载的类信息、常量、静态常量、即使编译器编译后的代码等数据
方法区和永久代并不等价
不同虚拟机实现不同,JDK8之前Hotspot用永久代实现方法区
容易内存溢出
不同方法表现不同 String::intern()
JDK7字符串常量池、静态变量移出永久代
JDK8废弃永久代,使用本地内存元空间代替
垃圾回收很少,主要类型卸载和常量池的回收
会抛出OOM和Stack OverflowError
2.2.6 运行时常量池(Runtime Constant Pool)
类文件中的常量池表用于存放编译期生成的字面量和符号引用(以及翻译出的直接引用)、类加载后存于此
任何字符串的创建都会放在常量池中new String创建了几个对象? “hello”存放在哪里?字面量和符号引用是啥?
动态性:运行时将新常量放入常量池(String类的intern()方法)
同方法区:内存不足会抛出OOM
所有线程共享的数据区
2.2.7 直接内存(Direct Memory)
非虚拟机运行时数据区及规范定义的内存区域
不受堆内存限制,但是受机器内存、寻址空间等因素影响,会OOM
使用Native函数库直接分配堆外内存,然后堆内DirectByteBuffer引用,避免堆内和Native堆交互
2.3 HostSpot虚拟机对象探秘
2.3.1对象创建
new关键字执行过程(详细见第七章)
检查参数是否可以定位常量池中类的符号引用,并且该类已经加载、解析、初始化
没有则进行类加载
通过后创建对象
对象所需内存大小在类加载完成后即确定 2.3.2
给对象分配内存
指针碰撞
堆内存规整
Serial、ParNew
只移动指针
空闲列表
堆内存交错
CMS(基于清除)
需要维护内存情况列表
垃圾收集器是否具备空间压缩整理<--->堆内存是否规整<--->哪种分配方式
线程并发安全问题
线程同步处理(CAS+失败重试)
本地线程分配缓冲 TLAB,线程堆内预先分配缓冲,在对应缓存区分配(-XX:+/-UseTLAB)
内存分配后,内存空间进行零值初始化,(对基础类型字段进行初始化),保证实例字段具备初始值。
初始化对象头设置(类属性、哈希码、GC分代、偏向锁等)
执行构造方法
2.3.2 对象内存布局
对象头(Header)
运行时数据(Mark Word:包括哈希码(HashCode )、GC分代年龄、锁状态标志、线程持有锁、偏向线程ID、偏向时间戳等。)
类型指针(指向它的类元数据的指针,用于确定类型)
Java数组还需要记录数组长度的信息
实例数据(Instance Data)
对象真正存在的有效信息(各种类型的字段内容、包括父类继承)
虚拟机分配策略为longs/doubles、ints、shorts/chars、bytes/booleans、oops(Ordinary Object Points)【位数相同的分配到一起、父类在子类之前】
对齐填充Padding(不是必然存在的)
2.3.3 对象的访问定位
Java栈内的局部变量表存放reference类型,来指向对象的引用,虚拟机实现方法会不同会有句柄和直接指针两种
使用句柄
堆内划分句柄池,reference存放句柄地址,句柄存放对象实例数据和对象类型数据的指针
好处:存放稳定的句柄地址、对象移动时只需修改句柄中的实例地址,reference不修改
缺点:查找对象二次寻址
使用指针
堆对象内存布局需存储对象类型信息,reference直接存储对象地址
好处:访问对象速度快、节省二次寻址(Hotspot)
2.4 异常实战
堆异常
堆内存设置参数:-Xms -Xmx
堆转储快照参数 -XX:HeapDumpOutOfMemoryError
java.lang.OutOfMemoryError
Java heap space(无对象空间)
GC overhead limit exceeded(JVM花费了98%的时间进行垃圾回收,而只得到2%可用的内存)
内存泄漏、溢出定位、分析方法、工具见后面章节
栈异常
Hotspot不区分虚拟机栈和本地房发展-Xoss不起作用,只考虑 -Xss 栈大小参数
Hotspot不支持栈动态扩展
只有创建线程内存不足时才会OOM
老版本虚拟机支持动态扩展会输出OOM
多线程也可以OOM:线程内存越大,越容易OOM,超出OS最大内存。并不表示栈空间OOM,而是内存OOM
StackOverFlowError
请求栈深度大于允许最大深度
栈容量不够创建新的栈帧
减小栈空间 -Xss
增大栈帧大小
OutOfMemoryError
允许动态扩展且扩展栈无法申请足够内存
每执行一个方法入一条栈帧,所以可以嵌套函数,重复插栈帧
运行时常量池
永久代参数-XX:PermSize -XX:MaxPermSize
String::intern()
查找字符串常量池是否有相同对象,如果有返回引用引用、没有加入常量池并返回引用
JDK6 运行时常量池位于方法区,使用永久代实现
JDK7将常量池、静态变量移动到java堆中
永久代设置参数不生效
JDK8之后方法区使用直接内存实现-元空间,JDK7开始去永久代
方法区
存放类信息:类描述、访问修饰符、类名、方法描述、字段描述等
OOM
加载大量类信息时会OOM主题
动态生成大量类
JDK8之后使用元空间存储类信息,上述方法比较难OOM
-XX:MaxMetaSpaceSize
-XX:MetaSpaceSize
触发类型卸载
-XX:MinMetaSpaceFreeRatio
用于控制GC后最小元空间剩余容量百分比
直接内存
-XX:MaxDirectMemorySize
默认和heap大小一致
第3章 垃圾收集器与内存分配策略
3.1 概述
Lisp第一门使用内存动态分配和垃圾手机技术的语言
关注
哪些内存需要回收
什么时候回收
怎么回收
线程私有的栈、程序计数器随线程、方法的开始结束进行回收,不需要考虑GC
栈帧大小随类结构确定
GC重点关注堆和方法区,动态内存
3.2 对象存活判断
3.2.1 引用计数算法
原理:给对象添加一个引用计数器、有引用则+1,根据引用值判断是否存活
Python
FlashPlayer
特点:额外空间、实现简单、判定效率高
问题:需要额外处理(对象循环引用问题)
3.2.2 可达性分析算法
原理
GC Roots的跟对象作为起始节点集
到对象间有无引用链
可做GC Roots的类型
两栈
虚拟机栈(栈帧中的本地变量表)中引用的对象(参数、局部变量、临时变量等)
本地方法栈中JNI(即一般说的Native方法)引用对象
方法区
方法区中静态属性引用的对象(Java类的引用类型静态变量)
方法区中常量引用 的对象
虚拟机内部
虚拟机内部引用(基本数据类型对应的Class对象、常驻异常对象、系统类加载器(Integer、OOM、ClassLoader))
被同步锁持有的对象
JMVBean、JVMTI、本地代码缓冲等
3.2.3 再谈引用
传统定义:reference类型存储的是另外内存的地址,则为引用类型
四种引用类型
强引用
Object obj = new Object();
只要引用存在,不会GC
软引用
非必须对象
OOM之前会二次回收软引用的对象
弱引用
只会生存到下次GC
GC时不论内存是否足够,均会回收
虚引用
唯一作用:回收时,系统通知
四种引用类型
3.2.4 生存还是死亡
两次标记
不可达(没有GC Roots引用链)(第一次标记)
是否有必要执行finalize方法
不需要
未覆盖finalize方法
finalize已执行
需要执行
入F-queue队列
自动建立、低优先级Finalizer线程执行队列内对象的finalize方法
finalizer线程只发起执行,不保证完成
其他永久等待
GC崩溃
第二次标记(未与GC Roots引用链建立联系),随后被回收
3.2.5 回收方法区
垃圾回收效率很低
JDK 11 ZGC收集器不支持类卸载
废弃的常量、不再使用的类型
类不再使用判断
类的所有实例均已回收
类加载器已回收
类对应的Class对象无引用,不可反射访问该类
3.3 垃圾收集算法
垃圾收集算法分类(按判定对象消亡的方式)
引用计数式垃圾收集
追踪式垃圾收集
3.3.1 分代收集理论
基于的假说
弱分代假说
绝大多数对象朝生熄灭
强分代假说
熬过越多次GC约难消亡
跨代引用假说
新生代和老年代互相引用
同时生存and同时死亡
记忆集:标识哪一块老年代和新生代跨代引用、避免扫描全部老年代
设计理论
java堆划分不同区域
将对象按年龄分配不同区域
重点关注少量存活对象、较低频率处理老对象
堆区域划分
Minor GC、Major GC、Full GC等回收类型划分
标记-复制、标记-清除、标记-整理等针对区域对象特征的垃圾收集算法
新生代(新对象、朝生夕死) 、老年代(经历多次GC的对象)
3.3.1 标记-清除算法
标记所有需要/不需要回收的对象
统一回收
不足
执行效率不稳定(待回收对象数量较多时,效率较低)
内存空间碎片化(后续需要大空间可能又需要GC)
3.3.2 复制算法
基础原理
内存划分大小相等两块,每次使用一块
回收时清理将存活的复制,清理原来块
特点
内存可用空间只有1/2
不需要考虑碎片问题
对象大量存活时,复制开销大
商用虚拟机多数使用来回收新生代
优化:Appel式回收算法
HotSpot的Serial、ParNew新生代收集器使用
原理
新生代对象有98%会第一次被回收
新生代划分成1:8:1的survivor eden区域
使用eden和单个survivor区域分配内存,损失10%空间
每次回收后将存活对象复制到survivor中,清理90%空间
特点
加大利用率
不用考虑碎片问题
内存的分配担保
单个survivor并不一定能容纳GC后的存活对象
通过内存分配担保机制移动到老年代
3.3.3 标记整理算法
标记-复制不适合老年代(存活较多对象,复制开销大)
基础原理
标记阶段和标记清除算法一样
将存活对象放在一端,然后清除边界外的内存
特点
适用于老年代回收
移动式的回收算法
老年代存活对象较多,移动开销大,且需要stop the world
不移动则大量碎片空间,影响内存分配
吞吐量
赋值器(垃圾收集的应用程序)和收集器效率总和
内存分配和访问比gc频率高
整理:停顿时间长、内存分配快、无碎片
不整理:停顿时间短、gc快、碎片多
Parallel Scavenge关注吞吐量:标记整理
CMS 关注延迟:标记-清除
折中算法
平时使用标记-清除、不关注碎片
碎片化比例达到阈值使用标记-整理
CMS
3.4 HotSpot的算法实现
3.4.1 枚举根节点
3.4.2 安全点
3.4.3 安全区域
3.4.4 记忆集和卡表
3.4.5 写屏障
3.4.6 并发的可达性分析
太抽象 暂不看
3.5 经典垃圾收集器
3.5.1 Serial 收集器
单线程垃圾收集器
新生代:标记-复制;老年代:标记-整理
进行GC时需要Stop The world
适用于单核处理器或核心较少的情况:没有线程交互开销
内存消耗最低
3.5.2 ParNew 收集器
多线程的Serial收集器,并行收集
参数、回收策略等全部和Serial一致
新生代:标记-复制;老年代:标记-整理
参数
CMS
-XX:+UseConcMarkSweepGC
ParNew
-XX:+UseParNewGc
JDK9 之后上述参数删除,只能默认搭配CMS,退出历史舞台
特点
单核心处理器中,因为线程交互开销,不比Serial
默认GC线程和处理器核心相同
-XX:ParallelNewThreads
3.5.3 Parallel Scavenge 收集器
标记-复制算法
多线程、新生代收集器
参数
可控最大停顿时间
-XX:MaxGCPauseMillis停顿时间
吞吐量大小
-XX:GCTimeRatio (用户程序/GC)
定义:程序时间/(GC时间+程序时间)
自适应调节策略
-XX:+UseAdaptiveSizePolicy
jvm收集系统运行情况和性能监控,自使用调节,以满足上面两个参数
特点
追求吞吐量的收集器
停顿时间短:适用于与用户交互、保证服务质量
吞吐量大:适用于最高效利用机器资源,后台运算,不需要大量交互
自适应调节策略
新生代收集器
3.5.4 Serial Old 收集器
Serial的老年代版本
标记-整理算法
单线程
用途
客户端模式下的HotSpot
服务端
JDK5之前搭配Parallel Scavenge
CMS收集器失败后的backup(Concurrent Mode Failure情况)
3.5.5 Parallel Old 收集器
Parallel Scavenge的老年代版本
注重吞吐量或者机器资源稀缺情况下,推荐Parallel Old+ Parallel Scavenge
标记-整理算法
多线程
3.5.6 CMS收集器
CMS(Concurrent Mark Sweep) 并发、标记、清理
原理(三标一清)
初始标记
标记GC Roots直接关联的对象
Stop The World
并发标记
从直接关联对象遍历对象图,并发
重新标记
Stop The World
并发标记阶段,应用运行导致引用变化
修整变动的对象
并发清除
清除标记死亡的对象
优缺点
优点
低停顿时间
并发收集
缺点
处理器资源敏感
并发程序对处理器资源敏感
占用线程(处理器能力)
应用程序变慢、吞吐量降低
默认线程(Core+3)/4
core<4 影响较大
增量并发式收集器
GC、用户线程交替运行,减少GC独占时间
速度变慢时间多、变慢程度降低
JDK9 已废弃
无法处理浮动垃圾 可能出现Concurrent Mode Failure、Full GC
因为并发标记和清除,所以老年代需要预留空间
并发标记和并发清除阶段,应用程序产生新垃圾
预留空间不够,产生CMF
冻结用户线程、Serial Old后备GC
碎片化资源
标记整理会产生碎片空间
碎片空间不够大对象时,触发Full GC
两个调整参数
是否在FullGC之前开碎片合并整理 -XX:+UseCMSCompactAtFullCollection
执行多少次不整理的FullGC后开始整理 -XX:CMSFullGCssBreforeCompation
老年代收集器
3.5.7 G1收集器
G1(Garbage-First)
卡表、任务集、写屏障、读屏障、STAB
简介
面向服务端的收集器
基于Region的内存布局形式
Region根据需要扮演新生代、老年待等
堆划分为多个大小相等的Region(1MB~32MB 2的N次幂)
老年代、新生代是不连续一系列Region的集合
最小回收单元、回收Region集合
Humongous 区域
面向大对象的存储
超过Region一般视为大对象
视为老年代
Mixed GC
面向堆内存回收:垃圾多、回收收益大
目标:回收集概念,而不是新生代、老年代
跟踪Region内垃圾收集的价值(回收空间、回收时间)
维护列表,根据用户设定的停顿时间,优先回收收益大的Region
原理、步骤
初始标记
标记GCRoots直接关联对象
修改TAMS指针
并发标记时正确分配对象
Minor GC时同步完成
短暂停顿
并发标记
堆对象可达性分、遍历扫描
维护STAB记录下的并发时引用变动
并发、耗时较长
最终标记
短暂停顿
处理并发结束后少量STAB记录
筛选回收
统计Region数据、对回收价值和成本排序
根据期望停顿时间,指定回收计划
复制Region存活对象到空Region、清理旧Region
需暂停用户线程
并行但不并发
难点
跨region引用
每个Region维护双向卡表
内存占用较多 >20%
并发标记线程互相干扰
用户线程改变引用
G1 STAB
CMS 增量更新
回收过程新对象分配
两个TAMS指针空间来分配新对象
可能导致STW来Full GC
可靠的停顿预测模型
衰减均值
其他
指定期望停顿时间
不可太低
只回收一小部分导致垃圾堆积
默认200ms
CMS对比
可指定最大停顿时间
CMS 标记-清除
G1整体 标记-清理、Region局部 标记-复制
G1 无碎片、内存规整、大对象分配问题少、利于长时间运行
内存占用、程序负载高
卡表复杂
写屏障维护卡表、STAB跟踪引用变化 负载较大
小内存应用CMS占优,大内存G1占优(6G-8G)
3.6 低延迟垃圾收集器
3.6.1 Shenandoah收集器
目标
任意可管理堆大小情况下,实现GC停顿10ms之内
原理&步骤
初始标记
同G1标记GC Roots
STW
停顿时间跟GC Roots数量有关
并发标记
同G1,标记全部可达对象
并发
时间取决于存活对象数量和对象图复杂程度
最终标记
同G1处理剩余的STAB
统计出回收价值高的Region,组成回收集
一小段停顿
并发清理
清理完全没有对象存活的Region
并发回收
将回收集Regions中存活对象拷贝到未使用Regions中
使用读屏障和Brooks Pointers实现并发拷贝
时间取决于回收集大小
初始引用更新
设立线程集合点,确保线程完成复制任务
短暂停顿
并发引用更新
按内存物理地址,线性搜索出引用类型,修改为拷贝对象的新址
时间取决于内存中涉及的引用数量
最终引用更新
停顿时间与GC Roots数量有关
修整存在于GC Roots中的引用
并发清理
回收集中Regions无存活对象,并发清理,回收内存
图示
特点
只有OpenJDK具备
G1对比
相同
具备相同堆布局,初始标记并发标记实现思路一致,共享代码
同样Region、Humongous region
同样默认回收策略:回收价值最大Region
不同
G1筛选回收可并行不可并发;S可并发
S无分代收集概念
S使用连接矩阵维护跨Region引用;G1用双向记忆集
各款收集器并发情况
Brooks Pointers 原理
转发指针
被移动对象内存设置保护陷阱
用户程序访问时触发异常保护
访问转发到新对象
缺点
用户态和核心态频繁切换
原理
在对象最前面增加引用字段
未并发移动时指向自己
并发移动时指向新地址
特点
简介对象访问,需要额外寻址开销
有副本后只需要修改指针地址,便可完成转发
CAS操作保证GC和app并发写对象的并发问题
执行频率问题:访问对象地方均需要覆盖访问操作
基于引用访问屏障
只处理引用类型的读写操作
3.6.2 ZGC收集器
简介
原理
特点
Azul C4、PGC
低延迟收集器
3.7 选择合适的垃圾收集器
3.7.1 Epsilon收集器
没有GC的垃圾收集器(自动内存管理系统)
适用于短时间、规模小的系统
堆内存耗尽之前,已退出
十款GC
年轻代
Serial
ParNew
Parallel Scavenge
老年代
Serial Old
Parallel Old
CMS
不分代
G1
Shenandoah
ZGC
其他
Azul PGC、C4
Epsilion
3.7.2 收集器的权衡
应用程序关注点
吞吐量
延迟(停顿时间)
内存占用
基础设施情况
CPU
内存
OS
JDK发行商
3.7.3 虚拟机及垃圾处理器日志
JDK9前后参数变化较大
-XX:+PrintGC -Xlog:gc
-XX:+PrintGCDetails -X-log:gc*
-XX:+PrintHeapAtGc -Xlog:gc+heap=debug
...
3.7.4 垃圾收集器参数总结
https://blog.csdn.net/en_joker/article/details/79786096
3.8 内存分配与回收策略
3.8.1 对象优先在Eden分配
对象优先分配到Eden区域
空间不足时Minor GC
Eden 和Survior区域的比例是8:1 -XX:SurvivorRatio=8
Minor GC
新生代GC
非常频繁
速度快
Major GC
老年代GC
一版在执行Major GC的时候会执行一次Minor GC
Major GC的速度一般会比Minor GC慢10倍以上
Full GC
整个堆和方法区的GC
-Xms -Xmx -Xmn 堆最小 堆最大 新生代大小
3.8.2 大对象直接进入老年代
长字符串、数量大的数组
提供-XX:PretenureSizeThreshold=3M,大于3M的时候直接进入老年代
只对Serial和ParaNew有效
避免Eden和Survivor区之间发生大量的内存复制
复制大对象需要比较大开销
3.8.3 长期存活的对象将进入老年代
定义对象年龄计数器,Minor GC一次年龄增加1岁
默认是15次MinorGC后进入老年代
配置参数-XX:MaxTenuringThreshold=1
3.8.4 动态对象年龄判定
survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象可以直接进入老年代
不是所有的进入老年代都要等到MaxTenUringTheshold中要求的值
3.8.5 内存分配担保
老年代空间大于新生代所有对象空间
设置参数:-XX:HandlePromotionFailure设置是否允许担保
MinorGC之前检查老年代最大连续空间 是否大于所有新生代对象
大于,则此次MinorGC是安全的
小于,是否开启内存分配担保
开启
老年代最大连续空间是否大于历代进入老年代的平均大小
是,进行有风险的MinorGC
否,Full GC
未开启
Full GC
为什么有风险
新生代是复制算法
拿的历代平均大小对比,如果全部存活,大于连续空间,则必须STW进行Full GC
JDK6 Update24之后直接第一步判断,大于进行Minor GC,小于 Full GC
概述
分配内存 and 自动回收内存
一般都是在堆上分配内存,还有栈上分配优化
分代设计下:新生代分配内存、大对象老年代分配
第4章 虚拟机性能监控、故障处理工具
4.1 概述
知识经验是基础
数据是依据
运行日志
异常堆栈
GC日志
线程快照
堆转储快照
4.2 JDK命令行工具
4.2.1 jps 虚拟机进程状况工具
JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程
得到的id是本地虚拟机唯一id
-q 只输出LVMID,省略主类名称
-m 输出虚拟机进程启动时传递给main()函数的参数
-l 输出主类的全名,如果进程执行的是jar包,输出jar路径
-v 输出虚拟机进程启动时JVM参数
4.2.2 jstat
JVM Statictics Monitoring Tool 用于收集HotSpot 虚拟机各方面运行的数据
显示本地或者远程虚拟机进程中的类装载、内存、垃圾收集、JIT编译等运行数据
jstat -gc pid
4.2.3 jinfo:java配置信息工具
Configuration Info for Java 显示虚拟机配置信息
查看参数
# jinfo -flag GCLogFileSize 8807 -XX:GCLogFileSize=10485760
打开gc
jinfo -flag+PrintGCDetails 12278
关闭gc
jinfo -flag -PrintGCDetails 12278
jinfo -flag -PrintGC 12278
4.2.4 jmap
Memory Map for Java 生成虚拟机的内存转储快照(heapdump 文件)
使用方法:jmap -dump:format=b,file=test.bin 16184 这种方式可以用 jvisualvm.exe 进行内存分析, 或者采用 Eclipse Memory Analysis Tools (MAT)这个工具
jmap -histo:live pid这种方式会先出发fullgc,所有如果 不希望触发fullgc 可以使用jmap -histo pid
jdk启动加参数: -XX:+HeapDumpBeforeFullGC -XX:HeapDumpPath=/httx/logs/dum 这种方式会产生dump日志,再通过jvisualvm.exe 或者Eclipse Memory Analysis Tools 工具进行分析
获取dump的三种方法
4.2.5 jhat
JVM Heap Dump Browser 用于分析heapdump文件, 它会建立一个HTTP/HTML 服务器,让用户可以在浏览器查上查看分析结果
使用方法:jhat test.bin
自启动一个内置的tomcat在网页分析
支持OQL语句
4.2.6 jstack
Stack Trace for java 显示虚拟机的线程快照
线程出现停顿、死锁、循环、请求外部资源导致过长等原因
4.2.7 HSDIS:JIT生成源代码反汇编
4.3 JDK的可视化工具
4.3.1 Jconsole:Java监视与管理控制台
4.3.2 VisualVM :多合一处理工具
第5章 调优案例分析与实践
虚拟机执行子系统
第6章 类文件结构
6.1 概述
计算机只认识0和1
操作系统指令无关、平台中立作为编译后的格式,如:字节码
6.2 无关性的基石
一次编译到处运行
实现在OS的应用层
JVM语言无关性
Groovy JRuny Jython Scala 都可以JVM中运行
JVM只与字节码(class)绑定,不绑定java语言
虚拟机执行变异后的class文件,不关心语言来源
不同语言编译成相同规范的class文件-->不同OS的虚拟机来屏蔽系统差异,解释执行,保证最终一致性
6.3 CLass 类文件的结构
概述
class文件对应类和接口的定义信息,但类定义不依赖class文件
class文件简介
以字节为单位的二进制文件
严格按照顺序排列
无分隔符和占位符
8个字节以上:高位在前
无符号数
u1 u2 u4 u8 分别代表几个字节数据
描述数字、索引、数值量、字符串
表
多个无符号数、表组成的复合结构
以_info结尾
class 格式
6.3.1 魔数与Class文件的版本
class头4个字节成为魔数 0xCAFEBABE
5、6字节次版本号;7、8字节版本号
cafe babe 0000 0034
JDK1 45
JDK8 45+7=52
6.3.2 常量池
常量池容量在版本号后面,占有两个字节
使用javap -verbose 类名 输出常量池定义信息,方便查询
字面量
文本字符串
final常量值等
符号引用
类和接口的全限定名
字段名称和描述符
方法名称和描述符
方法句柄、方法类型
...
class文件不保存方法、字段的内存布局信息,在类加载时获取符号引用,类创建/运行时解析翻译到内存地址
常量池每一项都是表、且第一个字节标识类型
6.3.3 访问标志
是类还是接口
定义是不是public
是否定义abstract
是否是final
access_flags有16个标志位能用,当前使用8个,没有用到的的标志位一律为0
6.3.4 类索引、父类索引与接口索引集合
类、父类索引u2类型
接口 u2集合
可实现多个接口
指向class_info,根据name_index 查utf8_info
6.3.5 字段表集合
描述变量
类级字段和实例级字段
字段修饰符(访问控制、静态、序列化、并发可见性、可变性)用标志位标识
字段名称和数据类型引用常量池
全限定名类路径.类名
简单名称:没有类型和参数修饰的方法或者字段名
描述符:描述字段数据类型、方法的参数列表和返回值等
基本类型大写字母 B、C、I等
对象类型L加对象全限定名
无父类继承的子字段
6.3.6 方法表集合
基本同字段表集合
添加了属性表集合【CODE、Exception等】
6.3.7 属性表集合
1 code属性
字节码长度
2 Exceptions 属性
3 lineNumberTable属性
4 LocalVariableTable属性
5 SourceFile 属性
6 Con斯坦Value属性
7 InnerClasses属性
8 Deprecated及Synthetic属性
9 StackMapTable属性
10 Singnature属性
11 BootstrapMethods属性
6.4 字节码指令简介
概述
操作码
单个字节长度、代表特定含义的数字
一个字节最多256条指令
很多操作码没有操作数
操作数
JVM面向操作数栈而非寄存器
操作数放在操作数栈中
可能需要重建数据 ---> 损失性能
6.4.1 字节码与数据类型
指令包含操作对应的数据类型信息,如iload(int)lload(long)
i int
l long
d double
a reference
c char
b byte
特定操作支持持有限的类型指令
某些数据类型没有对应的操作指令 如 boolean没有特有指令,使用int的
boolean byte char short 扩展成int 使用int指令
fiald 代指 float int reference long double
6.4.2 加载和存储指令
加载:局部变量表到操作数栈
iload lload aload fload dload
存储:反之
istore lstore astore...
6.4.3 运算指令
就是将基础运算符进行转化为指令,方便计算机计算
fild 类型的add sub mul div neg等计算指令
6.4.4 类型转换指令
低到高无需显式转换
高到低 i2c i2b i2s l2c l2b等
精度丢失可能
6.4.5 对象创建与访问指令
new newarray anewarray
访问字段 getfield getstatic
arraylength
fiald的 astore和aload代表操作数栈和局部变量表的加载和存储
6.4.6 操作数栈管理指令
pop2 pop swap dup dup2
6.4.7 控制转移指令
ifeq goto ifge
6.4.8 方法的调用和返回指令
invokevirtual 调用对象实例方法
invokeinterface 调用接口方法
invokespecial 实例初始化、私有、父类方法
invokestatis 调用类静态方法
fiald搭配的 return指令
void的return
6.4.9 异常处理指令
athrow
所有异常
6.4.10 同步指令
不需要字节码指令标识是否同步,使用标志位标识
monitorenter
monitorexit
第7章 虚拟机类加载机制
7.1概述
运行时动态加载和动态链接
7.2类加载时机
阶段 加载,连接(验证,准备,解析)、初始化、使用、卸载
加载、准备、验证、初始化、卸载顺序固定
有且仅有6中情况对必须类进行初始化
遇到new、getstatic、putstatic或者invokestatic这四个指令时
new关键字实例化对象
读取或设置一个类的静态字段
调用类的静态方法
使用指令的场景
使用java.lang.reflect包的方法对类进行反射调用的时候,如果类 没有进行过初始化,则需要先触发其初始化
当初始化一个类的时候,如果发现其父类还没有初始化,则需要先 触发其父类的初始化
当虚拟机启动时,用户需要制定一个执行的主类(包括main()方 法的那个类)虚拟机会优先初始化这个类
在初次调用java.lang.invoke.MethodHandle实例时,通过java虚 拟机解析出类型是REF_getStatic,REF_puStatic,REF_invokeStatic 的方法句柄时
JDK8中接口定义了默认方法default,接口实现类初始化前该接口先初始化
快速记忆 new、子父类、main、反射、default、methodHandle
7.3类加载过程
7.3.1加载
通过一个类的全限定名来获取定义此类 的二进制流
从ZIP中获取
从网络中获取
运行时计算生成
动态代理
其他文件生成
数据库读取
将这个字节流所代表的静态存储结构转 化为方法区的运行时数据结构
在内存中生成一个代表这个类的java.lang .Class对象,作为方法区这个类的各种 数据访问接口
类型数据放在方法区后,在堆内存实例化Class对象
其他
用户可控最强
引导类加载器、自定义类加载器
数组类型
数组类不通过类加载器创建
直接在内存动态构造
数组的元素类型需要类加载器加载
引用类型
正常加载
数组标识在加载该类型的类加载器的类名称空间上
数据类的访问性和组件类型一致
非引用类型,如int
数组标识为与引导类加载器关联
数据类的访问性默认public
7.3.2验证
文件格式验证
是否符合class文件规范,是否可被当前版本jvm处理
保证输入字节流可正确解析并进入方法区,格式上符合Java类型要求
后续验证基于方法区存储进行,不再读入字节流
魔数是否是以0xCAFEBABE开头
主次版本号是否在虚拟机处理范围
常量池是否有不支持的常量类型 tags
常量池索引值是否指向不存在常量或不符合类型常量
utf_info型常量是否UTF8编码
Class文件中信息是否完备或多余
主要验证项
元数据验证
语义分析:符合Java语言规范要求
对数据类型进行校验
这个类是否有父类
父类是否继承了不允许的继承的类
如非抽象类,是否实现了父类 、接口中要求实现的所有方法
类中字段、方法是否与父类产生矛盾
主要验证项
字节码验证
最复杂的校验
目的:数据流、控制流分析语义合法性
对类的方法体(Class文件中的Code)进行校验分析
通过检查不一定安全:停机问题
Code增加StackMapTable
记录基本快开始时本地变量表、操作栈的状态
验证时检查记录是否合法,节省时间
操作数栈的数据类型和指令代码序列配合,如 int类型不用lload
跳转指令不会跳转方法体之外
方法体中类型转换有效性
主要验证项
符号引用验证
基本概念
直接引用
可以直接指向目标的指针、相对偏移量、句柄
符号引用
以一组符号描述所引用的目标,如常量池中的类全限定名com.iceman.Test,指代类Test
任意形式的字面量
引用的目标不一定是已经加载到内存
对类自身以外的各类信息进行匹配性校验
该类是否缺少或被禁止访问它以来的外部类、方法、字段等
理解:引用是否合法
字符串描述的全限定名是否有对应的类
指定类中是否有字段描述符、简单名称描述的字段和方法
类、字段、方法的可访问性是否满足
主要验证项
可通过 -Xverify:none关闭
7.3.3准备
为类中定义的变量分配内存并设置初始值
设置类型零值,而不是初始值
final修饰的字段根据程序赋初值
7.3.4解析
概述
将常量池中的符号引用转换为直接引用
对方法、字段的可访问性检查
对第一次解析结果缓存
类或接口、字段、类方法、接口方法、方法类型、方法句柄、调用点限定符7类,对应常量池中常量类型
类、接口的解析:当前类C 符号引用N 引用类或接口C
非数组类型:将代表N的全限定名用D类加载器加载
数组类型:1.按对象加载方式加载数据元素类型2.虚拟机生成代表数组维度和元素的数组对象
符号引用验证:对引用类的访问验证
java.lang.IllegalAccessError
字段解析
对字段表内class_index项中索引的符号引用(字段所属类、接口的符号引用)进行解析
异常,结束
正常,C代表类或接口
C包含简单名称和字段描述符匹配的字段,返回结束
C实现了接口,按继承关系、从下往上递归搜索接口和父接口,按第2条查找
按类继承关系从下往上搜索父类,按第2条查找
失败,java.lang.NoSuchFieldError
方法解析
对方法表内class_index项中索引所属方法、接口的符号引用进行解析
异常,结束
正常,C代表类
类的方法和接口方法符号引用的常量类型定义是分开的,如线索引C是接口,抛出java.lang.IncompatibleClassChangeError异常
在类C查找简单名称和字段描述符匹配的方法,返回结束
在类C的父类中查找
在C实现的接口、父接口查找
失败,java.lang.NoSuchMethodError
成功,访问权限验证
java.lang.IllegalAccessError
接口方法解析
对接口方法表内class_index项中索引所属方法、接口的符号引用进行解析
异常,结束
正常,C代表接口
同上,如索引C是方法,抛出java.lang.IncompatibleClassChangeError异常
在接口C查找简单名称和字段描述符匹配的方法,返回结束
在接口C的父接口递归查找
接口多继承:多个父类相同的匹配方法,则返回其中一个
失败,java.lang.NoSuchMethodError
成功,访问权限验证
JDK9之前接口默认public
JDK9增加接口静态私有方法
java.lang.IllegalAccessError
7.3.5初始化
类加载的最后一步
执行用户程序
初始化类变量和其他资源
执行类构造器<clinit>()方法的过程
<clinit>()方法
虚拟机自动生成
自动收集所有类变量的复制动作和平台语句块,合并生成、类文件自然顺序执行
不需要显式调用(实例构造方法需显式调用)
父类执行在子类之前
父类静态语句块优先于子类静态语句块
接口没有静态语句块,但可以初始化赋值
不需要先执行父接口
接口实现类初始化不执行接口clinit
多线程会加锁
7.4类加载器
7.4.1 类与类加载器
类的唯一性
由类加载器和类一起确定
equals isInstance
自定义类加载器
高度灵活性
代码加密
类加载器可以实现热部署
7.4.2 双亲委派模型
虚拟机角度
启动类加载器
BootStrap ClassLoader
C/C++实现
不能被调用
其他类加载器
继承java.lang.ClassLoader
Java实现
独立于虚拟机外部
开发人员角度
三层类加载器
启动类加载器
BootStrap ClassLoader
路径/参数:<JAVA_HOME>\lib 或者 -Xbootclasspath
不能被Java程序直接引用
自定义类加载器,委派启动类加载器时,用null代替
扩展类加载器
Extension Class Loader
sun.misc.Launcher$ExClassLoader用Java代码实现
路径/参数:<JAVA_HOME>\lib\ext 或者 java.ext.dirs
应用程序类加载器
Application Class Loader
sun.misc.Launcher$AppClassLoader用Java代码实现
系统类加载器:getSystemClassLoader返回值
classpath
双亲委派机制
除顶层启动类加载器之外,企图类加载器都要有自己的父类加载器
工作原理过程
类加载器收到类加载请求,将请求委派给父类加载器
所有类加载请求最终都委托给启动类加载器
父类反馈无法完成加载请求时,子类进行加载
特点
优先级的层次关系
代码执行过程
首先会检查请求加载的类是否已经被加载过
若没有被加载过
递归调用父类加载器的loadClass()
父类加载器为空后就使用启动类加载器加载
如果父类加载器和启动类加载器均无法加载请求,则调用自身的加载功能
7.5 Java模块化系统
JDK9引用Java模块化系统
第8章 虚拟机字节码执行引擎
8.1 概述
物理机执行引擎
处理器、缓存、指令集、OS
虚拟机执行引擎
软件自行实现
解释执行
解释器解释字节码
编译执行
及时编译期编译成本地机器码执行
8.2 运行时栈帧结构
概述
栈帧是用于支持虚拟机进行方法调用和执行的数据结构
栈帧包含局部变量表、操作数栈、动态链接、方法出口等
栈帧需要的局部变量表和操作数栈空间在编译期间已确定
存储在方法表的Code属性中
运行时不影响
栈顶:当前栈帧 方法:当前方法;字节码指令只针对当前栈帧操作
8.2.1 局部变量表
最小值是变量槽slot
存储方法参数和局部变量
Code属性的max_locals确定所需最大容量
引用类型
直接或间接查到对象在java堆中的数据起始地址或索引
直接或间接查到对象所属类型在方法区的类型信息
建立在栈帧中,线程私有,无并发问题
栈帧索引从1开始,0为this
局部变量表可重用:PC计数器的值超过变量作用域,后重用空间
类变量不赋初值,则使用准备阶段的零值,局部变量必须赋初值
8.2.2 操作数栈
后入先出(LIFO)
Code属性的max_stacks确定所需最大容量
栈中元素必须和字节码指令匹配
8.2.3 动态链接
栈帧包含执行运行时常量池中该栈帧所述方法的引用
用于支持动态链接
字节码的方法调用 以常量池内指向方法的符号引用为参数
静态解析
类加载或第一次使用时转化为直接引用
动态链接
每一次运行时转换为直接引用
8.2.4 方法返回地址
方法正常退出
主调方法的PC计数器的值作为返回地址
异常退出
通过异常处理器表确定
退出操作
恢复上层方法局部变量表和操作数栈
返回值压入调用者的操作数栈
调整PC计数器指向方法调用指令后面的指令
8.2.5 附件信息
8.3 方法调用
概述
一切方法调用在class文件都是常量池的符号引用
非方法入口直接引用
动态扩展能力
调用在类加载期间、运行期间才能确定目标的直接引用
8.3.1 解析
invokestatic:调用静态方法
invokespecial:调用实例构造器<init>方法、私有方法和父方法
解析阶段确定唯一调用版本 (类加载阶段,能够转化为直接引用的前提)
可确定的调用版本
运行期间不变
静态方法、私有方法、实力构造器、父类方法
final 修饰的方法(invokeVirtual)
invevirtual:调用所有虚拟方法(java的默认方法调用)
invokeinterface:调用接口方法,会在运行时再确定一个实现此接口的对象
invokedynamic:
8.3.2 分派
静态分派(依赖静态类型确定调用版本)
Human man = new Man();
Human 静态类型、外观类型
静态类型变化仅在使用时发生
变量本身的静态类型不会变化
最终静态类型编译期可知
(Man)man
Man 实际类型、运行时类型
结果只在运行期可知
编译期不知道实际类型
使用哪个重载
取决于传入参数的数量和数据类型
通过参数的静态类型判定
编译期可知静态类型,符号引用写入到字节码调用中
发生编译期,二分虚拟机执行
动态分派(依赖实际类型、方法接受者)
重写
运行期间确定接受者的实际类型
通过接受者的实际类型判定
invokeVirtual
单分派、多分派
静态分派:多分派(依赖静态类型和参数类型)
动态分派:单分派(依赖实际类型)
8.4 动态类型语言支持
8.5 基于栈的字节码解释执行引擎
8.5.1 解释执行
编译期
javac编译期实现代码经词法分析、语法分析到抽象语法树,遍历语法树生成线性字节码指令流的过程
虚拟机外部实现
对指令流通过解释器解释执行
编译执行:编译成目标代码,编译执行
8.5.2 基于栈的指令集与基于寄存器的指令集
栈
简介
零地址指令
依赖操作数栈工作
指令不需要带参数
使用操作数栈中的参数做指令输入
计算结果也放在操作数栈中
优缺点
不受硬件约束,可移植
不直接使用寄存器,程序自定义寄存器用途
代码紧凑
编译期实现简单
缺点:理论上执行速度慢
入栈、出栈消耗
频繁访问内存
过程
取栈顶元素
结果存入栈顶
根据字节码指令特点决定栈顶数据怎么取
寄存器
特点
二地址指令
包含两个单独的输入参数
依赖寄存器访问和存数据
第9章 类加载及执行子系统的案例与实践
程序编译与代码优化
第10章 前端编译与优化
概述
前端编译器
javac
即时编译器
Hotspot C1 C2
提前编译期
Jaotc
10.2 Javac编译器
10.2.1 Javac的源码和调试
编译过程
准备过程:初始化插入式注解处理器
解析与填充符号表(10.2.2)
词法、语法分析(源代码字符流转为标记集合,构造抽象语法书)
填充符号表
产生符号地址、符号信息
符号表用于语义检查、产生中间代码
插入式注解处理器的注解处理过程
分析与字节码生成
标注检查
数据流与控制流分析
解语法糖
字节码生成
10.2.2 解析与填充符号表
词法、语法分析
词法分析:源代码字符流转变为标记集合
int a = b+2 6个标记
语法分析:根据标记序列构造抽象语法书
后续操作基于AST进行
填充符号表
产生符号地址、符号信息
符号表用于语义检查、产生中间代码
10.2.3 注解处理器
”编译期插件“允许修改、读取、添加AST
10.2.4 语义分析和字节码生成
语义分析
对结构上正确的源程序进行上下文相关性质检查
标注检查
变量使用前是否生命、变量与赋值数据类型匹配
数据及控制流分析
局部变量使用前是否赋值
方法路径返回值完备性
受检查异常是否处理
解语法糖
字节码生成
语法书、符号表转成字节码
添加、转换少量代码
10.3 Java语法糖味道
10.3.1 泛型
消除式泛型
编译后泛型类型不存在
Java和C#的泛型
泛型的历史背景
类型擦除
裸类型
值类型与未来的泛型
10.3.2 自动装箱、拆箱与遍历循环
10.3.3 条件编译
if (boolean true)实现 ,编译期间忽略非true部分
10.4 实战:插入式注解处理器
略
第11章 后端编译与优化
11.1 概述
提前编译
即时编译
11.2 即时编译器
热点代码
某个方法或代码块运行特别频繁
提交执行效率
热点代码编译成本地机器码
代码优化
11.2.1 编译器与解释器
编译器
快速启动和运行
节省内存
解释器
运行过程中字节码编译成本地代码,减少损耗,提高效率
HotSpot即时编译器
客户端编译器 C1
服务端编译器C2
JDK10 Graal编译器
混合模式 Mixed mode
分层编译功能
即时编译器占用程序时间
解释器收集性能信息
启动响应速度、执行效率平衡
11.2.2 编译对象与触发条件
热点代码
多次调用的方法
多次执行的循环体
栈上替换
方法入口修改
编译对象都是整个方法体
热点探测
基于采样的热点探测
周期性检查各个线程的调用栈顶
简单高效容易获取方法调用关系、难精准确认方法热度
基于计数器的热点探测(HotSpot)
为每个方法、代码块建立计数器、统计方法执行次数
需建立并维护计数器
不能直接获取方法调用关系
方法调用计数器+回边计数器
11.2.3 编译过程
未完成编译之前,按解释方式执行代码
客户端编译器
三段式编译器
平台独立前端将字节码构造成高级中间码 HIR
与目标机器指令集无关
平台相关后端将HIR产生低级中间码LIR
与目标机器指令集相关
平台相关后端使用线性臊面产生机器代码
11.3 提前编译期
11.3.1 提前编译的优劣得失
程序运行之前把程序代码比那已成机器码的静态翻译工作
把即时编译期的编译工作提前做好保存
即时编译器占用程序运行时间和运算资源
即时编译器优势
性能分析制导优化
激进预测性优化
链接时优化
11.4 编译期优化技术
输出代码优化质量高低决定编译期优秀与否的关键
11.4.1 优化技术概览
内联
冗余存储消除
复写传播
无用代码消除
11.4.2 方法内联
优化之母
去除方法调用成本(查找方法版本、栈帧)
为其他优化建立基础
虚方法调用需要在运行时根据实际类型动态分派,无法内联
类继承关系分析
非虚方法:直接内联
虚方法
CHA查询是否有多个目标版本
一个版本
守护内联
逃生门:解释执行
多个版本
内联缓存:建立在目标方法正常入口前的缓存
11.4.3 逃逸分析
分析对象动态作用域
外部方法引用:方法逃逸
其他线程访问:线程逃逸
对象不存在方法、线程逃逸
栈上分配
对象线上分配,随线程退出而销毁,不用GC
变量替换
创建对象若干原始类型属性,替代创建对象
栈上分配变量代替堆内分配内存
同步消除
没有线程逃逸,则消除同步措施
11.4.4 公共子表达式消除
表达式E已经被计算过且变量值未改变,则称为公共子表达式
局部公共子表达式消除
全局公共子表达式消除
11.4.5 数组边界检查消除
编译期根据数据流分析确定数组最大值,判断不越界,则运行时不再检查
隐式异常优化
高效并发
第12章 Java内存模型与线程
12.1 概述
每秒处理事务数 TPS(Transsactions Per Second)
处理器和存储、通信系统速度差异
12.2 硬件效率与一致性
高速缓存作为内存和处理器之间缓存
缓存一致性
每个处理器都有缓存,读写同一块主存,保证缓存数据一致
内存模型:特定操作协议下,对特定内存、高速缓存进行读写的抽象
计算机访问缓存协议有
MSI
MESI
MOSI
Synapse
Dragon
Protocol
12.3 Java内存模型
12.3.1 主内存与工作内存
Java内存模型
目的:定义程序各种变量的访问规则 关注虚拟把变量存储到内存和取出内存的细节
变量:实力字段、静态字段、数据对象元素;不包括局部变量和方法参数(线程私有)
要求
所有变量存储在主内存
每条线程有自己的工作内存
线程对变量的读取必须再工作内存进行,且不能直接访问主内存数据
线程间数据传递通过主内存
12.3.2 内存间交互操作
八种操作:原子、不可再分(long double例外)
lock unlock:作用主内存,标量标识线程独占/锁定状态释放
read write:作用主内存,把变量的值从主内存传输到线程内存/把工作内存变量值放到主内存
load store :作用工作内存,把从主内存得到的值放入工作内存变量副本/吧工作内存中变量的值传送到主内存中
use assign: 作用工作内存 把工作内存中变量值传递执行引擎/把执行引擎接收的值赋给工作内存变量
限制
不允许read和load,store和write操作之一单独出现,即不允许一个变量从主内存读取了但工作内存不接受,或者工作内存发起回写了但主内存不接受的情况。
不允许一个线程丢弃它最近的assign操作,即变量在工作内存中改变了之后必须把该变化同步回主内存
不允许一个线程无原因的(没有发生过任何assign操作)把数据从线程的工作内存同步回主内存中。
一个新的变量只能在主内存中“诞生”,不允许在工作内存中直接使用一个未被初始化(load或assign)的变量,换句话说就是对一个变量实施user、store操作之前,必须先执行assign和load操作
一个变量在同一个时刻只允许一条线程对其进行lock操作,但lock操作可以被同一条线程重复执行多次,多次执行lock后,只有执行相同次数的unlock操作,变量才会被解锁
如果对一个变量执行lock操作,那将会清空工作内存中此变量的值,在执行引擎使用这个变量前,需要重新执行load或assign操作以初始化变量的值
如果一个变量没有被执行lock操作,那么不允许对它执行unlock操作,也不允许unlock一个被其他线程锁定的变量
对一个变量执行unlock操作之前,必须先把此变量同步回主内存中
12.3.3 对于volatile型变量的特殊规则
保证此变量对所有线程可见性:一个线程修改,其他线程立即得知
volatile在所有线程一致,但并不线程安全
修改后立即存入主内存,用时候立即从主内存获取
禁止指令重排序优化
代码执行顺序与程序中执行顺序一致
加了内存屏障, lock指令保证其他线程无效化缓存
指令重拍无法越过屏障
12.3.4 针对long和double型变量的特殊规则
对64位数据类型不保证原子性
12.3.5 原子性、可见性与有序性
原子性
基本数据的访问具备原子性
lock unlock
monitorenter monitorexit
synchronized
可见性
线程修改后其他线程立即可见
volatile
synchronized
对一个变量unlock之前必须先写入主内存
final
构造器初始化后,其他线程可见
有序性
本线程观察所有均有序,线程间观察无序
指令重排序、工作内存主内存之间同步延迟
volatile
禁止指令重排序
synchronized
一个变量在同一时刻只允许同一线程lock
12.3.6 先行发生原则
先行发生的影响会被后发生观察到
程序次序规则
控制流顺序
管程锁定规则
unlock先于同一锁的lock
volatile变量规则
volatile写先于读
线程启动规则
start()先于所有
线程终止规则
线程所有操作先于终止检测
线程中断规则
线程所有操作先于代码检测到中断发生
对象终结规则
对象初始化完成先于finalize()
传递性
A>B B>C A>C
操作时间上先发生不代表先行发生,无关系
12.4 Java与线程
12.4.1 线程的实现
线程目的:把进程的资源分配和执行调度分开
线程共享进程资源、独立调度
使用内核线程实现 1:1
内核线程:直接操作系统内核的线程KLT
使用内核线程的高级接口:轻量级进程
每个LWP都是单独调度单元
线程操作需要系统调用,进行用户态、内核态奇幻
消耗内核资源,支持LWP有限
Java
使用用户线程实现 1:N
完全建立在用户空间
系统内核无感知,用户态完成线程建立、同步、销毁等
不需要系统内核支持
实现困难:解决线程映射处理器
混合实现 N:M
用户线程建立在用户空间
可支持大规模并发
LWP作为用户线程和内核线程的桥梁
Java
HotSpot JDK1.3之后1:1
Solaris 同时支持
12.4.2 Java线程调度
协同式
线程执行时间自己控制
执行完成后通知系统切换
实现简单、易阻塞引发系统崩溃
线程执行时间不可知、不存在同步问题
抢占式
线程执行时间系统可控
线程优先级
Java和OS优先级定义不一致
12.4.3 线程转换
New
创建后尚未启动
Runable
包括Running、Ready (正在执行、等待OS分配执行时间)
Blocked
阻塞,等待获取排他锁
Waiting
无限期等待,不会分配系统时间,等待显示唤醒notify
Object::wait() Thread::join() LockSupport::park()
Timed Waiting
定时等待,不会分配系统时间,无需唤醒,时间到系统唤醒
Terminated
终止
等待队列、阻塞队列等状态和上述状态的细节关系、区别
12.5 Java和协程
第13章 线程安全与锁优化
13.1 概述
保证并发正确性
再保证并发高效性
13.2 线程安全
当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为,那么这个类就是线程安全的。
13.2.1 Java语言中的线程安全
不可变
final
绝对线程安全
相对线程安全
线程兼容
线程对立
13.2.2 线程安全的实现方法
互斥同步
synchronized
monitorentor、monitorexit指令
上述指令需要reference类型参数指定锁对象
锁计数器:根据上述两指令,加减1实现锁访问
可重入
释放锁之前,禁止其他线程进入
重量级锁
Java线程映射OS内核线程实现
唤醒、阻塞需要OS实现线程切换
用户态、内核态切换:耗费处理器时间
ReentrantLock
等待可中断
其他等待线程可选择放弃等待锁
公平锁
根据申请锁时间顺序依次获得:synchronized非公平,默认非公平
锁绑定多个条件
多个Condition实现锁,synchronized一个
推荐synchronized
语法清晰简单
优化更多
Lock需要finally释放
同步:多线程并发访问共享数据,只有一个线程可使用
互斥:实现同步手段
非阻塞同步
悲观锁
只要不做同步措施,肯定出现问题
乐观锁
不管风险,先操作,线程冲突再补偿
不需要阻塞,挂起线程
CAS
原子性
ABA问题
AtomicInteger
无同步方案
可重入代码
线程本地存储
ThreadLocal
13.3 锁优化
13.3.1 自旋锁与自适应自旋
互斥同步最大影响是阻塞的实现,阻塞和恢复需要内核态、用户态切换
让等待线程不放弃处理器执行时间,执行忙循环
多核心处理器
避免线程切换,但是耗费处理器时间
自旋次数
自适应自旋
13.3.2 锁消除
根据逃逸分析支持,其他线程无访问时
解释执行仍然同步
即时编译执行消除同步
13.3.3 锁粗化
多个加锁操作,会提升锁位置到整个操作序列外部(多个sb.append())
13.3.4 轻量级锁
CAS
没有多线程竞争前提下,减少重量级锁使用操作系统互斥量的消耗
对象头
对象运行状态数据
aka Mark Word
非固定动态结构
根据不同锁状态存储不同内容
指向类型数据指针
代码进入同步块如果没有锁定,在线程栈帧创建锁记录,存储对象Mark Word拷贝。CAS把对象头的Mark Word修改为指向锁记录的指针。成功则用有锁,失败先检查是否指向当前栈帧,是则代表当前线程占锁。否则直接膨胀为重量级锁
补充图示
13.3.5 偏向锁
无竞争情况下消除所有同步
偏心于第一个获取它的锁
优化:标红、图片、总结、结构
Shenandoah
补充关键的图片
CMS G1对比 各个阶段的不同 特点
标红部分因为主题都丢了,重新编辑下
并行-并发
并行:多条相同线程同时工作,用户线程等待
并发:GC线程和用户线程同时工作
GC名词
Partial GC
Minor GC/Youngh GC
新生代GC
Major GC/Old GC
老年代GC(或整堆)
只有CMS单独收集老年代
Mixed GC
整个新生代和部分老年代GC
只有G1支持
Full GC
收集整个堆和方法区