《深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)》读书笔记
2022-05-21 17:53:29 0 举报
AI智能生成
分代假说;jvm垃圾回收算法;java虚拟机工具;
作者其他创作
大纲/内容
垃圾回收算法
1.标记清除
1.标记出需要被回收的对象
2.标记完成后然后进行统一回收
3.也可以反过来标记存活的对象
2.标记完成后然后进行统一回收
3.也可以反过来标记存活的对象
缺点:
1.执行效率不稳定
2.会产生大量的空间碎片
2.标记复制
即半区复制
1.将内存分为等大小的两块
2.每次只使用其中的一块,等到这块使用完了,就将存活的对象复制到另一块上
3.将使用过的那块,完全清除掉
1.将内存分为等大小的两块
2.每次只使用其中的一块,等到这块使用完了,就将存活的对象复制到另一块上
3.将使用过的那块,完全清除掉
优点
1.不用考虑内存碎片的问题
缺点
1.浪费一半空间
2.如果大部分对象都是存活的,就会产生大量的对象copy的成本
3.标记整理
1.标记:标记出存活的对象(标记过程与标记清除算法的标记过程一样)
2.所有存活对象向内存一端移动
3.直接清理掉边界以外的内存
2.所有存活对象向内存一端移动
3.直接清理掉边界以外的内存
分代收集理论
1.弱分代假说:绝大多数对象都是朝生夕灭的
2.强分代假说:熬过越多次垃圾收集,对象就越难以消亡
分代收集的难点:存在跨代引用
例如:在进行Minor GC时,这时年青代当中的部分对象被老年代引用,就不得不在年青代的GCRoot的基础上额外添加老年代的GCRoot
跨代引用假说
1.跨代引用相对同代引用只占极少数
根据“弱分代假说”和“强分代假说”可以推理出,存在互相引用的两个对象,更倾向于同生共死。
例如:某个新生代存在跨代引用,由于老年代的对象难以消亡,新生代的这个对象同样得以存活,
这样熬过多次minor GC后,同样得以晋升到老年代,跨代引用也就消除了
例如:某个新生代存在跨代引用,由于老年代的对象难以消亡,新生代的这个对象同样得以存活,
这样熬过多次minor GC后,同样得以晋升到老年代,跨代引用也就消除了
结论:依据这条假说,我们不必在每次minor GC时对整个老年代进行扫描,也不必浪费空间记录每一个对象是否存在跨代引用。
只需在新生代创建一个全局的数据结构,称之为:“记忆集”,这个结构把老年代划分成若干个小块,标识出哪一块内存存在跨代引用,在minor GC时,将对应的对象加入GC ROOTS即可。
只需在新生代创建一个全局的数据结构,称之为:“记忆集”,这个结构把老年代划分成若干个小块,标识出哪一块内存存在跨代引用,在minor GC时,将对应的对象加入GC ROOTS即可。
可作为GC Roots的节点
1.全局性引用
1.常量
2.类静态引用
2.执行上下文
1.栈帧中的局部变量表
3.注意点:在根节点枚举时,会产生STW(stop the word)
安全点
1.安全点选择的原则-基本都以"是否具有让程序长时间运行"为原则进行判定
长时间执行明显的特征就是:指令序列复用
方法调用、循环跳转、异常跳转都属于指令序列复用,都可以产生安全点
2.安全点里记录了栈上,寄存器里哪些位置存放的是对象的引用
3.作用
方便垃圾收集器开始扫描时,可以直接获取到对象的引用,而不用一个不漏的从GC Roots查找
4.如何在垃圾回收时让所有的线程(不包括JNI调用的线程)都跑到最近的安全点
1.抢先式中断
1.垃圾收集发生时,系统直接把所有的线程全部中断
2.发现有线程中断在不安全点上,就恢复该线程,等其运行到最近的安全点上再中断
3.几乎没有虚拟机采用抢先式中断
2.主动式中断
1.当垃圾收集发生时,系统不主动操作用户线程,仅仅是设置一个简单的标志位
2.各执行线程在执行时会主动的不断去轮询这个标志位
3.当标志位为true时,线程就主动跑到最近的安全点上挂起
4.轮询标志和安全点是重合的
安全区域
何为"安全区域"?
安全区域是指在一段代码片段中引用关系不会发生变化,在这个片段中任何位置开始垃圾收集都是安全的
有了安全点,为何需要安全区域?
只有获得cpu时间片的线程才有机会从不安全点运行到安全点。对于sleep或者blocked的线程,短时间内无法获得cpu时间片,
系统也不可能一直等待它们。这是候就需要安全区域。
系统也不可能一直等待它们。这是候就需要安全区域。
使用
当用户线程运行到安全区域的代码时,会先标识自己进入安全区域了,当垃圾收集器开始工作时,就不用去管这些申明自己在安全区域的线程。
当要离开安全区域时,用户线程必须先检查GC Roots 根是否已经枚举完成,如果完成了,就当做没事发生过一样,继续执行,否则必须等待GC Roots根枚举完成。
当要离开安全区域时,用户线程必须先检查GC Roots 根是否已经枚举完成,如果完成了,就当做没事发生过一样,继续执行,否则必须等待GC Roots根枚举完成。
三色标记法
概念
白色:表示对象尚未被垃圾收集器访问过
在可达性分析刚刚开始的阶段,所有对象都是白色的,
在可达性分析结束的时候,若对象仍为白色的,则代表该对象不可达
在可达性分析刚刚开始的阶段,所有对象都是白色的,
在可达性分析结束的时候,若对象仍为白色的,则代表该对象不可达
灰色:标识垃圾收集器已经访问过该对象,但对象上至少还存在一个引用未被垃圾收集器访问过
黑色:表示这个对象已经被垃圾收集器访问过,且对象上所有的引用都已经被扫描过
黑色代表对象已经被扫描过,且它是安全存活的,如果有其他对象引用指向了黑色对象,无需再重新扫描一遍。
黑色对象不能跨过灰色对象,直接指向白色对象
黑色代表对象已经被扫描过,且它是安全存活的,如果有其他对象引用指向了黑色对象,无需再重新扫描一遍。
黑色对象不能跨过灰色对象,直接指向白色对象
扫描过程
1.初始状态
所有对象都是白色的,只有GC ROOTS 是黑色的
2.初始标记阶段
将所有GC ROOTS 直接引用的对象,标记为灰色
3.并发标记
扫描整个引用链
没有子节点的话,将本节点置为黑色
有子节点的话,将本节点置为黑色,子节点置为灰色
没有子节点的话,将本节点置为黑色
有子节点的话,将本节点置为黑色,子节点置为灰色
4.重复并发标记阶段
直至灰色对象没有其他子节点对象引用时结束
5.扫描完成
此时黑色对象时存活对象
白色对象就是已消亡可回收的对象
白色对象就是已消亡可回收的对象
三色标记法缺陷
在并发标记阶段时,用户线程和GC线程同时运行,会产生 多标和漏标 的情况
文章
https://www.jianshu.com/p/d6d3a2adc50a
垃圾收集器
1.Serial收集器
特征
1.单线程收集器
2.serial收集器工作时必须暂停其他工作线程(STW)
工作机制
1.新生代
采用复制算法,暂停所有用户线程
2.老年代
采用标记整理算法,暂停所有用户线程
2.ParNew收集器
特征
1.实际ParNew收集器可以看成是Serial收集器的多线程并行版本
工作机制
1.新生代
采用复制算法,暂停所有用户线程
2.老年代
采用标记整理算法,暂停所有用户线程
3.Serial Old收集器
特征
1.Serial Old收集器是Serial收集器的老年代版本
4.Parallel Scavengae收集器
特征
1.又称为“吞吐量优先收集器”
工作机制
1.新生代收集器
2.基于标记复制算法实现
3.该收集器关注点不在垃圾收集时间的长短,而是整体达到一个可控制的“吞吐量”
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集器执行时间)
吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集器执行时间)
垃圾收集器参数配置
-XX:MaxGCPauseMillis 直接设置垃圾收集最大停顿时间
1.该值是一个大于0的毫秒数,设置后垃圾收集器将尽量保证垃圾收集的时间不超过用户设置的时间
2.该值并不是越小就越好,垃圾收集时间时间缩短是通过牺牲吞吐量和缩小新生代空间实现的,还会导致垃圾收集变得频繁
-XX:GCTimeRatio 直接设置吞吐量大小
5.Parallel Old收集器
特征
1.Parallel Scavenge收集器的老年代版本
2.支持多线程并发收集
工作机制
1.基于标记整理算法实现
在注重吞吐量和处理器资源稀缺的场景下,可以考虑使用Parallel Scavenge + Parallel Old
6.CMS收集器
特征
1.CMS收集器是一款以获取最短GC停顿时间为目的的收集器
2.
工作机制
1.基于标记清除算法实现
工作过程
1.初始标记
需要STW 这个过程仅仅只标记GC Root直接关联到的对象
2.并发标记
从GC Root直接关联的对象开始,并发的遍历整个对象图
3.重新标记
需要STW 这个过程是为了修正在并发标记过程中,因为用户程序继续运行,而导致标记变动的那部分对象的标记
4.并发清除
并发清理删判断为已经死亡的对象
优点
1.并发收集
2.低停顿
甚至一些官方文档又将CMS称为"并发低停顿收集器"
缺点
1.对处理器资源非常敏感
在并发过程中,虽然不会导致用户线程停顿,但却会因为占用了一部分线程,导致应用程序变慢,降低了总吞吐量
2.CMS默认的回收线程数=(处理器数+3)/4
也就是在4个处理器及以上的场景中,并发时,最多占用不超过25%的处理器资源,并且随着处理器数增加而下降
但当处理器数目不足4个时,对用户线程的影响将变得很大
3.无法处理“浮动垃圾”
有可能导致“并发失败”,转而采用STW的Full GC
G1收集器
虚拟机排查工具
jps
用途
列出虚拟机正在运行的进程
命令格式
jps [options] [hostid]
参数
-q
只输出LVMID(本地虚拟机进程唯一id--进程号),省略主类名称
-m
输出虚拟机进程启动时传递给主函数main()的参数
-l
输出主类的全名,如果运行的是jar包,则输出jar包的全路径
-v
输出虚拟机进程启动时的jvm参数
-V
输出通过flag文件传递到JVM中的参数
jstat
用途
虚拟机统计信息监控工具
命令格式
jstat [option vmid [ interval [s|ms] [count] ] ]
ps1:如果是本地虚拟机进程,则VMID和LVMID一致
ps2:如果是远程虚拟机,则VMID的格式为:[prptocol:][//]lvmid[@hostname[:port]/servername]
实例
jstat -gc 2958 250ms 10
查看进程2958的gc信息,每隔250ms打印一次,共10次
参数
-gc
监视gc信息
-class
监控类的加载,卸载,总空间及类装载耗时
-gccapacity
监视内容与-gc基本相同,但输出主要关注java堆各个区域使用到的最大最小空间
-gcutil
监视内容与-gc基本相同,单输出主要关注百分比
-gccause
在-gcutil的基础上,增加上一次gc的原因
-gcnew
监视新生代垃圾收集情况
-gcnewcapacity
与-gcnew基本相同,额外关注最大最小空间
-gcold
监视老年代垃圾收集情况
-gcoldcapacity
与gcold基本相同,输出关注使用到的最大最小空间
-gcpermcapacity
输出永久代最大最小空间
-compiler
输出及时编译器编译过的类和耗时信息
-printcompilation
输出及时编译器编译过的方法
jinfo
用途
查看和调整虚拟机的各项参数
命令格式
jinfo [option] pid
参数
-flag [+|-] name
-flag name=value
运行期修改一部分运行期可修改的虚拟机参数值
jmap
用途
1.用于生产堆转储快照(Heap Dump)
2.查询finalize 的执行队列
命令格式
jmap [option] vmid
参数
-dump
生成java堆转储快照
格式为 -dump: [live] ,format=b,file=<filename> 其中live子参数说明是否只dump出存活的对象
-finalizerinfo
显示在F-queue队列中等待finalize线程执行finalize方法的对象
-heap
显示java堆信息,如使用哪种回收器,参数配置,分代状况等
-histo
显示堆中对象统计信息,包括,类,实例数量,合计容量
-permstat
以classloader为统计口径,显示永久代内存状况
-F
当虚拟机进程对-dump没有响应时,可以使用-F强制生成快照
实例
jmap -dump:live,format=b,file=dump.bin 90699
jhat
用途
虚拟机转储快照分析工具
使用
jhat 转储的dump文件
jhat会启动一个内置的http服务器
屏幕显示Server is ready后,浏览器输入 http://localhost:7000,就可以看到信息
jstack
用途
java堆栈跟踪工具
用于生成虚拟机的线程快照
命令格式
jstack [option] vmid
参数
-F
当正常输出线程快照的命令不被响应时,使用-F强制输出线程快照
-l
除堆栈外显示关于锁的额外信息
-m
如果调用本地方法的话,可以下手C/C++堆栈
jvm命令强制垃圾回收
jcmd 进程id GC.run
Class类文件结构
前置说明
ps:一个class文件对应着一个唯一的类或者接口定义信息,但是反过来说,类或接口却不一定得定义在文件里,可以动态生成,直接送入类加载器
ps2:class文件是一个以8个字节为基础单位存储的二进制流
文件结构内容
前置说明
u1,u2,u4,u8分别代表1个字节,2个字节,4个字节,8个字节的无符号数
无符号数字可以用来描述,数字,数量值,引用,UTF-8编码的字符串
无符号数字可以用来描述,数字,数量值,引用,UTF-8编码的字符串
表
由多个无符号数或者其他表构成的复合数据类型,通常以 _info 结尾
类型 名称 数量
u4 magic 1
前四个字节为固定魔数 0xCAFEBABY
魔数的唯一作用就是用来标识这个class文件是否是能被java虚拟机接受
u2 minor_version 1
子版本号
u2 major_version 1
主版本号
高版本的jdk能向下兼容低版本的class文件,但不能运行以后的java版本
u2 constant_pool_count 1
常量池的大小
cp_info constant_pool constant_pool_count-1
常量池表
u2 access_flags 1
u2 this_class 1
u2 super_class 1
u2 interfaces_count 1
u2 interface_count interfaces_count
u2 field_count 1
field_info fields field_count
u2 methods_count 1
methods_info methods methods_count
u2 attributes_count 1
attributes_info attributes attributes_count
class的加载机制
1.顺序
1.加载
1.通过类的全限定名获取定义此类的二进制流
2.将这个二进制流所代表的静态存储结构转换为方法区的运行时数据结构
3.在内存中生成一个代表这个类的java.lang.Class对象,作为方法区的这个类的各种数据访问的入口
2.验证
确保Class文件包含的字节流符合java虚拟机规范,保证这些信息被当成代码运行后不会危及虚拟机自身的安全
1.文件格式验证
2.元数据验证
3.字节码验证
4.符号引用验证
3.准备
1.为类中定义的类变量(静态变量)分配内存并赋初始值
2.此时赋的值时零值
4.解析
5.初始化
6.使用
7.卸载
2.固定顺序
其中只有:加载、验证、准备、初始化、卸载这5个操作的顺序是固定的
3.链接
验证、准备、解析三个阶段又称为链接
0 条评论
下一页