深入理解Java虚拟机
2020-06-16 10:16:23 2 举报
AI智能生成
深入理解Java虚拟机
作者其他创作
大纲/内容
深入理解Java虚拟机
第一部分 走进 Java
第 1 章 走近 Java
1.1 概述
1.2 Java 技术体系
组成部分
Java 程序设计语言
各种硬件平台上的 Java 虚拟机
Class 文件格式
Java API 类库
来自商业机构和开源社区的第三方 Java 类库
1.3 Java 发展史
1.4 Java 虚拟机发展史
1.4.1 Sun Classic/Exact VM
1.4.2 Sun HotSpot VM
1.4.3 Sun Mobile-Embedded VM/Meta-Circular VM
1.4.4 BEA JRockit/IBM J9 VM
1.4.5 Azul VM /BEA Liquid VM
1.4.6 Apache Harmony / Google Android Dalvik VM
1.4.7 Microsoft JVM 及其他
1.5 展望 Java 技术的未来
1.5.1 模块化
1.5.2 混合语言
1.5.3 多核并行
1.5.4 进一步丰富语法
1.5.5 64 位虚拟机
1.6 实战:自己编译 JDK
1.6.1 获取 JDK 源码
1.6.2 系统需求
1.6.3 构建编译环境
1.6.4 进行编译
1.6.5 在 IDE 工具中进行源码调试
1.7 本章小结
第二部分 自动内存管理机制
第 2 章 Java 内存区域与内存溢出异常
2.1 概述
2.2 运行时数据区域
2.2.1 程序计数器
2.2.2 Java 虚拟机
2.2.3 本地方法栈
2.2.4 Java 堆
2.2.5 方法区
2.2.6 运行时常量池
2.2.7 直接内存
2.3 HotSpot 虚拟机对象探秘
2.3.1 对象的创建
指针碰撞(Bump the Pointer)
空闲列表(Free List)
并发创建问题
CAS配上失败重试的方式
把内存分配的动作按照线程划分在不同的空间之中进行:本地线程分配缓冲
2.3.2 对象的内存布局
对象头(Header)
实例数据(Instance Data)
对齐填充(Padding)
2.3.3 对象的访问定位
使用直接句柄访问
使用直接指针访问
2.4 实战:OutOfMemoryError 异常
2.4.1 Java 堆溢出
内存泄露(Memory Leak)
内存溢出(Memory Overflow)
2.4.2 虚拟机栈和本地方法栈溢出
如果线程请求的栈深度大于虚拟机所允许的最大深度,将抛出 StackOverflowError 异常
如果虚拟机在扩展栈时无法申请到足够的内存空间,则抛出 OutOfMemoryError 异常
2.4.3 方法区和运行时常量池溢出
2.4.4 本机直接内存溢出
2.5 本章小结
第 3 章 垃圾收集器与内存分配策略
3.1 概述
哪些内存需要回收
什么时候回收
如何回收
3.2 对象已死吗
3.2.1 引用计数法
很难解决对象之间相互循环引用的问题
Reference Counting
3.2.2 可达性分析算法
Reachability Analysis
GC Roots
虚拟机栈(栈帧中的本地变量表)中引用的对象
方法区中类静态属性引用的对象
方法区中常量引用的对象
本队方法栈中 JNI (即一般说的 Native 方法)引用的对象
3.2.3 再谈引用
强引用 (Strong Reference)
永远不会回收掉被引用的对象
软引用(Soft Reference)
在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收
无论当前内存是否足够,都会回收调只被弱引用关联的对象
为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知
弱引用(Weak Reference)
虚引用(Phantom Reference)
3.2.4 生存还是死亡
3.2.5 回收方法区
3.3 垃圾收集算法
3.3.1 标记-清除算法
3.3.2 复制算法
3.3.3 标记-整理算法
3.3.4 分代收集算法
3.4 HotSpot 的算法实现
3.4.1 枚举根节点
3.4.2 安全点
抢先式中断
主动式中断
3.4.3 安全区域
3.5 垃圾收集器
3.5.1 Serial 收集器
单线程的收集器
必须暂停其他所有的工作线程
3.5.2 ParNew 收集器
Serial 收集器的多线程版本
3.5.3 Parallel Scavenge 收集器
新生代并行的多线程收集器
目标是达到一个可控制的吞吐量,就是 CPU 用于运行用户代码的时间与 CPU 总消耗时间的比值
MaxGCPauseMillis
GCTimeRatio
UseAdaptiveSizePolicy
3.5.4 Serial Old 收集器
Serial 收集器的老年代版本
是一个单线程收集器
3.5.5 Parallel Old 收集器
Parallel Scavenge 收集器的老年代版本
注重吞吐量以及 CPU 资源敏感的场合
3.5.6 CMS 收集器
获取最短回收停顿时间为目标的收集器
步骤
初始标记 (CMS initial mark)
并发标记(CMS concurrent mark)
重新标记(CMS remark)
并发清除(CMS concurrent sweep)
缺点
对CPU资源非常敏感
无法处理浮动垃圾
是基于标记-清除算法实现的,会有大量空间碎片产生
参数
UseCMSCopactAtFullCollection
CMSFullGCsBeforeCompaction
3.5.7 G1 收集器
特点
并行与并发
分代收集
空间整合
可预测的停顿
初始标记(Initial Marking)
并发标记(Concurrent Marking)
最终标记(Final Marking)
筛选回收(Live Data Counting and Evacuation)
3.5.8 理解 GC 日志
GC 发生的时间
垃圾收集的停顿类型
GC 发生的区域
GC前该内存区域已使用容量 -> GC后该内存区域已使用容量(该内存区域总容量)
GC前Java 堆已使用容量 -> GC 后 Java 堆已使用容量(Java堆总容量)
用户态消耗CPU时间、内核态消耗的CPU时间和操作从开始到结束所经过的墙钟时间
3.5.9 垃圾收集器参数总结
UseSerialGC
UseParNewGC
UseConcMarkSweepGC
UseParallelGC
UseParallelOldGC
SurvivorRatio
PretenureSizeThreshold
MaxTenuringThreshold
HandlePromotionFailure
ParallelGCThreads
CMSInitiatingOccupancyFraction
UseCMSCompactAtFullCollection
3.6 内存分配与回收策略
3.6.1 对象优先在 Eden 分配
3.6.2 大对象直接进入老年代
3.6.3 长期存活的对象将进入老年代
3.6.4 动态对象年龄判定
3.6.5 空间分配担保
3.7 本章小结
第 4 章 虚拟机性能监控与故障处理工具
4.1 概述
4.2 JDK 的命令行工具
4.2.1 jps:虚拟机进程状况工具(jps [options] [hostid])
-q 只输出 LVMID,省略主类的名称
-m 输出虚拟机进程启动时传递给主类 main() 函数的参数
-l 输出主类的全名,如果进程执行的是 jar 包,输出 jar 路径
-v 输出虚拟机进程启动时 JVM 参数
4.2.2 jstat:虚拟机统计信息监视工具(jstat [option vmid [interval [s | ms] [count]]])
VMID [protocol:][//]lvmid[@hostname[:port]/servername]
-class 监视类装载、卸载数量、总空间以及类装载所耗费的时间
-gc 监视 Java 堆状况,包括 Eden 区、两个 survivor 区、老年代、永久代等的容量、已用空间、GC 时间内合计等信息
-gccapacity 监视内容与 -gc 基本相同,但输出主要关注 Java 堆各个区域使用到的最大、最小空间
-gcutil 监视内容与 -gc 基本相同,但输出主要关注已使用空间占总空间的百分比
-gccause 与 -gcutil 功能一样,但是会额外输出导致上一次 GC 产生的原因
-gcnew 监视新生代 GC 状况
-gcnewcapacity 监视内容与 -gcnew 基本相同,输出主要关注使用到的最大、最小空间
-gcold 监视老年代 GC 状况
-gcoldcapacity 监视内容与 -gcold 基本相同,输出主要关注使用到的最大、最小空间
-gcpermcapacity 输出永久代使用到的最大、最小空间
-compiler 输出 JIT 编译器编译过的方法、耗时等信息
-printcompilation 输出已经被 JIT 编译的方法
4.2.3 jinfo:Java 配置信息工具(jinfo [option] pid)
-flag [+/-] name
jinfo -flag CMSInitiatingOccupancyFraction 1444
-flag name=value
4.2.4 jmap:Java 内存映像工具(jmap [option] vmid)
-finalizerinfo 显示在 F-Queue 中等待 Finalizer 线程执行 finalize 方法的对象。只在 Linux/Solaris 平台下有效
-heap 显示 Java 堆详细信息,如使用哪种回收器、参数配置、分代状况等。只在 Linux/Solaris 平台下有效
-histo 显示堆中对象统计信息,包括类、实例数量、合计容量
-permstat 以 ClassLoader 为统计口径显示永久代内存状态。只在 Linux/Solaris 平台下有效
-F 当虚拟机进程堆 -dump 选项没有响应时,可使用这个选项强制生成 dump 快照。只在 Linux/Solaris 平台下有效
4.2.5 jhat:虚拟机堆转储快照分析工具
4.2.6 jstack:Java 堆栈跟踪工具(jstack [option] vmid)
-F 当正常输出的请求不被响应时,强制输出线程堆栈
-l 除堆栈外,显示关于锁的附加信息
-m 如果调用到本地方法的话,可以显示 C/C++ 的堆栈
4.2.7 HSDIS:JIT 生成代码反汇编
4.3 JDK 的可视化工具
4.3.1 JConsole:Java 监视与管理控制台
1. 启动 JConsole
2. 内存监控
3. 线程监控
4.3.2 VisualVM:多合一故障处理工具
1. VisualVM 兼容范围与插件安装
2. 生成、浏览堆转储快照
3. 分析程序性能
4. BTrace 动态日志跟踪
4.4 本章小结
第 5 章 调优案例分析与实战
5.1 概述
5.2 案例分析
5.2.1 高性能硬件上的程序部署策略
不定期出现长时间失去响应的情况
使用64位 JDK 来管理大内存 面临问题
内存回收导致的长时间停顿
现阶段,64位 JDK 的性能测试结果普遍低于 32 位 JDK
需要保证程序足够稳定,无法分析十几GB 的 Dump 文件
相同程序在64位JDK 消耗的内存一般比32位 JDK 大,这是由于指针膨胀,以及数据类型对齐补白等因素导致的。
逻辑集群
尽量避免节点竞争全局的资源,最典型的就是磁盘竞争
很难最高效率地利用某些资源池
各个节点仍然不可避免地受到32位内存限制
大量使用本地缓存的应用
5.2.2 集群间同步导致的内存溢出
过滤器导致集群各个节点之间网络交互非常频繁。当网络情况不能满足传输要求时,重发数据在你内存中不断堆积,很快就产生了内存溢出。
5.2.3 堆外内存导致的溢出错误
受操作系统进程最大内存的限制
Direct Memory
线程堆栈
Socket 缓存区
JNI 代码
虚拟机和 GC
5.2.4 外部命令导致系统缓慢
5.2.5 服务器 JVM 进程崩溃
两个服务之间调用长达3分钟才能返回,并且返回结果都是连接中断
5.2.6 不恰当数据结构导致内存占用过大
5.2.7 由 Windows 虚拟内存导致的长时间停顿
-XX:+PrintGCAppliccationStoppedTime
-XX:+PrintGCDateStamps
-Xloggc:gclog.log
-XX:+PrintReferenceGC
5.3 实战:Eclipse 运行速度调优
5.3.1 调优前的程序运行状态
5.3.2 升级 JDK1.6 的性能变化及兼容问题
5.3.3 编译时间和类加载时间的优化
-Xverify:none 禁止掉字节码验证过程
5.3.4 调整内存设置控制垃圾收集频率
-XX:+DisableExplicitGC 屏蔽 System.gc()
5.3.5 选择收集器降低延迟
5.4 本章小结
第三部分 虚拟机执行子系统
第 6 章 类文件结构
6.1 概述
如何存储?
6.2 无关性的基石
6.3 Class 类文件的结构
6.3.1 魔数与 Class 文件的版本
6.3.2 常量池
6.3.3 访问标志
6.3.4 类索引、父类索引与接口索引集合
6.3.5 字段表集合
6.3.6 方法表集合
6.3.7 属性表集合
6.4 字节码指令简介
6.4.1 字节码与数据类型
6.4.2 加载和存储指令
6.4.3 运算指令
6.4.4 类型转换指令
6.4.5 对象创建与访问指令
6.4.6 操作数栈管理指令
6.4.7 控制转移指令
6.4.8 方法调用和返回指令
6.4.9 异常处理指令
6.4.10 同步指令
6.5 公有设计和私有实现
6.6 Class 文件结构的发展
6.7 本章小结
第 7 章 虚拟机类加载机制
7.1 概述
如何载入(创建)?
7.2 类加载的时机
-XX:+TraceClassLoading
通过子类引用父类的静态字段,不会导致子类初始化
通过数组定义来引用类,不会触发此类的初始化
常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化
7.3 类加载的过程
7.3.1 加载
通过一个类的全限定名来获取定义此类的二进制字节流
将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
在内存中生成一个代表这个类的 java.lang.Class 对象,作为方法区这个类的各种数据的访问入口
7.3.2 验证
1. 文件格式验证
验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的许你就处理
2. 元数据验证
对字节码描述的信息进行语义分析
3. 字节码验证
通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的
对类的方法提进行校验分析
-XX:+FailOverToOldVerifier
-XX:-UseSplitVerifier
4. 符号引用验证
对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验
-Xverify:none
7.3.3 准备
正式为类变量分配内存并设置类变量初始值的阶段
7.3.4 解析
将常量池内的符号引用替换为直接引用的过程
符号引用
以一组符号来描述所引用的目标
直接引用
直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄
1. 类或接口的解析
2. 字段解析
3. 类方法解析
4. 接口方法解析
7.3.5 初始化
7.4 类加载器
7.4.1 类与类加载器
比较两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义
7.4.2 双亲委派模型
类加载器
启动类加载器
扩展类加载器
应用程序类加载器
7.4.3 破坏双亲委派模型
JDK1.2 发布之前
线程上下文类加载器
由这个模型自身的缺陷所导致的
Java 中涉及 SPI 的加载动作基本上都采用这种方式,例如:JNDI、JDBC、JCE、JAXB、JBI等
OSGI
用户对程序动态性的追求而导致的。
代码热替换 HotSwap
模块热部署 Hot Deployment
类搜索
1. 将以 java.* 开头的类委派给父类加载器加载
2. 否则,将委派列表名单内的类委派给父类加载器加载
3. 否则,将 Import 类表中的类委派给 Export 这个类的 Bundle 的类加载器加载
4. 否则, 查找当前 Bundle 的 ClassPath,使用自己的类加载器加载
5. 否则,查找类是否在自己的 Fragment Bundle 中,如果在,则委派给 Fragment Bundle 的类加载器加载
6. 否则,查找 Dynamic Import 列表的 Bundle,委派给对应 Bundle 的类加载器加载
7. 否则,类查找失败
7.5 本章小结
第 8 章 虚拟机字节码执行引擎
8.1 概述
如何执行?
8.2 运行时栈帧结构
8.2.1 局部变量表
用于存放方法参数和方法内部定义的局部变量
8.2.2 操作数栈
它是一个后入先出(LIFO)栈
8.2.3 动态连接
每个栈帧都包含一个指向运行时常量池中该栈帧所属方法的引用
8.2.4 方法返回地址
退出方式
执行引擎遇到任意一个方法返回的字节码指令
在方法执行过程中遇到异常,并且这个异常没有在方法体内得到处理
8.2.5 附加信息
虚拟机规范运行具体的虚拟机实现增加一些规范里没有描述的信息到栈帧中,例如与调试相关的信息
8.3 方法调用
8.3.1 解析
invokestatic
调用静态方法
invokespecial
调用实例构造器<init>方法、私有方法和父类方法
invokevirtual
调用所有的虚方法
invokeinterface
调用接口方法,会在运行时再确定一个实现此接口的对象
invokedynamic
先在运行时动态解析出调用点限制符所引用的方法,然后再执行该方法
8.3.2 分派
1. 静态分派
重载加载顺序
char->int->long->float->double
自动装箱 Character
Serializable
Object
变长参数:char...
2. 动态分派
重写
3. 单分派与多分派
单分派是根据一个宗量对目标方法进行选择
多分派则是根据多余一个宗量对目标方法进行选择
4. 虚拟机动态分派的实现
8.3.3 动态类型语言支持
1. 动态类型语言
关键特征是它的类型检查的主题过程是在运行期而不是编译期
2. JDK1.7 与动态类型
3. java.lang.invoke 包
4. invokeddynamic 指令
5. 掌控方法分派规则
8.4 基于栈的字节码解释执行引擎
8.4.1 解释执行
1. 程序源码
2. 词法分析
3. 单词流
4. 语法分析
5. 抽象语法树
分支一
6. 指令流(可选)
7. 解释器
8. 解释执行
分支二
6. 优化器(可选)
7. 中间代码(可选)
8. 生成器
9. 目标代码
8.4.2 基于栈的指令集与基于寄存器的指令集
栈的指令集
iconst_1、iconst_1、iadd、istore_0
可移植
执行速度相对来说稍慢一些
指令数量
栈实现在内存上,频繁访问内存
基于寄存器
由硬件直接提供
8.4.3 基于栈的解释器执行过程
8.5 本章小结
第 9 章 类加载及执行子系统的案例与实战
9.1 概述
9.2 案例分析
9.2.1 Tomcat:正统的类加载器架构
9.2.2 OSGI:灵活的类加载器架构
9.2.3 字节码生成技术与动态代理的实现
System.getProperties().put(\"sun.misc.ProxyGenerator.saveGeneratedFiles\
9.2.4 Retrotranslator:跨越 JDK 版本
新增功能分类
1. 在编译器层面做的改进
2. 对 Java API 的代码增强
3. 需要在字节码中进行支持的改动
4. 虚拟机内部的改进
9.3 实战:自己动手实现远程执行功能
9.3.1 目标
9.3.2 思路
1. 如何编译提交到服务器的 Java 代码?
2. 如何执行编译之后的 Java 代码?
3. 如何收集 Java 代码的执行结果?
9.3.3 实现
9.3.4 验证
9.4 本章小结
第四部分 程序编译与代码优化
第 10 章 早期(编译期)优化
10.1 概述
10.2 Javac 编译器
10.2.1 Javac 的源码与调试
编译过程
解析与填充符号表过程
插入式注解处理器的注解处理过程
分析与字节码生成过程
com.sun.tools.javac.main.JavaCompiler
准备过程. initProcessAnnotations(processors) 准备过程:初始化插入式注解处理器
过程2. processAnnotations 执行注解处理
过程1.2 enterTrees 输入到符号表
过程1.1 parseFiles 词法分析、语法分析
过程3 compile2() 分析及字节码生成
过程3.1 attribute 标注
过程3.2 flow 数据流分析
过程3.3 desugar 解语法糖
过程3.4 generate 生成字节码
10.2.2 解析与填充符号表
1. 词法、语法分析
2. 填充符号表
10.2.3 注解处理器
10.2.4 语义分析与字节码生成
1. 标注检查
2. 数据及控制流分析
3. 解语法糖
4. 字节码生成
10.3 Java 语法糖的味道
10.3.1 泛型与类型擦除
10.3.2 自动装箱、拆箱与遍历循环
10.3.3 条件编译
10.4 实战:插入式注解处理器
10.4.1 实战目标
10.4.2 代码实现
10.4.3 运行与测试
10.4.4 其他应用案例
Hibernate Validator Annotation Processor
Project Lombok
10.5 本章小结
第 11 章 晚期(运行期)优化
11.1 概述
11.2 HotSpot 虚拟机内的即时编译器
11.2.1 解释器与编译器
解释器:当程序需要迅速启动和执行的时候,解释器可以首先发挥作用,省去编译的时间
编译器:随着时间的推移,把越来越多的代码编译成本地代码之后,可以获取更高的执行效率
-Xint 强制虚拟机运行与 解释模式
-Xcomp 强制虚拟机运行于 编译模式
11.2.2 编译对象与触发条件
热点代码
被多次调用的方法
被多次执行的循环体
热点探测
基于采样的热点探测
基于计数器的热点探测
方法调用计数器(Invocation Counter)
回边计数器(Back Edge Counter)
-XX:CompileThreshold 设置调用次数
-XX:-UseCounterDecay 关闭热度衰减
-XX:CounterHalfLifeTime 设置半衰周期的时间
11.2.3 编译过程
11.2.4 查看及分析即时编译结果
11.3 编译优化技术
11.3.1 优化技术概览
编译器策略
延迟编译
分层编译
栈上替换
延迟优化
程序依赖图表示
静态单赋值表示
基于性能监控的优化技术
乐观空值断言
乐观类型断言
乐观类型增强
乐观数组长度增强
裁剪未被选择的分支
乐观的多态内联
分支频率预测
调用频率预测
基于证据的优化技术
精确类型推断
内存值推断
内存值跟踪
常量折叠
重组
操作符退化
空值检查消除
类型检测退化
代数化简
公共子表达式消除
数据流敏感重写
条件常量传播
基于流承载的类型缩减转换
无用代码消除
语言相关的优化技术
类型继承关系分析
去虚拟机化
符号常量传播
自动装箱消除
逃逸分析
当一个对象在方法中被定义后,它可能被外部方法所引用
栈上分配
同步消除
如果逃逸分析能够确定一个变量不会逃逸出线程,无法被其他线程访问,那这个变量的读写肯定就不会有竞争
标量替换
标量是指一个数据已经无法再分解成更小的数据来表示了
-XX:+DoEscapeAnalysis 开启逃逸分析
-XX:+PrintEscapeAnalysis 查看分析结果
-XX:+EliminateAllocations 开启标量替换
+XX:+EliminateLocks 开启同步消除
-XX:+PrintEliminateAllocations 查看标量的替换情况
锁消除
锁膨胀
消除反射
内存及代码位置变换
表达式提升
表达式下沉
冗余存储消除
相邻存储合并
交汇点分离
循环变换
循环展开
循环剥离
安全点清除
迭代范围分离
范围检查消除
循环向量化
全局代码调整
内联
去除方法调用的成本
为其他优化建立良好的基础
全局代码外提
基于热度的代码布局
Switch 调整
控制流图变换
本地代码编排
本地代码封包
延迟槽填充
着色图寄存器分配
线性扫描寄存器分配
复写聚合
常量分裂
复写移除
地址模式匹配
指令窥孔优化
基于确定有限状态机的代码生成
11.3.2 公共子表达式消除
11.3.3 数组边界检查消除
11.3.4 方法内联
11.3.5 逃逸分析
优化
11.4 Java 与 C/C++ 的编译器对比
1. 因为即时编译球运行占用的是用户程序的运行时间
2. Java 语言是动态的类型安全语言
3. Java 语言中虽然没有 virtual 关键字,但使用虚方法的频率却远远大于 C/C++ 语言
4. Java 语言是可以动态扩展的语言
5. Java 语言中对象的内存分配都是堆上进行的,只有方法中的局部变量才能在栈上分配
11.5 本章小结
第五部分 高效并发
第 12 章 Java 内存模型与线程
12.1 概述
12.2 硬件的效率与一致性
缓存一致性
12.3 Java 内存模型
12.3.1 主内存与工作内存
12.3.2 内存间交互操作
lock 锁定
作用于主内存的变量,它把一个变量标识为一条线程独占的状态
unlock 解锁
作用于主内存的变量,他把一个处于锁定状态的变量释放出来,释放后的变量才可以被其它线程锁定
read 读取
作用于主内存的变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的 load 动作使用
load 载入
作用于工作内存的变量,它把read 操作从主内存中得到的变量值放入工作内存的变量副本中
use 使用
作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
assign 赋值
作用于工作内存的变量,它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
store 存储
作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中,以便随后的 write 操作使用
write 写入
作用于主内存的变量,它把 store 操作从工作内存中得到的变量的值放入主内存的变量中
12.3.3 对于 volitile 型变量的特殊规则
保证此变量对所有线程的可见性
运算结果并不依赖变量的当前值,或者能够确保只有单一的线程修改变量的值。
变量不需要与其它的状态变量共同参与不变约束
禁止指令重排序优化
内存屏障
volatile变量读操作的性能消耗与普通变量几乎没有什么差别,但是写操作则可能会慢一些
12.3.4 对于 long 和 double 型变量的特殊规则
12.3.5 原子性、可见性与有序性
原子性 Atomicity
read、load、assign、use、store、write
lock 和 unlock
monitorenter、monitorexit
可见性 Visibility
指一个线程修改了共享变量的值,其他线程能够立即得知这个修改
volatile、synchronized、final
有序性 Ordering
如果在本线程内观察,所有的操作都是有序的
如果在一个线程中观察另一个线程,所有的操作都是无序的。
12.3.6 先行发生原则
程序次序规则
在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作
管程锁定规则
一个unlock 操作先行发生于后面对同一个锁的 lock 操作。
volatile 变量规则
对一个 volatile 变量的写操作先行发生于后面对这个变量的读操作
线程启动规则
Thread 对象的 start() 方法先行发生于此线程的每一个动作
线程终止规则
线程中的所有操作都先行发生于对此线程的终止检测
Thread.join
Thread.isAlive
线程中断规则
对线程 interrupted 方法的调用先行发生于被中断线程的代码检测到中断事件的发生。
对象终结规则
一个对象的初始化完成(构造函数执行结束)先行发生于它的 finalize() 方法的开始
传递性
12.4 Java 与线程
12.4.1 线程的实现
1. 使用内核线程实现
就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度
由于是基于内核线程实现,所以各种线程操作,如创建、析构及同步,都需要进行系统调用
每个轻量级进程都需要有一个内核线程的支持,因此轻量级进程要消耗一定的内核资源
2. 使用用户线程实现
一个线程只要不是内核线程,就可以认为是用户线程
狭义上的用户线程指的是完全建立在用户空间的线程库上,系统内核不能感知线程存在的实现
3. 使用用户线程加轻量级进程混合实现
4. Java 线程的实现
12.4.2 Java 线程调度
指系统为线程分配处理器使用权的过程
协同式线程调度
Lua 语言
线程执行时间不可控制,设置如果一个线程编写有问题,一直不告知系统进行线程切换,那么程序就会一直阻塞在那里
抢占式线程调度
每个线程将由系统来分配执行时间
Thread.yield 可以让出执行时间
线程优先级
12.4.3 状态转换
新建 New
创建后尚未启动的线程处于这种状态
运行 Runable
无限期等待 Waiting
处于这种状态的线程不会被分配 CPU 执行时间,它们要等待被其他线程显式的唤醒
没有设置 Timeout 参数的 Object.wait() 方法
没有设置 Timeout 参数的 Thread.join() 方法
LockSupport.park() 方法
限期等待 Timed Waiting
处于这种状态的线程也不会被分配CPU执行时间,不过无须等待被其他线程显式地唤醒,在一定时间之后它们会由系统自动唤醒
Thread.sleep() 方法
设置了 Timeout 参数的 Object.wait() 方法
设置了 Timeout 参数的 Thread.join() 方法
LockSupport.parkNanos() 方法
LockSupport.parkUntil() 方法
阻塞 Blocked
在等待着获取到一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生
结束 Terminated
已终止线程的线程状态,线程已经结束执行。
12.5 本章小结
第 13 章 线程安全与锁优化
13.1 概述
当多个线程访问一个对象时,如果不用考虑这些线程在运行时环境下的调度和交替执行,也不需要进行额外的同步,或者调用方进行任何其他的协调操作,调用这个对象的行为都可以获得正确的结果,那这个对象是线程安全的。
13.2 线程安全
13.2.1 Java 语言中的线程安全
1. 不可变
String、Long、Double、BigInteger、BigDecimal
只要一个不可变的对象被正确地构建出来(没有发生 this 引用逃逸的情况),那其外部的可见状态永远也不会改变
2. 绝对线程安全
不管运行时环境如何,调用者都不需要任何额外的同步措施
3. 相对线程安全
它需要保证对这个对象单独的操作是线程安全的。
Vector、HashTable、Collections的 synchronizedCollection()
4. 线程兼容
指对象本身并不是线程安全的,但是可以通过在调用端正确地使用同步手段来保证对象在并发环境中可以安全地使用
5. 线程对立
指无论调用端是否采取了同步措施,都无法在多线程环境中并发使用的代码
Thread 类的 suspend() 和 resume() 方法
13.2.2 线程安全的实现方法
1. 互斥同步
指在多个线程并发访问共享数据时,保证共享数据在同一个时刻只被一个(或者是一些,使用信号量的时候)线程使用
临界区 Critial Sectionn
互斥量 Mutex
信号量 Semaphore
ReentrantLock
等待可中断
指当持有锁的线程长期不释放锁的时候,正在等待的线程可以选择放弃等待
可实现公平锁
指多个线程在等待同一个锁时,必须按照申请锁的时间顺序来依次获得锁。
锁可以绑定多个条件
指一个 ReentrantLock 对象可以同时绑定多个 Condition 对象
2. 非阻塞同步
基于冲突检测的乐观并发策略
指令
测试并设置 Test-and-Set
获取并增加 Fetch-and-Increment
交换 Swap
比较并交换 Compare-and-Swap 简称 CAS
ABA问题
提供了一个带有标记的原子引用类 AtomicStampedReference
加载链接/条件存储 Load-Linked/Store-Conditional 简称 LL/SC
3. 无同步方案
可重入代码 Reentrant Code
可以在代码执行的任何时刻中断它,转而去执行另外一段代码(包括递归调用它本身),而在控制权返回后,原来的程序不会出现任何错误。
线程本地存储 Thread Local Storage
把共享数据的可见范围限制在同一个线程之内
ThreadLocal
13.3 锁优化
13.3.1 自旋锁与自适应自旋
-XX:+UseSpinning
-XX:PreBlockSpin
13.3.2 锁消除
指虚拟机即时编译器在运行时,对一些代码上要求同步,但是被检测到不可能存在共享数据竞争的锁进行消除
13.3.3 锁粗化
总是推荐将同步块的作用范围限制得尽量小--只在共享数据的实际作用域中才进行同步
13.3.4 轻量级锁
是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗
对象头
用于存储对象自身的运行时数据
用于存储指向方法区对象类型数据的指针
13.3.5 偏向锁
就是在无竞争的情况下把整个同步都消除掉
-XX:+UseBiasedLocking
13.4 本章小结
附录
附录 A 编译 Windows 版的 OpenJDK
附录 B 虚拟机字节码指令表
附录C HotSpot 虚拟机主要参数表
1. 内存管理参数
2. 即时编译参数
3. 类型加载参数
4. 多线程相关参数
5. 性能参数
6. 调试参数
附录D 对象查询语言(OQL)简介
1. Select 子句
1. 选择特定的显示列
2. 使用列别名
3. 拼合成为一个对象列表选择项目
4. 排除重复对象
2. FROM 子句
1. FROM 子句指定需要查询的类
2. 包含子类
3. 禁止查询类实例
3. WHERE 子句
2. =、!=(等于操作)
3. AND (条件“与”操作)
4. OR (条件“或”操作)
5. 文字表达式
4. 属性访问器
1. 访问堆转储快照中对象的字段
2. 访问 Java Bean 属性
3. 调用 OQL Java 方法
4. OQL 的内建函数
5. OQL 语言的 BNF 范式
附录E JDK 历史版本轨迹
0 条评论
回复 删除
下一页