jvm 第3版
2020-11-23 10:58:44 27 举报
AI智能生成
java虚拟机第三版
作者其他创作
大纲/内容
二、自动内存管理
第2章 内存区域
2.2 运行时数据区域
1 程序计数器
分支、循环、跳转、异常处理、线程恢复等
如果执行的是java方法,则记录的是字节码指令的地址,如果是native方法则为空
2 Java虚拟机栈
每个方法一个栈:局部变量表、操作数栈、动态链接、方法出口等
局部变量表:基本数据类型、对象引用、returnAddress类型(变量槽 Slot)
3 本地方法栈
跟上面的栈非常类似,上面是为java方法服务,而这个是为Native方法服务
4 Java堆
5 方法区
类型信息、常量、静态变量、即时编译器编译的代码缓存等
永久代:HotSpot用“永久代”来实现方法区,JDK8废弃,使用元空间
6 运行时常量池
是方法区的一部分,存放字面量和符号引用
具备动态性,运行期间也可以将新的常量放入其中
7 直接内存
2.3 HotSpot虚拟机对象
1 创建对象
1.new指令,检查指令的参数是否能在常量池中定位到一个类的符号引用,并且该类是否被加载
2.加载后为新对象分配内存,使用“指针碰撞”还是“空闲列表”就看垃圾回收器是否会整理内存
3.分配内存线程安全问题:一种是CAS同步处理,一种是本地线程分配缓冲TLAB
4.生成对象头:类的实例、类的元数据信息、哈希码、对象GC分代年龄等
5.执行init方法,按照程序员的意愿进行初始化
2 对象的内存布局
对象在内存中的存储布局分为三个部分
对象头
1.运行时数据
哈希码、GC分代年龄、锁状态标志、线程持有的锁等
2.类型指针
对象指向其元数据的指针,虚拟机通过它来确定对象所属的类
实例数据
是对象真正存储的有效信息
定义字段的默认存储顺序:longs/doubles、ints、shorts、chars、bytes/booleans、oops
相同宽度的字段总是被分配到一起,so 父类定义的变量会在子类之前
对齐填充
没有特别的含义,仅仅是占位符
3 对象的访问定位
Java程序通过栈上的reference数据来操作堆上的具体对象,reference有两种方式
1.句柄
2.直接指针
4 OOM实战
1 堆溢出
内存泄漏
memory leak
收不回
占着茅坑不拉粑粑
内存使用后不归还,GC无法回收
分类
常发性内存泄漏
偶发性内存泄漏
一次性内存泄漏
隐式内存泄漏
举例
List对象一直往里面加数据,但是一直不释放
内存溢出
out of memory
给不起
申请内存时,内存不够了
常见原因
内存中加载的数据量过于庞大,如一次从数据库取出过多数据
集合类中有对对象的引用,使用完后未清空,使得JVM不能回收
代码中存在死循环或循环产生过多重复的对象实体
使用的第三方软件中的BUG
启动参数内存值设定的过小
解决办法
修改JVM启动参数,直接增加内存
检查错误日志,查看“OutOfMemory”错误前是否有其它异常或错误
对代码进行走查和分析,找出可能发生内存溢出的位置
检查是否有一次获取全部数据的查询
是否有死循环或者递归
是否有大循环重复产生新对象
检查List、Map等集合使用后是否清除
使用内存查看工具动态查看内存使用情况
2 栈溢出
3 方法区和运行时常量池溢出
4 直接内存溢出
第3章 垃圾回收器和内存分配策略
1.垃圾回收主要针对方法区和堆
2.对象是否存活
可达性分析(GC Roots)
栈中引用的对象:参数、局部变量、临时变量
方法区中的静态变量、常量
栈中Native方法引用的对象
虚拟机的内部引用:基本数据类型的class对象、异常对象、系统类加载器
被同步锁(synchronized)持有的对象
引用类型
强引用
永不会被回收
软引用
内存溢出前会二次回收
弱引用
一定会回收
虚引用
非真实引用,只是一个标记,用来发系统通知
3.垃圾回收算法
分代收集
Young Generation
Old Generation
跨代引用:存在互相引用关系的两个对象,是应该倾
向于同时生存或者同时消亡的
向于同时生存或者同时消亡的
只需要在新生代建立一个全局的数据结构:记忆集
记忆集将老年代划分为若干小块
当Minor GC的时候,只需要扫描包含了跨代引用的小块内存,而不是整个老年代
部分收集
新生代收集
只是新生代的垃圾收集
老年代收集
只是老年代的垃圾收集
混合收集
新生代和部分老年代的垃圾收集
整堆收集
堆和方法区的垃圾收集
标记-清除算法(最早最基础)
CMS收集器
标记-复制算法(简称复制算法)
Eden、Survivor
标记-整理算法
Parallel、Scavenge收集器
4.算法细节实现
根节点枚举
迄今为止,所有收集器在这步都必须暂停用户线程
HotSpot使用OopMap的数据结构能直接得到哪些地方存放着对象
从而快速完成枚举,而不是每个根节点都去检查一遍
从而快速完成枚举,而不是每个根节点都去检查一遍
安全点
只在安全点上生成OopMap,而不是所有在所有修改OopMap的指令上生成
因为这样的指令太多了,维护成本很高
因为这样的指令太多了,维护成本很高
选择标准:是否具有让程序长时间执行的特征
例如方法调用、循环跳转、异常跳转等
主动式中断
线程轮询标志位
安全区域
针对“不执行”的程序,例如sleep状态的线程
记忆集和卡表
记忆集是一个抽象的数据结构,卡表是其实现之一
就好比Map 和 HashMap
就好比Map 和 HashMap
卡表,HotSpot是用数组实现的
卡表的每一个元素都对应一个卡页
只要卡页中有一个对象存在跨代指针,就将卡表的值标为1(变脏)
GC Roots扫描的时候,就可以轻松的找到包含跨代指针的内存了
写屏障(维护卡表)
其他分代区域中对象引用了本区域对象,就应该维护卡表
写屏障:“引用类型字段赋值”这个动作的AOP切面
写前屏障
写后屏障
高并发时的“伪共享”
判断卡表未被标记时才将其变脏
增加了一次判断,解决了“伪共享问题”
并发的可达性分析
对象按照“是否被访问过”标记3种颜色
白色
未被垃圾收集器访问过
若分析结束仍然是白色,即代表不可达
黑色
已被垃圾收集器访问过
对象的所有引用都已经扫描过,是安全存活的
不可能直接指向白色对象
灰色
已被垃圾收集器访问过
对象上至少存在一个引用还没有被扫描过
可达性分析的扫描过程
收集器在“对象图”上标记颜色
同时,用户线程在修改引用关系
导致两个后果
同时,用户线程在修改引用关系
导致两个后果
1.对象存活:把原本消亡的对象错误标记为存活
可以忍
2.对象消亡:把原本存活的对象错误标记为消亡
不能忍
Wilson定理
产生对象消亡问题的两个充分必要条件
1.赋值器插入了一条或多条从黑色对象到白色对象的新引用
2.赋值器删除了全部从灰色对象到该白色对象的直接或间接引用
打破“对象消亡”
只需要破坏上面两个条件之一就可以了
增量更新
破坏了第一个条件
黑色对象引入指向白色对象的引用时,记录此引用,并发扫描结束后
再以记录过的引用中的黑色对象为根,重新扫描一次
再以记录过的引用中的黑色对象为根,重新扫描一次
黑色对象一旦新插入了指向白色对象的引用,就变回灰色对象了
原始快照
破坏了第二个条件
灰色对象要删除指向白色对象的引用时,记录此引用,并发扫描结束后
再以记录过的引用中的灰色对象为根,重新扫描一次
再以记录过的引用中的灰色对象为根,重新扫描一次
无论引用关系是否删除,都会按照刚刚开始扫描的那一刻的对象图进行搜索
5.垃圾回收器
1.Serial收集器
目前为止,所有的收集器仍然需要stop the world
最老的,最基础的收集器,单线程回收
仍是HotSpot客户端模式下默认新生代收集器,因为它简单且高效
2.ParNew收集器
是Serial收集器的多线程并行版本
由于它只能配置CMS使用,在CMS辉煌的时候它也如日中天
但是随着G1收集器的出现,它被合并到了CMS中,已经退出历史舞台
但是随着G1收集器的出现,它被合并到了CMS中,已经退出历史舞台
3.Parallel Scavenge收集器
目标则是达到一个可控制的吞吐量
吞吐量
运行用户代码时间/(运行用户代码时间 + 垃圾收集时间)
用户代码加上垃圾收集总耗时100分钟,其中垃圾收集
花掉1分钟,那吞吐量就是99%
花掉1分钟,那吞吐量就是99%
动态调整
参数-XX:+UseAdaptiveSizePolicy
自动分配新生代大小、Eden与Survivor区的比例等
4.Serial Old收集器
老年代的收集器
作为CMS收集器发生失败时的后备预案
5.Parallel Old收集器
老年代收集器
在注重吞吐量或者处理器资源较为稀缺的场合,可以优先考虑
Parallel Scavenge加Parallel Old收集器这个组
Parallel Scavenge加Parallel Old收集器这个组
6.CMS收集器
以获取最短回收停顿时间为目标的收集器
1)初始标记(CMS initial mark)只是标记GC Roots能直接关联到的对象,速度快,停顿短
2)并发标记(CMS concurrent mark)从GC Roots的直接关联对象开始遍历整个对象图
3)重新标记(CMS remark)为了修正并发标记期间变动的标记,停顿时间稍长
4)并发清除(CMS concurrent sweep)删除掉标记阶段判断的已经死亡的对象
2)并发标记(CMS concurrent mark)从GC Roots的直接关联对象开始遍历整个对象图
3)重新标记(CMS remark)为了修正并发标记期间变动的标记,停顿时间稍长
4)并发清除(CMS concurrent sweep)删除掉标记阶段判断的已经死亡的对象
缺点
1.对处理器资源敏感
在处理器核心数不足4时,就会过多占用CPU,对用户程序影响比较大
2.无法处理浮动垃圾
3.会产生大量的空间碎片
有参数可以在full gc时整理碎片,但是停顿时间会边长
7.Garbage First收集器
里程碑:它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式
到JDK 8 Update 40的时候的G1收集器才被官方称为“全功能的垃圾收集器”
把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以
根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间
后台维护一个优先级列表,每次根据收集停顿时间,
优先处理回收价值收益最大的那些Region
优先处理回收价值收益最大的那些Region
缺点
1.跨Region引用对象问题
每个Region维护一个记忆集,实现“双向”卡表结构
由于Region数量很多,G1要耗费堆内存的10-20%来维护
由于Region数量很多,G1要耗费堆内存的10-20%来维护
2.收集线程与用户线程互不干扰问题
这个问题的首要问题是,确保对象图结构不被打破
使用“原始快照”算法来保证对象图结构不被修改
使用“原始快照”算法来保证对象图结构不被修改
3.建立可靠的停顿预测模型问题
衰减均值(Decaying Average)
Region的统计状态越新越能决定其回收的价值
Region的统计状态越新越能决定其回收的价值
4.要维护记忆集需要的内存开销大
5.写屏障过多
执行过程
初始标记(Initial Marking):标记一下GC Roots能直接关联到的对象,速度快,停顿短
并发标记(Concurrent Marking):从GC Root开始对堆中对象进行可达性分析
最终标记(Final Marking):短暂的暂停,用于处理并发阶段结束后仍遗留下来的SATB记录
筛选回收(Live Data Counting and Evacuation):对各个Region的回
收价值和成本进行排序,并制定回收计划,必须暂停用户线程
收价值和成本进行排序,并制定回收计划,必须暂停用户线程
与CMS对比
1.G1不会有内存碎片
2.G1占用的内存比CMS多
3.G1的写屏障的消耗比CMS大
4.小内存(4G以内)CMS的表现基本上会优于G1
6G以上基本上是G1更好
6G以上基本上是G1更好
8.Shenandoah收集器
第一款不由Oracle(或Sun)公司的虚拟机团队所领导开发的HotSpot垃圾收集器
是一款只有OpenJDK才会包含,而OracleJDK里反而不存在的收集器
执行过程
初始标记(Initial Marking):与G1一样
并发标记(Concurrent Marking):与G1一样
最终标记(Final Marking):与G1一样
并发清理(Concurrent Cleanup):清理那些整个区域内连一个存活对象都没有的Region
并发回收(Concurrent Evacuation):(与其他收集器的核心差异)把存活的对象复制到未使用的Region中
初始引用更新(Initial Update Reference):把堆中所有指向旧对象的引用修正到复制后的新地址
并发引用更新(Concurrent Update Reference):真正开始进行引用更新操作
最终引用更新(Final Update Reference):把GC Roots中所有指向旧对象的引用修正到复制后的新地址
并发清理(Concurrent Cleanup):整个回收集中所有的Region就没有存活对象了,将这些空间回收
Brooks Pointer
在原有对象布局结构的最前面统一增加一个
新的引用字段,在正常情况下,该引用指向对象自己
新的引用字段,在正常情况下,该引用指向对象自己
9.ZGC收集器
如果说Shenandoah是G1的继承者
那么ZGC更像是PGC和C4收集器的同胞兄弟
那么ZGC更像是PGC和C4收集器的同胞兄弟
Region的容量
小型Region(Small Region):容量固定为2MB,用于放置小于256KB的小对象
中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象
大型Region(Large Region):容量不固定,可以动态变化,用于放置4MB或以上的大对象
中型Region(Medium Region):容量固定为32MB,用于放置大于等于256KB但小于4MB的对象
大型Region(Large Region):容量不固定,可以动态变化,用于放置4MB或以上的大对象
染色指针技术
把标记信息记在引用对象的指针上
这时,与其说可达性分析是遍历对象图来标记对象,
还不如说是遍历“引用图”来标记“引用”了
还不如说是遍历“引用图”来标记“引用”了
Linux下64位指针的高18位不能用来寻址,剩余的46位指针能支持64TB内存
将46位中的高4位用来做四个标志信息,用来实现三色标记
于是ZGC最大只有4TB的内存,不能支持32位平台,不能支持压缩指针
将46位中的高4位用来做四个标志信息,用来实现三色标记
于是ZGC最大只有4TB的内存,不能支持32位平台,不能支持压缩指针
三大优势
1.一旦某个Region中的存活对象被移走之后就能立即释放
不用等所有指向该Region的引用都被修正才释放
不用等所有指向该Region的引用都被修正才释放
Shenandoah需要等到引用更新阶段结束以后才能释放Region
一个极端情况:如果堆中几乎所有对象都存活,需要1:1复制对象到新Region的话
就必须要有一半的空闲Region才能完成收集
一个极端情况:如果堆中几乎所有对象都存活,需要1:1复制对象到新Region的话
就必须要有一半的空闲Region才能完成收集
2.可以大幅减少在垃圾收集过程中内存屏障的使用数量
设置内存屏障是为了记录对象引用的变动情况
将这些信息直接维护在指针中,省去维护工作,对效率大有裨益
将这些信息直接维护在指针中,省去维护工作,对效率大有裨益
3.标记信息可以用来记录更多与对象标记、重定位过程相关的数据
以便日后进一步提高性能
以便日后进一步提高性能
执行过程
初始标记(Initial Marking):与G1、Shenandoah一样
并发标记(Concurrent Mark):与G1、Shenandoah一样
可达性分析阶段
短暂停顿
标记是在指针上进行的,标记的是Marked 0、Marked 1标志位
并发预备重分配(Concurrent Prepare for Relocate):确定重分配集
重分配集决定了里面的存活对象会被复制到其他的Region中
里面的Region会被释放,这个扫描是针对所有Region的,跟G1不同
里面的Region会被释放,这个扫描是针对所有Region的,跟G1不同
并发重分配(Concurrent Relocate):重分配是ZGC执行过程中的核心阶段
把重分配集中的存活对象复制到新的Region上
得益于染色指针的支持,ZGC收集器能仅从引用上就明
确得知一个对象是否处于重分配集之中
“自愈”
通过内存屏障截获对象引用的变更,处理并发问题
“转发表”记录了对象的转向关系,在第一次访问旧对象时
通过转发表修改引用的值,使其指向新对象就是“自愈”
“转发表”记录了对象的转向关系,在第一次访问旧对象时
通过转发表修改引用的值,使其指向新对象就是“自愈”
并发重映射(Concurrent Remap):修正指向重分配集中旧对象的所引用
这一步不迫切,因为引用可以“自愈”
之所以要做是为了不让所有引用都去自愈导致系统变慢
ZGC巧妙的把这一步放在下一次的“并发标记”阶段
因为总是要遍历所有对象的,可以节省遍历的开销
因为总是要遍历所有对象的,可以节省遍历的开销
几乎整个收集过程都全程可并发,短暂停顿也只与GC Roots大小相关而与堆内存大小无关
对比G1和Shenandoah
G1的问题在于
1.维护记忆集的内存开销大
2.写屏障的过多使用给程序造成额外负担
1.维护记忆集的内存开销大
2.写屏障的过多使用给程序造成额外负担
ZGC没有记忆集,暂时也没有分代
也不需要记录跨代引用的卡表
所以也没有用到写屏障
也不需要记录跨代引用的卡表
所以也没有用到写屏障
ZGC没有分代收集,所以如果对象持续高速创建,
就会产生大量的浮动垃圾,堆中可用的空间会越来越小
就会产生大量的浮动垃圾,堆中可用的空间会越来越小
C4实现了分代收集,效率比PGC提升十倍
ZGC还有个优点:支持“NUMA-Aware”的内存分配
10.Epsilon收集器
是个不能够进行垃圾收集的垃圾收集器
如果应用只要运行数分钟甚至数秒,
只要Java虚拟机能正确分配内存,那它就是个很好的选择
只要Java虚拟机能正确分配内存,那它就是个很好的选择
6.如何选择垃圾收集器
不差钱又没经验——C4
掌控的了软硬件型号——ZGC
应用必须运行在Windows下——Shenandoah
遗留系统,软硬件都比较落后
1.内存 4-6G 的选 CMS
2.内存更大的选 G1
1.内存 4-6G 的选 CMS
2.内存更大的选 G1
第4章 虚拟机性能监控、故障处理工具
1.基础故障处理工具
jps:虚拟机进程状况工具
jps [ options ] [ hostid ]
jps -l
options
-q
hostid
RMI注册表中注册的主机名
使用频率最高的工具,用来查询LVMID
jstat:虚拟机统计信息监视工具
类加载、内存、垃圾收集、即时编译等运行时数据
jstat [ option vmid [interval[s|ms] [count]] ]
jstat -gc 2764 250 20
jstat -gcutil 2764
jinfo:Java配置信息工具
实时查看和调整虚拟机各项参数
jinfo [ option ] pid
jinfo -flag CMSInitiatingOccupancyFraction 1444
jmap:Java内存映像工具
用于生成堆转储快照
jmap [ option ] vmid
jmap -dump:format=b,file=eclipse.bin 3500
jstack:Java堆栈跟踪工具
用于生成虚拟机当前时刻的线程快照
jstack [ option ] vmid
jstack -l 3500
2.可视化故障处理工具
JHSDB:基于服务性代理的调试工具
JConsole:Java监视与管理控制台
自带
VisualVM:多合-故障处理工具
插件
生成、浏览堆转储快照
分析程序性能
BTrace动态日志跟踪
Java Mission Control:可持续在线的监控工具
3.HotSpot虚拟机插件及工具
HSDIS:JIT生成代码反汇编
第5章 调优案例分析与实战
1.大内存硬件上的程序部署策略
现象:从32位1.5G迁移到64位12G,反而更慢了
原因:由于程序存在很多大对象直接分配到老年代的情况,导致full gc频繁
而且由于老年代内存有12G,一次full gc需要14秒
而且由于老年代内存有12G,一次full gc需要14秒
解决:把JDK做成集群,5个JDK,每个2G内存,改用CMS收集器
2.堆外内存导致的溢出错误
现象:32位考试系统,考试时,经常内存溢出,将内存调整到1.6G后反而更严重
原因:总共2G内存,java堆就用了1.6G,那么直接内存就很小了
程序中又存在很多NIO需要用到直接内存,就导致了内存溢出
程序中又存在很多NIO需要用到直接内存,就导致了内存溢出
解决:调整java堆内存为1G
3.服务器虚拟机进程崩溃
现象:程序运行一段时间后,虚拟机进程自动关闭并抛出一个错误
远端断开连接的异常 java.net.SocketException: Connection reset
远端断开连接的异常 java.net.SocketException: Connection reset
原因:异步调用另一个服务,需要3分钟才能得到返回值,
时间越长就累积了越多Web服务没有调用完成,导致在等待的线程和Socket连接越来越多
最终超过虚拟机的承受能力后导致虚拟机进程崩溃
时间越长就累积了越多Web服务没有调用完成,导致在等待的线程和Socket连接越来越多
最终超过虚拟机的承受能力后导致虚拟机进程崩溃
解决:改为生产者/消费者模式的消息队列实现
4.不恰当数据结构导致内存占用过大
现象:每10分钟加载一个80M的文件时,系统就很卡
原因:ParNew加 CMS的收集器组合,在处理80M文件时,程序用的是hashmap
产生了800M的数据,显然这个数据结构有问题,日志显示,Eden区在gc的时候利用率是100%
对象没有“朝生夕死”,导致复制算法速度承受了很大的负担
产生了800M的数据,显然这个数据结构有问题,日志显示,Eden区在gc的时候利用率是100%
对象没有“朝生夕死”,导致复制算法速度承受了很大的负担
解决:从数据结构上着手解决,把hashmap中的无效数据剔除
5.由安全点导致长时间停顿
现象:HBase集群,运行在JDK 8上,使用G1收集器,一段时间后发现gc停顿时间在3秒以上
日志:[Times: user=1.51 sys=0.67, real=0.14 secs]
日志:[Times: user=1.51 sys=0.67, real=0.14 secs]
原因:有两个线程特别慢,其他走进安全点的线程都必须自旋等待这两个线程
而这两个线程慢的原因是他们处于一个int类型的for循环中,int循环被称为可数循环
虚拟机设置安全点的原则是“是否具有让程序长时间执行的特征”,而可数循环被认为
执行时间不会太长,不会设置安全点,long型的不可数循环则会被设置安全点
而这两个线程慢的原因是他们处于一个int类型的for循环中,int循环被称为可数循环
虚拟机设置安全点的原则是“是否具有让程序长时间执行的特征”,而可数循环被认为
执行时间不会太长,不会设置安全点,long型的不可数循环则会被设置安全点
解决:将循环的int改为long,让虚拟机为那段程序设置安全点
四、程序编译与优化代码
第10章 前端编译与优化
第11章 后端编译与优化
一、走近Java
1.技术体系
JDK(最小开发环境)
Java语言
Java虚拟机
Java类库
JRE(标准运行环境)
Java类库的一部分
Java虚拟机
2.发展史
1991.4
James Gosling的绿色计划,Java的前身Oak
1995.5.23
Oak改名为Java,并发布1.0版本
write once, run anywhere
1996.1.23
JDK1.0发布
Java虚拟机、Applet、AWT等
1996.5
首届JavaOne大会
1997.2.19
Sun发布了JDK1.1
JAR、JDBC、JavaBean、RMI、内部类、反射
1998.12.4
JDK1.2发布
拆分出了:J2SE、J2EE、J2ME
1999.4.27
HotSpot诞生
由一家小公司开发,被Sun收购,1.3后上位
2000.5.8
JDK1.3发布
主要改进是在Java类库上,新增了很多API
2002.2.13
JDK1.4发布
正则表达式、NIO、日志类、XML解析器等
2004.9.30
JDK5发布
自动装箱、泛型、枚举、可变长参数、遍历循环等
2006.12.11
JDK6发布
支持动态语言、提供编译期注解、HTTP服务器API等
虚拟机改进了:锁与同步、垃圾收集、类加载等
虚拟机改进了:锁与同步、垃圾收集、类加载等
由于2006年Sun开源了Java,并且Oracle收购等原因
导致6的生命周期很长,一共发布了211个补丁,直到2018.10.18
导致6的生命周期很长,一共发布了211个补丁,直到2018.10.18
2009.2.19
JDK7完成了第一个里程碑版本,但是按照计划一共有10个
Sun公司在技术竞争和商业竞争中陷入泥潭
无力推动JDK7的研发,Oracle收购后也阉割了7的功能
无力推动JDK7的研发,Oracle收购后也阉割了7的功能
2009.4.20
Oracle用74亿收购了曾经2000亿市值Sun
完成收购后,Oracle就拥有了三大商用虚拟机中的两个:JRockit、HotSpot
2011.7.28
JDK7发布
原计划2010.9
原计划2010.9
新的G1收集器(直到2014.4才商用)、可并行的类加载架构等
Oracle接手后体现出了极具商业化的风格,对Java SE产品线,
定义了一套新的Java SE Support商业产品,JDK7计划维护到2022年
定义了一套新的Java SE Support商业产品,JDK7计划维护到2022年
2014.3.18
JDK8发布
原计划2013.9
原计划2013.9
新功能包括:lambda、新的时间API、移除HotSpot永久代等
B计划
Jigsaw模块化延期到JDK9
天字一号大坑
微软的DLL、Java的JAR、.NET的Assembly,
工程做大后都无一例外陷入模块化地狱
工程做大后都无一例外陷入模块化地狱
Jigsaw面临的更大困难是大厂之间的竞争
IBM等13家企业反对Jigsaw,因为IBM的模块化做的最好
它不光实现了高度模块化,还成立了OSGi联盟,制定了模块化的标准
so,它想把OSGi推到Java规范里,不想被Jigsaw干掉
它不光实现了高度模块化,还成立了OSGi联盟,制定了模块化的标准
so,它想把OSGi推到Java规范里,不想被Jigsaw干掉
Oracle没有丝毫退让,并发公开信表示将废弃JSR专家组,独立发展带Jigsaw的Java版本
Java顿时面临Python2与Python3那样的分裂危机
Java顿时面临Python2与Python3那样的分裂危机
最后经过6轮投票,Java没有分裂,JDK9带着Jigsaw发布了
2017.9.21
JDK9发布
原计划2016年
2017年又两次跳票
原计划2016年
2017年又两次跳票
整顿了日志系统、支持HTTP2等
Oracle宣布每年3、9月发一个版本,避免交付风险
程序员吐槽“新版本还没用就过时了”
程序员吐槽“新版本还没用就过时了”
从此以后,每6个版本中才会出现一个长期支持版
否则就只有6个月的寿命
JDK8和JDK11是LTS版本,下一个是2021年的JDK17
否则就只有6个月的寿命
JDK8和JDK11是LTS版本,下一个是2021年的JDK17
2018.3.20
JDK10发布
主要是内部重构(连JDK都重构了)
2018.3.27
Android的Java侵权案,Google赔偿Oracle88亿(等于免费买的Sun)
收购后立刻用Sun的专利把Google告了(不要脸)
收购后立刻用Sun的专利把Google告了(不要脸)
业界都站队Google,认为这种行为是断送Java发展前景
当年Android刚起步的时候是Sun主动向Google抛的橄榄枝
Android的流行也巩固了Java的地位
当年Android刚起步的时候是Sun主动向Google抛的橄榄枝
Android的流行也巩固了Java的地位
但是Google使用了Java语法和API类库,开发出来的程序却不能运行在Java虚拟机上
确实很不厚道
确实很不厚道
当年微软用J++做了同样的事情,被Sun告到登报道歉(业界应该都站队微软呀,为啥没有呢)
Oracle邮件门,高管发邮件表示Java系统中无法盈利的部分“按计划报废”
把Java EE扫地出门,扔给了Eclipse基金会,改名为Jakarta EE
2018.9.25
JDK11发布
ZGC收集器
Oracle从JDK11起把以前的商业特性全部开源给OpenJDK
Oracle宣布以后会同时发布两个JDK,一个是OpenJDK,一个是OracleJDK
前者免费,但是只有半年时间的更新支持,后者对个人免费,但是商用收费,可以有三年的更新支持
Oracle宣布以后会同时发布两个JDK,一个是OpenJDK,一个是OracleJDK
前者免费,但是只有半年时间的更新支持,后者对个人免费,但是商用收费,可以有三年的更新支持
由此得出Java要收费一说,轰动一时
2018.10
最后一届JavaOne大会(1996-2018)
2019.2
Oracle宣布放弃对上一个版本OpenJDK的维护
RedHat同时接过OpenJDK8和11
RedHat同时接过OpenJDK8和11
Oracle不愿在旧版本上浪费资源,
而RedHat和其背后的IBM又乐意扩大自己的影响力
双赢
而RedHat和其背后的IBM又乐意扩大自己的影响力
双赢
2019.3.20
JDK12发布
加入了RedHat领导开发的Shenandoah垃圾收集器
但是在JDK11时说要保证OracleJDK和OpenJDK一致
转眼就在OracleJDK12里把Shennandoah剔除(出尔反尔)
转眼就在OracleJDK12里把Shennandoah剔除(出尔反尔)
2019.9.18
JDK13发布
增强 ZGC、更新Socket API等
2020.3.18
JDK14发布
优化G1、JFR 事件流、改进 NullPointerExceptions、switch 表达式扩展了 switch 语句、
移除 CMS等
移除 CMS等
2020.9.15
JDK15发布
EdDSA算法、instanceof的模式匹配、删除 Solaris 和 SPARC Ports
移除 Nashorn JavaScript 引擎、禁用偏向锁
移除 Nashorn JavaScript 引擎、禁用偏向锁
2021.3.*
JDK16发布
优化ZGC、弹性元空间、迁移到GitHub等
3.展望未来
1.无语言倾向
Java的第一的底气并不在于它有多先进多好用,而是庞大的用户群和成熟的生态
Graal VM:可以作为任何语言的运行平台,支持不同语言混用对方接口和对象
2.新即时编译器
Graal编译器(C2),由于C2编译器too 复杂 to 维护,Graal等于重构了C2
3.Native
微服务下Java表现的不适应:启动时间长、需要预热才能达到高性能等
Application Class Data Sharing:允许缓存类型信息,提升启动速度
Ahead of Time Compilation AOT:提前编译
4.HotSpot
经历了一系列的重构与开放
5.语言语法持续增强
博采众长
三、虚拟机执行子系统
第6章 类文件结构
1.Class类文件的结构
2.字节码指令简介
第7章 虚拟机类加载机制
1.类加载的时机与过程
加载(Loading)
1)通过一个类的全限定名来获取定义此类的二进制字节流
从ZIP压缩包中读取
从网络中获取
运行时计算生成
从数据库中读取
......
2)将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构
3)在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的数据的访问入口
验证(Verification)
目的是确保Class文件的字节流中包含的信息符合《Java虚拟机规范》
1.文件格式验证
是否以魔数0xCAFEBABE开头
主、次版本号是否在当前Java虚拟机接受范围之内
常量池的常量中是否有不被支持的常量类型
2.元数据验证
这个类是否有父类
这个类的父类是否继承了不允许被继承的类
如果这个类不是抽象类,是否实现了所有的方法
3.字节码验证
最复杂的一个阶段
保证被校验类的方法在运行时不会做出危害
虚拟机安全的行为
4.符号引用验证
发生在虚拟机将符号引用转化为直接引用的时候
符号引用中通过字符串描述的全限定名是否能找到对应的类
在指定类中是否存在符合方法的字段描述符
符号引用中的类、字段、方法的可访问性
准备(Preparation)
正式为静态变量分配内存并设置类变量初始值的阶段
仅针对类变量,不包括实例变量
初始值是类型的零值,不是定义的值
解析(Resolution)
虚拟机将常量池内的符号引用替换为直接引用的过程
符号引用
以一组符号来描述所引用的目标
直接引用
直接指向目标的指针
相对偏移量
间接定位到目标的句柄
初始化(Initialization)
有且只有六种情况必须初始化
1)遇到new、getstatic、putstatic或invokestatic
2)反射调用的时候
3)父类还没初始化,要先初始化父类
4)执行的主类要先初始化
5)当使用JDK 7新加入的动态语言支持时
6)当一个接口中定义了JDK 8新加入的默认方法时
被动引用的例子
子类访问父类的静态变量,只会触发
父类的初始化而不会触发子类的初始化
父类的初始化而不会触发子类的初始化
SubClass extends SuperClass
SubClass.value
SubClass.value
创建一个数组时,对象没有初始化
SuperClass[] sca = new SuperClass[10];
常量传播
ConstClass.HELLOWORLD
使用(Using)
卸载(Unloading)
2.类加载器
两个类相等
双亲委派
破坏双亲委派
类加载热部署
OSGi
自定义的类加载器
3.Java模块化系统(jdk9)
显式依赖
模块路径
扩展类加载器(Extension)被平台类加载器取代
取消了jre目录,因为随时可以组合构建出程序运行所需的JRE来
第8章 虚拟机字节码执行引擎
第9章 类加载及执行子系统的案例与实战
五、高效并发
第12章 Java内存模型与线程
1.主内存与工作内存
线程、主内存、工作内存
2.内存间交互操作
·lock(锁定):作用于主内存的变量,它把一个变量标识为一条线程独占的状态
·unlock(解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来
·read(读取):作用于主内存的变量,它把一个变量的值从主内存传输到工作内存
·load(载入):作用于工作内存的变量,它把read操作的变量值放入工作内存的变量副本中
·use(使用):作用于工作内存的变量,它把工作内存中一个变量的值传递给执行引擎
·assign(赋值):作用于工作内存的变量,它把一个从执行引擎接收的值赋给工作内存的变量
·store(存储):作用于工作内存的变量,它把工作内存中一个变量的值传送到主内存中
·write(写入):作用于主内存的变量,它把store操作的变量的值放入主内存的变量中
3.对于volatile型变量的特殊规则
保证此变量对所有线程的可见性
每次使用之前都要先刷新
禁止指令重排序优化
双锁检测DCL单例模式
public class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton.getInstance();
}
}
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
public static void main(String[] args) {
Singleton.getInstance();
}
}
4.先行发生原则
5.状态转换
6种状态
·新建(New):创建后尚未启动的线程处于这种状态
·运行(Runnable):包括操作系统线程状态中的Running和Ready
可能正在执行
也可能等待分配执行时间
·无限期等待(Waiting):处于这种状态的线程不会被分配处理器执行时间,需要被唤醒
■没有设置Timeout参数的Object::wait()方法;
■没有设置Timeout参数的Thread::join()方法;
■LockSupport::park()方法。
·限期等待(Timed Waiting):处于这种状态的线程也不会被分配处理器执行时间
■Thread::sleep()方法;
■设置了Timeout参数的Object::wait()方法;
■设置了Timeout参数的Thread::join()方法;
■LockSupport::parkNanos()方法;
■LockSupport::parkUntil()方法。
·阻塞(Blocked):线程被阻塞了
等待获取一个排它锁
·结束(Terminated):已终止线程的线程状态,线程已经结束执行
第13章 线程安全与锁优化
1.线程安全
1.互斥同步
synchronized关键字
ReentrantLock
·等待可中断
·公平锁
·锁绑定多个条件
2.非阻塞同步
CAS
2.锁优化
1.自旋锁与自适应自旋
2.锁消除
逃逸分析
3.锁粗化
4.轻量级锁
5.偏向锁
0 条评论
下一页