《深入理解java虚拟机》读书笔记(二)
垃圾回收机制
进行垃圾回收的前提是判断对象是否还活着,这就引出两种判断的方法。
- 引用计数法:给对象添加一个引用计数器,每当有一个地方引用就加一,引用失效的时候减一。在任何时候引用计数器都为0的时候就证明对象不可被引用。
java不使用的原因:难以解决对象循环引用的问题 - 可达性分析法:通过GC Roots作为起始节点,然后开始向下搜索,走过的路径称为引用链。若一个对象到GC Roots没有任何引用链,则证明对象是不可达的。
固定作为GC Roots的对象包括:
- 虚拟机栈引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中引用的对象等
JDK1.2之后,Java将引用分为强引用、软引用、弱引用、虚引用。
- 强引用:最传统的引用,类似 Object obj = new Object();只要引用有效,便不回收。
- 软引用:还有用,但非必须的对象。内存足够就不回收,内存溢出前回收
- 弱引用:非必须对象,当下一次GC收集的时候被回收
- 虚引用:最弱,唯一的目的是在对象被回收的时候,收到一个系统通知
一个对象真正不可用,要经过两次标记过程:
- 进行可达性分析,没有引用链的对象被第一次标记
- 筛选,筛选的条件是对象是否有必要执行finalize()方法。若对象没有重写,或jvm已经调用过此方法,则由GC回收
- 如果有必要执行finalize()方法,对象会被放入到F-Queue的队列当中,jvm会新建一个优先级较低的线程去执行finalize()方法(并不承诺会等待方法结束)。
- finalize()方法是对象最后一次自救的机会,如果finalize()方法使对象和引用链上对象关联,则自救成功;否则对象被第二次标记,进行回收。
回收方法区:回收废弃的常量和不再使用的类型
标记清除算法
-
标记-清除算法:两个阶段:标记-清除
缺点:执行效率不稳定;产生大量内存碎片 -
标记-复制算法:将内存划分为两块,当一块内存用完的时候,就把存活的对象复制到另一块内存中,解决标记-清除执行效率不稳定的问题
缺点:浪费一半内存 -
标记-整理算法:在老生代中比较适用。首先进行标记之后,把存活的对象移动到内存一端,直接清理到边界以外的内存。
HotSpot虚拟机里面关注吞吐量的Parallel Scavenge收集器是基于标记-整理算法的,而关注延迟的CMS收集器则是基于标记-清除算法的,这也从侧面印证这点。
经典的垃圾回收器
重点介绍CMS,G1
-
Serial收集器
单线程工作,stop the world,复制算法,客户端模式下适用 -
ParNew收集器
Serial收集器多线程并行版本,复制算法,服务端模式下适用,和CMS搭配使用 -
Parallel Scavenge收集器
多线程,复制算法,特点是吞吐量优先 -
Serial Old收集器
Serial的老年代版本,单线程,使用标记-整理算法 -
Parallel Old收集器
Parallel Scavenge的老年代版本,多线程,标记-整理算法 -
CMS收集器
特点:以最短回收停顿时间为目标,使用标记-清除算法。
过程:
*初始标记:标记GC Roots能关联到的直接对象,stop the world
*并发标记:进行GC Roots tracing
*重新标记:修正并发标记期间,用户程序运行而导致标记产生变化的那一部分对象的标记记录;stop the world.
*并发清除:清除对象
优点:并发清除,低停顿
缺点:资源敏感,无法处理浮动垃圾(并发清除的时候,用户程序依旧再运行这时产生的垃圾称为浮动垃圾),产生大量空间碎片。 -
Garbage First收集器(G1收集器)
特点:面向服务端应用的垃圾收集器,不再固定大小以及固定数量的分代区域划分,而是把连续的Java堆划分为大小一致的独立区域(Region),优先处理回收收益大的Region。整体:标记-整理算法,局部标记-复制算法
问题解决:
跨Region引用:使用记忆集,避免全堆作为GC Roots的扫描
收集线程和用户线程互相干扰:原始快照算法保证当用户线程更改对象引用关系时,图结构不被改变
可靠的停顿预测:衰减均值理论
过程:
*初始标记:标记GC Roots能关联到的直接对象,stop the world
*并发标记:可达性分析
*最终标记:处理并发标记结束遗留下的标记记录,stop the world
*筛选回收:对各个Region的回收价值和成本进行排序,制定回收计划。stop the world