Note_深入理解java虚拟机

image-20190605212425037

​ 这里只是简单的列出书中的知识点, 其实本书是对相关规范(JVM和JLS)和基于虚拟机规范官方实现(HotSpot)的总结 ,更多详细可以看下面的扩展链接

内存区域

运行时数据区域

程序计数器

​ 线程独立,在虚拟机概念模型里,字节码解释器工作时通过这个计数器来选取下一条需要执行的指令

Java虚拟机栈

​ 线程独立,每个方法在执行时会创建一个栈帧,调用直至执行完成的过程,就对应一个栈帧在虚拟机栈中入栈到出栈的过程

本地方法栈

​ 与虚拟机栈类似,不过它为Native方法服务

Java堆

​ 线程共享,所有对象实例以及数组都要在堆上分配(不绝对)
​ 不绝对的原因:JIT编译器发展与逃逸分析技术逐渐成熟,栈上分配、标量替换

方法区

​ 线程共享,用于存储已被虚拟机加载的类信息、常量、静态变量、即时编译器编译后的代码
​ 运行时常量池, 用于存放编译器生成的各种字面量和符号引用

直接内存

​ 并不是虚拟机运行时数据区的一部分,会收到本机总内存大小以及处理器寻址空间的限制
​ NIO,引入一种基于通道与缓冲区的I/O方式

HotSpot虚拟机对象探秘

对象的创建

  1. 检查
  2. 类加载
  3. 分配内存
  4. 初始化零值
  5. 对象头处理
  6. 初始化

对象的内存布局

对象头
对象自身的运行时数据
对象哈希码、对象分代年龄
GC分代年龄
锁状态标记
线程持有的锁
偏向线程ID
偏向时间戳
类型指针
实例数据
对齐填充
对象大小必须是8字节的整数倍

对象的访问定位

使用句柄
reference存储的对象的句柄地址,句柄包含了实例数据和数据类型各种的地址
优势,在对象被移动时,只会改变句柄中的实例数据指针,而reference本身不需要修改
直接指针(HotSpot虚拟机使用)
reference存储的是对象地址
优势,速度快,节省了一次指针定位的时间开销


垃圾收集器与内存分配策略

对象可回收判断

​ 引用计数算法
​ 缺点是很难解决对象相互引用问题
​ 可达性分析算法 GC Roots对象
​ 虚拟机栈中引用的对象
​ 方法区中类静态属性引用的对象
​ 方法区中常量引用的对象
​ 本地方法栈中JNI引用的对象
​ 引用
​ 强引用
​ 软引用
​ 在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行二次回收
​ 弱引用
​ 只能生存到下一次垃圾收集发生之前
​ 虚引用
​ 生存还是死亡
​ 1.第一次标记
​ 2.是否覆盖finalize方法,或者finalize方法已经被虚拟机调用过
​ 3.放置在F-Queue队列
​ 回收方法区
​ 废弃常量
​ 无引用
​ 无用的类
​ 该类的所有实例都已经被回收
​ 加载该类的ClassLoader已经被回收
​ 该类对应的java.lang.Class对象没有在任何地方被引用,无法在任何地方通过反射访问该类的方法

垃圾收集算法

标记-清除算法

​ 不足
​ 效率问题
​ 空间问题

复制算法

不足:将内存缩小为原来的一半
优化:由于新生代对象大部分都是朝生夕死,所以将内存划分为8:1(Eden和Survivor)

标记-整理算法

老年代

分代收集算法

根据对象存活周期的不同将内存划分为几块,根据各个年代的特点采用最适合的收集算法

HotSpot算法实现

枚举根节点
使用OopMap的数据结构来得知哪些地方存放这对象引用(执行上下文和全局的引用位置)
安全点
HotSpot没有为每条指令都生成OopMap,只有在特定的位置,这些位置称为安全点
程序执行时,只有达到安全点才能暂停
GC时候怎么让线程都在最近安全点停止
抢先式中断
GC时候,先把所有线程中断,如果发现有线程没有在安全点上,就恢复线程,让它跑到安全点
主动式中断
GC需要中断的时候,不直接对线程操作,通过设置一个标记,各个线程在安全点时候去轮询这个标识,发现中断标识为真时候就自动挂起
安全区域
是指在一段代码片段之中引用关系不会发生变化
进入safe Region中的代码,会标识自己进入safe Region

垃圾收集器

Serial收集器
运行在Client模式下的单线程垃圾收集器的虚拟机
ParNew收集器
运行在Server模式下的多线程垃圾收集器的虚拟机
Parallel Scavenge收集器
可控制的吞吐量
Serial Old收集器
ParOld收集器
CMS收集器
最短回收停顿时间为目标的收集器
运行过程
初始标记
标记一下GC Roots能直接关联到的对象,STop The World
并发标记
GC Roots Tracing的过程
重新标记
修正并发标记期间因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,STop The World
并发清除
缺点
对CPU资源非常敏感
无法处理浮动垃圾
标记清除,碎片过多的解决方案
UseCMSCompactAtFullCollection
FullGC前先内存合并
CMSFullGCsBeforeCompaction
执行多少次不压缩的FullGC后,跟着来一次带压缩的
G1收集器(JDK1.7)
特点
并行与并发
分代收集
空间整合
可预测的停顿
基于Region,维护一个优先列表,有计划的回收区域垃圾,优先回收价值大的Region
Remembered Set来避免全堆扫描,保存Region之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用
步骤
初始标记
并发标记
最终标记
虚拟机将并发标记期间对象的变化记录在线程Remembered Set Logs里,合并到Remember Set中,需停顿,可并行
筛选回收
对Region的回收价值和成本进行排序,根据用户期待的GC停顿时间来制定回收计划

理解GC日志

垃圾收集器参数

内存分配与回收策略

​ 对象优先在Eden分配
​ 大对象直接进入老年代
​ 长期存活的对象将进入老年代
​ 动态对象年龄判断
​ 空间分配担保


虚拟机性能监控与故障处理工具

JDK的命令行工具

​ jps
​ JVM Process Status Tool,显示指定系统内所有的HotSpot虚拟机进程
​ jstat
​ JVM Statistics Monitoring Tool,用于收集HotSpot虚拟机各方面的运行数据
​ jinfo
​ Configuration Info for Java,显示虚拟机配置信息
​ jmap
​ Memory Map for Java,生成虚拟机的内存快照(heapdump文件),另外finalize执行队列、Java堆、永久代的详细信息
​ jhat
​ JVM Heap Dump Browser,用于分析headdump文件,建立一个HTTP/HTML服务器
​ jstack
​ Stack Trace for Java ,显示虚拟机的线程快照,主要目的是定位线程出现长时间停顿的原因,如线程间死锁、死循环、请求外部资源

JDK的可视化工具

JConsole

​ 概述 jps
​ 内存 jstat
​ 线程 jstack
​ 类
​ VM概要 jinfo
​ VMBean

VisualVM

​ 功能
​ 显示虚拟机进程以及进程的配置、环境信息(jps、jinfo)
​ 监视应用程序的CPU、GC、堆、方法区以及线程的信息(jstat、jstack)
​ dump以及分析堆转储快照(jmap、jhat)
​ 方法级的程序运行性能分析,找出被调用最多、运行时间最长的方法
​ 离线程序快照:收集程序的运行时配置、线程dump、内存dump等信息建立ige快照
​ …
​ 插件
​ BTrace动态日志跟踪


类文件结构

无关性的基石

​ 平台无关性
​ 操作系统的应用层面上,虚拟机载入和执行同一种平台无关的字节码
​ 语言无关系
​ Groovy
​ Scala
​ …

Class类文件的结构

​ 魔数与Class文件的版本
​ 常量池
​ 访问标志
​ 类索引、父类索引与接口索引集合
​ 字段表集合
​ 方法表集合
​ 属性表集合

字节码指令简介

​ 字节码与数据类型
​ 加载和存储指令
​ 运算指令
​ 类型转换指令
​ 对象创建与访问指令
​ 操作数栈管理指令
​ 控制转移指令
​ 方法调用和返回指令
​ 异常处理指令
​ 同步指令


虚拟机类加载机制

类加载的时机

必须初始化的情况
​ 遇见字节码指令
​ new
​ 使用new关键字实例化对象的时候
​ getstatic
​ putstatic
​ invokestatic
​ 读取或设置一个类的静态字段,调用一个类的静态方法的时候
​ 使用java.lang.reflect包对类进行反射调用的时候,触发其初始化
​ 初始一个类的时候,如果发现其父类没有进行初始化,对其父类进行初始化
​ 虚拟机启动时,用户需要指定一个要执行的主类,会先初始化这个主类
​ MethodHandle实例最后解析结果REF_(..)static的方法句柄,句柄对应的类没有初始化,需要先触发其初始化
类的生命周期
加载
连接
​ 验证
​ 准备
​ 解析
初始化
使用
卸载

类加载的过程

​ 加载
​ 1.通过一个类的全限定名来获取定义此类的二进制字节流
​ 从ZIP包获取
​ 从网络中获取
​ 运行时计算生成,动态代理
​ 其他文件,JSP
​ 从数据库中读取
​ 2.将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
​ 3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口
​ 连接
​ 验证
​ 1.文件格式
​ Class文件格式规范
​ 2.元数据
​ 对字节码的语义分析
​ 3.字节码
​ 通过数据流和控制流,语义是否合法和符合逻辑的
​ 4.符合引用
​ 将符号引用转化为直接引用的时候
​ 基于方法区的存储结构进行
​ 准备
​ 为类变量分配内存并设置类变量初始化值的阶段
​ 解析
​ 虚拟机将常量池内的符号引用替换为直接引用的过程
​ 定义解释
​ 符号引用
​ 与虚拟机布局无关,但是符号引用必须一致,因为符号引用的字面量形式明确定义在Java虚拟机规范Class文件格式汇总
​ 直接引用
​ 是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄,与虚拟机布局相关
​ 优化
​ 对第一次解析的结果进行缓存,在运行时常量池中记录直接引用,并把常量标识为已解析状态(除了invokedynamic)
​ 针对
​ 类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点
​ 初始化

类加载器

​ 类与类加载器
​ 类是否相等的比较,前提是这两个类由同一个类加载器加载的前提下才有意义
​ 双亲委派模型
​ 启动类加载器
​ 扩展类加载器
​ 应用程序类加载器
​ 破坏双亲委派模型
​ 第一次破坏
​ 原因:向前兼容
​ 解决方法:ClassLoader新增一个protected方法findClass
​ 第二次破坏
​ 原因:基础类想要调用SPI(Service Provider Interface)
​ 解决方法:线程上下文类加载器
​ 第三次破坏
​ 原因:追求程序的动态性,代码热替换,模块热部署
​ 解决方法:OSGI


虚拟机字节码执行引擎

运行时栈帧结构

​ 局部变量表
​ 是一组变量值存储空间,用于存放方法参数和方法内部定义的局部变量,Slot为最小单位
​ 局部变量表不像类变量,并不会赋默认值
​ 操作栈
​ LIFO栈
​ 动态连接
​ 每个栈帧都包含了一个指向运行时常量池中该栈帧所属方法的引用,持有这个引用是为了支持方法调用过程中的动态连接
​ 返回地址
​ 正常执行返回
​ 异常退出
​ …

方法调用

​ 唯一任务就是确定被调用方法的版本(即调用哪一个方法)
​ 解析
​ 字节码指令
​ invokestatic
​ 调用静态方法
​ invokespecial
​ 调用实例构造器方法、私有方法和父类方法
​ invokevirtual
​ 调用所有虚方法
​ invokeinterface
​ 调用接口方法,会在运行时再缺点一个实现此接口的对象
​ invokedynamic
​ 先在运行时动态解析出调用点限定符所引用的的方法,然后执行,分派逻辑是用户所设定的引导方法决定的
​ 非虚方法,除了这两个指令,还包含被final修饰的方法
​ 分派
​ 静态分派
​ 所有依赖静态类型来定位方法执行版本的分派动作称为静态分派
​ 动态分派
​ invokevirtual指令的运行时解析过程
​ 1.找到操作数栈顶的第一个元素所指向的对象的实际类型,记为C
​ 2.如果在类型C中找到与常量中的描述符和简单名称都相符的方法,则进行权限校验
​ 通过,则返回这个方法的直接引用,查找过程结束
​ 否则,则返回java.lang.IllegalAccessError异常
​ 3.否则,按照继承关系从下往上依次对C的各个父类进行第2步的搜索和验证过程
​ 4.如果始终没有找到合适的方法,则抛出java.lang.AbstractMethodError异常
​ 单分派与多分派
​ Java语言是一门静态多分派、动态单分派的语言
​ 虚拟机动态分派的实现
​ 虚方法表
​ 虚方法表中存放着各个方法的实际入口地址
​ 非稳定“激进”优化
​ 内联缓存
​ 基于“类型继承关系分析技术”的守护内联

动态类型语言支持

​ 动态类型语言
​ 关键特征是它的类型检查的主题过程是在运行期而不是编译期
​ JDK1.7与动态类型
​ 1.7之前,虚拟机实现动态类型语言的方式,留个占位符类型,运行时动态生成字节码实现具体类型到占位符类型的适配
​ java.lang.invoke包
​ 本质上
​ MethodHandle是在模拟字节码层次的方法调用
​ findStatic
​ invokestatic
​ findVirtual
​ invokevirtual
​ findSpecial
​ invokespecial
​ Reflection是在模拟Java代码层次的方法调用
​ 内容上
​ MethodHandle仅仅包含与执行方法相关的信息 轻量级
​ Reflection包含了方法的签名、描述符以及方法属性表中各种属性的Java端表示方式,执行权限等运行期信息 重量级
​ 性能上
​ MethodHandle 是对字节码的方法指令调用模拟,所以可以复用虚拟机的各种优化,例如方法内联等
​ Reflection 没有相应的优化
​ invokedynamic指令

基于栈的字节码解释执行引擎

​ 解释执行
​ 程序源码
​ 词法分析
​ 单词流
​ 语法分析
​ 抽象语法树
​ 指令流
​ 解释器
​ 解释执行
​ 优化器
​ 中间代码
​ 生成器
​ 目标代码
​ 步骤一: javac执行
​ 步骤二:解释执行
​ 步骤二:传统编译原理
​ 基于栈的指令集与基于寄存器的指令集
​ 基于栈的指令集
​ 优点
​ 可移植
​ 缺点
​ 性能慢
​ 基于寄存器的指令集

实例

​ Tomcat正统的类加载器架构
​ 需要满足的要求
​ 部署在同一个服务器上的两个Web应用程序所使用的Java类库可以实现相互隔离
​ 部署在同一个服务器上的两个Web应用程序所使用的Java类库可以相互共享
​ 服务器需要尽可能地保证自身的安全不受部署的Web应用程序影响
​ 支持JSP应用的Web服务器
​ Tomcat目录
​ /common目录
​ 类库可被Tomcat和所有的Web应用程序共同使用
​ /server目录
​ 类库可被Tomcat使用,对所有的Web应用程序都不可见
​ /shared目录
​ 类库可被所有的Web应用程序共同使用,但对Tomcat自身不可见
​ /WEB-INF目录
​ 类库仅对此Web应用程序使用
​ Tomcat 6.x版本 合并到一个lib目录
​ OSGi 灵活类加载器架构
​ Bundle
​ 声明它所依赖的Java Package(通过Import-Package描述)
​ 声明允许导出发布的Java Package(通过Export-Package描述)
​ 类加载的查找规则
​ 以java.*开头的类,委托给父类加载器加载
​ 否则,委托列表名单内的类,委托给父类加载器
​ 否则,Import列表中的类,委托给Export这个类的Bundle的类加载器假期
​ 否则,查找当前Bundle的Classpath,使用自己的类加载器加载
​ 否则,查找是否在在自己Fragment Bundle中,如果是,则委派给Fragment Bundle的类加载器
​ 否则,查找Dynamic Import列表的Bundle,委派给对应Bundle的类加载器
​ 否则,类查找失败
​ 提供强大功能的同时,也引入了额外的复杂度,带来了线程死锁和内存泄露的风险(bundle的相互依赖)
​ 字节码生成技术与动态代理的实现
​ 字节码生成技术
​ 字节码类库
​ Javassist
​ CGLib
​ ASM
​ javac
​ JSP、AOP、动态代理、反射
​ 动态代理
​ 代理类
​ 大致过程就是根据Class文件的格式规范去拼接字节码
​ Retrotranslator
​ 跨越JDK版本,具体实现是使用ASM对字节码修改
​ JDK每次升级新增功能分类
​ 编译器层面做的改进
​ 自动拆箱装箱
​ 对Java API代码增强
​ JDK1.2的Collections集合类,JDK1.5的concurrent并发包
​ 需要在字节码中进行支持的改动
​ 譬如,动态语言支持,invokedynamic字节码指令
​ 虚拟机内部的改进
​ JDK1.5实现JSR-133规范重新定义Java内存模型(JMM)
​ 只能模拟这两类
​ 实战:远程执行功能
​ 目标
​ 不依赖JDK版本
​ 不改变原有服务端程序的部署,不依赖任何第三方类库
​ 不侵入原有程序
​ “临时代码”需要直接支持java语言
​ “临时代码”需要具备足够的自由度,不需要依赖特定的类
​ “临时代码”的执行结果可以返回客户端
​ 问题及解决思路
​ 如何编译提交到服务器的Java代码
​ 使用tools.jar包
​ javac编译好,上传字节码(.class)文件到服务端
​ 如何执行编译之后的Java代码
​ 让类加载器加载这个类生成一个Class对象,然后反射调用一下某个方法
​ 如何收集Java代码的执行结果
​ 使用标准输出和标准错误输出获取打印的信息,会影响到原有程序,这里标准输出设备是虚拟机线程全局共享的资源
​ 使用自己定义的PrintStream对象,会把其他线程向标准输出中打印的信息也收集了
​ 直接在执行的类中把对System.out的符合引用替换为我们准备的PrintStream的符合引用


编译器优化

虚拟机团队把对性能的优化集中到了后端的即时编译器中,这样可以让那些不是由Javac产生的Class文件也同样能享受到编译器优化所带来的好处

Javac编译器

编译过程
准备过程:初始化插入式注解处理器
1.解析与填充符合表过程
1.词法分析、语法分析
词法分析
将源代码的字符流转变为标记(Token)集合
语法分析
是根据Token序列构造抽象语法树的过程
2.输入到符号表
符号表
是由一组符号地址和符合信息构成的表格(K-V)
2.插入式注解处理器的注解处理过程
注解处理器
可以把它看作是一组编译器的插件,可以读取、修改、添加抽象语法树中的任意元素
注意:每次处理完注解,如果由修改都会Round
3.分析与字节码生成过程
1.标注检查
检查的内容包括,变量使用前是否已被声明,变量与赋值之间的数据类型是否能够匹配等
2.数据流分析
检查程序局部变量在使用前是否有赋值、方法的每条路径是否都有返回值、是否所有的受查异常都被正确处理
3.解语法糖
泛型
变长参数
自动装箱/拆箱
4.生成字节码
把前面所生成的信息(语法符合表)转换为字节码写到磁盘中
少量代码添加和转换工作
实例构造器和类构造器
字符串的加操作替换为StringBuffer或builder的append操作
语义分析

Java语法糖

​ 泛型与类型擦除
​ C# 类型膨胀,真实泛型
​ Java 伪泛型
​ 自动拆箱/装箱、变长参数、泛型
​ 条件编译
​ 只能实现在语句基本块级别的条件编译,去除分支不成立的代码块

实战:插入式注解


运行期优化

问题

​ 为何HotSpot虚拟机要使用解释器与编译器并存的架构?
​ 为何HotSpot虚拟机要实现两个不同的即时编译器?
​ 程序合何时使用解释器执行?何时使用编译器执行?
​ 哪些程序代码会被编译为本地代码?如何编译为本地代码?
​ 如何从外部观察即时编译器的编译过程和编译结果?

HotSpot虚拟机内的即时编译器

​ 编译器与解释器
​ 解释器
​ Interperter
​ 编译器
​ Client Compiler
​ Server Compiler
​ 编译对象与触发条件(Client即时编译方式)
​ 热点代码
​ 被多次掉调用的方法
​ 被多次执行的循环体(OSR编译,栈上替换)
​ 热点探测
​ 基于采样的热点探测
​ 基于计数器的热点探测(HotSpot)
​ 方法调用
​ 回边计数器
​ 热度衰减
​ 当超过一定的时间限度,如果方法的调用次数仍然不足以让它提交给即时编译器编译,那这个方法的调用计数器就会被减少一半
​ 编译过程
​ Client Compiler
​ 第一阶段
​ 字节码
​ 方法内联、常量传播、其他优化…
​ HIR(SSA形式)
​ 第二阶段
​ 空值检查消除、范围检查消除、其他优化…
​ 优化后的HIR
​ HIR到LIR的转换
​ 第三阶段
​ 使用线性扫描算法在LIR上分配寄存器
​ 在LIR上做窥孔优化
​ 生成机器码
​ Server Compiler
​ 它还会执行所有经典的优化动作
​ 无用代码消除
​ 循环展开
​ 循环表达式外提
​ 常量传播
​ 消除公共子表达式
​ 基本块重排序
​ …
​ 还会实施一些与Java语言特性密切相关的优化技术
​ 范围检查消除
​ 空值检查消除
​ …
​ 根据Cllient Compiler提供的性能监控信息,进行一些不稳定的激进优化
​ 守护内联
​ 分支频率预测
​ …
​ 查看及分析即时编译结果

编译优化技术

​ 优化技术概览
​ 编译器策略
​ 延迟编译
​ 分层编译
​ 栈上替换
​ 延迟优化
​ 程序依赖图表示
​ 静态单赋值表示
​ 基于性能监控的优化技术
​ 乐观空值断言
​ 乐观类型断言
​ 乐观类型增强
​ 乐观数组长度增强
​ 裁剪未被选择的分支
​ 乐观的多态内联
​ 多支频率预测
​ 调用频率预测
​ 基于证据的优化技术
​ 精确类型判断
​ 内存值推断
​ 内存值跟踪
​ 常量折叠
​ 重组
​ 操作符退化
​ 空值检查消除
​ 类型检测退化
​ 类型检测消除
​ 代数化简
​ 公共子表达式消除
​ 数据流敏感重写
​ 条件常量传播
​ 基于流承载的类型缩减转换
​ 无用代码消除
​ 语言相关的优化技术
​ 类型继承关系分析
​ 去虚拟化
​ 符合常量传播
​ 自动装箱消除
​ 逃逸分析
​ 锁消除
​ 锁膨胀
​ 消除反射
​ 内存及代码位置变换
​ 表达式提升
​ 表达式下沉
​ 冗余存储消除
​ 相邻存储合并
​ 交汇点分离
​ 循环变换
​ 循环展开
​ 循环剥离
​ 安全点消除
​ 迭代范围分离
​ 范围检查消除
​ 循环向量化
​ 全局代码调整
​ 内联
​ 全局代码外提
​ 基于热度的代码布局
​ Switch调整
​ 控制流图变换
​ 本地编码编排
​ 本地代码封包
​ 延迟槽填充
​ 着色图寄存器分配
​ 线性扫描寄存器分配
​ 复写聚合
​ 常量分裂
​ 复写移除
​ 地址模式匹配
​ 指令窥孔优化
​ 基于确定有限状态机的代码生成

与C++编译器对比

​ 劣势
​ 1.即时编译器运行占用用户程序的运行时间,它能提供的优化手段受制于编译成本
​ 2.Java语言是动态的类型安全语言,需要由虚拟机来确保程序不会违反语言语义或访问非结构化内存
​ 3.Java语言没有virtual关键字,进行优化时的难度大于C/C++
​ 4.Java语言是动态扩展的语言,运行时加载新的类可能改变程序类型的继承关系,这使得对一些全局优化只能使用激进的手段
​ 5.Java内存都是在堆上分配的,只有方法中的局部变量才能在栈上分配,而C/C++的对象由多种内存分配方式

优势
动态安全、动态扩展、垃圾回收


Java内存模型与线程

Java内存模型(JMM)

主内存与工作内存

​ 主内存
​ 所有变量都存储在主内存
​ 工作内存
​ 每条线程都有一个工作内存,保存了该线程使用到的变量的主内存副本拷贝

内存间交互操作

​ lock
​ 它把一个变量标识为一条线程独占的状态
​ unlock
​ 它把一个处于锁定状态的变量释放出来
​ read
​ 它把一个变量的值从主内存传输到线程的工作内存中
​ write
​ 它把store操作从工作内存中得到的变量的值放入主内存变量
​ load
​ 它把read操作从内存中得到的变量值放入工作内存的变量副本中
​ use
​ 它把工作内存中一个变量的值传递给执行引擎,每当虚拟机遇到一个需要使用到变量的值的字节码指令时将会执行这个操作
​ assign
​ 它把一个从执行引擎接收到的值赋给工作内存的变量,每当虚拟机遇到一个给变量赋值的字节码指令时执行这个操作
​ store
​ 它把工作内存中一个变量的值传送到主内存中,以便write使用
​ 作用于主内存
​ 作用于工作内存

约束

​ 不允许read和load、store和write操作之一单独出现
​ 不允许一个线程丢弃它的最近assign操作
​ 不允许一个线程无原因地把数据从线程的工作内存同步回主内存
​ 一个新的变量只能在主内存中“诞生,use和store操作之前,必须先assign和load
​ 一个变量在同一时刻只允许一条线程对其进行lock操作,但是同一条线程可以多次lock
​ 如果一个变量执行lock操作,那么清空工作内存中此变量的值,执行引擎使用这个变量前,需要重新load或assign
​ 一个变量没有被lock,就不允许被unlock,另外不允许unlock其他线程
​ 一个变量执行unlock操作之前,必须先把此变量同步回主内存中(store、write)

volatile

​ 特征
​ 1.保证此变量对所有线程的可见性
​ 2.禁止指令重排
​ 仍需要加锁的场景
​ 运算结果并不依赖变量当前值
​ 变量不需要与其他的状态变量共同参与不变约束

特征

​ 原子性
​ synchronized块保证原子性
​ 可见性
​ volatile可见性,synchronized和final
​ 有序性
​ volatile禁止指令重排,synchronized保证同一个锁两个同步块只能串行

先行发生原则 happen-before

​ 程序次序规则
​ 管程锁定规则
​ volatile
​ 线程启动规则
​ 线程终止规则
​ 线程中断规则
​ 对象终结规则
​ 传递性

Java与线程

线程

​ 1.使用内核线程实现
​ 2.使用用户线程实现
​ 3.使用用户线程加轻量级进程混合实现
​ 4.java线程的实现

Java线程调度

​ 协同式线程调度
​ 抢占式线程调度
​ 线程优先级(不确定性)

状态转换

​ 新建(New)
​ 运行(Runable)
​ Running
​ Ready
​ 无限期等待 (Wating)
​ 限期等待(Timed Wating)
​ 阻塞(Blocked)
​ 结束(Terminated)


线程安全与锁优化

线程安全

安全程度

​ 1.不可变
​ final
​ String
​ 枚举类型
​ …
​ 2.绝对线程安全
​ 不管运行时环境如何,调用者都不需要任何额外的同步措施
​ 如Vector就不是绝对线程安全
​ 3.相对线程安全
​ 我们通常意义上所讲的线程安全
​ 4.线程兼容
​ 不是线程安全的,ArrayList、HashMap
​ 5.线程对立
​ 死锁

线程安全的实现方法

1.互斥同步(悲观)

​ 保证共享数据在同一个时刻只被一个线程使用
​ 手段
​ 临界区
​ 互斥量
​ 信号量
​ synchronized
​ 阻塞唤醒,需要操作系统来帮忙,所以需要从用户态切换到核心态
​ 虚拟机优化自旋锁,自旋等待
​ ReentrantLock重入锁
​ 等待可中断
​ 当持有锁的线程长期不释放锁,正在等待的线程可以选择放弃等待,改为处理其他事情
​ 可实现公平锁
​ 多个线程在等待同一个锁的时候,按照申请锁的时间顺序来依次获得锁
​ 锁可以绑定多个对象
​ 可以同时绑定多个Condition对象

2.非阻塞同步(乐观)

​ 指令
​ Test-and-Set
​ Fetch-and-Increment
​ Swap
​ Compare-and-Swap(CAS)
​ 内存位置
​ 旧的预期值
​ 新值
​ Load-Linked/Store-Conditional(LL/SC)
​ 20世纪已存在大多数

3.无同步方案

​ 可重入代码
​ 只要输入相同的数据,都能返回相同的结果
​ 线程本地存储
​ 共享数据限制在一个线程内

锁优化

​ 自旋锁与自适应锁
​ 锁消除
​ 锁粗化
​ 轻量级锁
​ CAS操作
​ 偏向锁
​ 当另外一个线程去尝试获取这个锁的时候,偏向模式就宣告结束


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可以在下面评论区评论,也可以邮件至 951488791@qq.com

文章标题:Note_深入理解java虚拟机

字数:7.9k

本文作者:zhengyumin

发布时间:2019-06-05, 21:12:25

最后更新:2019-08-12, 10:39:53

原始链接:http://zyumin.github.io/2019/06/05/Note_%E6%B7%B1%E5%85%A5%E7%90%86%E8%A7%A3java%E8%99%9A%E6%8B%9F%E6%9C%BA/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。