这里写目录标题
理论模型
需要回收的对象的判断
引用计数法
在对象中添加引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效是,计数器值减一。
任何时刻,计数器为0,说明该对象不再被使用。
如果出现循环引用的问题,引用计数法很难解决。
可达性分析
通过一系列的GCRoot根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索。
如果某个对象没有任何路径可以到GCRoot集合,则说明此对象不可能再引用。
GCRoot根:
- 栈帧中,本地变量表引用的对象,(各个线程被调用的方法,堆栈中使用到的参数,局部变量,临时变量)。
- 在方法区中,类静态属性引用的对象,譬如Java类的引用类型静态变量。
- 在方法区中,常量引用的对象,譬如字符串常量池里的引用。
- 在本地方法栈中,JNI引用的对象。
- Java虚拟机内部的引用,如基本数据类型对应的Class对象,一些常驻的异常对象。
- 所有被同步锁(Synchronized关键字)持有的对象。
特例 finalize() 方法。
分代收集理论
收集器应该讲Java堆划分出不同的区域,然后将回收对象依据其年龄分配到不同的区域存储。
- MinorGC:对年轻代进行垃圾收集
- MajorGC:对老年代进行垃圾收集
- FullGC:全域的垃圾收集
标记-清除算法
首先标记出所有需要回收的对象,在标记完成后,统一回收所有被标记的对象。
也可以标记存活的对象,统一回收所有未被标记的对象。
缺点:
执行效率不稳定,如果Java堆中包含大量对象,而且大部分需要被回收,这是就要进行大量的标记和回收。
内存碎片化问题,标记-清除后产生大量不连续的内存碎片
标记-复制算法
半区复制:将可用内存划分为大小相同的两块,每次只使用其中的一块。
当一块的内存用完,就将还活着的对象复制到另一块上去,然后再把已经使用的内存空间一次性清理掉。
标记复制算法的优化:因为大部分时候,年轻代中的对象大部分都都朝生夕死的。
年轻代= Eden+ 2Survivor(from区 + to区);
Eden区与from/to区:8:1;
发生MiniorGC:Eden区使用标记复制算法复制到from区,to区域也复制到from区域,此时存活的对象都在from区。
标记-整理算法
标记与其他标记算法一样,后续步骤是让所有存活的对象都向内存空间一端移动,然后直接清理掉边界之外的空间。
内存分配与回收策略
对象优先在Eden分配。
大多数情况下,对象生成之后,直接在Eden区分配空间。当Eden区域不够时,发起MiniorGC。
大对象直接进入老年代
大对象就是需要连续内存空间存储的对象,最典型的就是字符串。如果存在大量的大对象,就会发生频繁的GC,但是如果大对象反复在年轻代标记-复制,会耗费大量的CPU资源,而且还会增大发生FullGC的概率。
设置大对象阈值:-XX:PretenureSizeThreadhold。
长期存活的对象进入老年代
由对象头中的对象年龄,作为判断依据。Survivor中,每在一次MiniorGC,或FullGC中存活下来,那么对象的年龄加1。默认当对象年龄达到一定程度(默认15),就会晋升到老年代中。
设置晋升老年代的年龄阈值:-XX:MaxTenuringThreadhold。
动态年龄对象判断
如果在Survivor空间中相同年龄的所有对象的大小总和超过Survivor区空间的一半,则年龄大于或等于该年龄的对象就可以直接进入老年代。
空间分配担保机制
发生MiniorGC之前:
判断 老年代剩余的连续空间 和 年轻代所有对象大小的总和。
判断是否设置参数:-XX:HandlePromotationFailure允许担保失败。
判断 老年代剩余的连续空间 和 历次年轻代经历MiniorGC之后进入老年代的平均大小。
实际垃圾收集器
CMS(Serial Old作为备份方案)
标记清除算法。
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。
常用在网站或者响应速度快的系统中。
- 1)初始标记CMS initial mark 标记GCRoot能直接关联到的对象
- 2)并发标记CMS concurrent mark 从GCRoot直接关联的对象开始遍历整个对象图。
- 3)重复标记CMS remark 修正并发标记期间,由于对象发生的改动,导致标记需要作出调整。
- 4)并发清除CMS concurrent sweep 清理掉标记已经死亡的对象。
初始标记和并发标记这两个步骤仍然会发生“Stop the world”。
三大缺点: - 对处理器资源非常敏感。CMS对处理器核心数量是有要求的。当处理器数量超过4个的时候,占用的处理器资源会随着处理器数量的增加而减少。
- 无法处理浮动垃圾。在并发标记和并发清理阶段,由于此时用户的其他线程仍然在执行,那么就会不断地有新的对象产生,但是这一部分对象是出现在标记以后,,CMS无法在这次回收中回收掉这些垃圾对象,只好留待下一次回收清理。(这样的垃圾称为浮动垃圾)。
- 标记-清除算法的通用缺点,会产生大量的空间碎片。
G1垃圾收集器(老年代与年轻代都可以使用)
面向局部收集和基于Region的内存布局。
停顿时间模型:在指定时间长度M毫秒的时间片段内,消耗在垃圾收集上的时间大概率不超过N毫秒。
- G1不再以新生代,老年代作为目标范围,而是面向堆中的任何部分;衡量标准不再是分代,而是回收利益(MixedGC)
- G1不再坚持固定大小以及固定数量的分代区域划分;把连续的Java堆划分为多个大小相等的独立区域(Region);
- 每一个Region都可以根据需要,扮演新生代和Eden空间,Survivor空间,或者老年代空间。
- 收集器能对扮演不同角色的Region采用不同的策略处理
- Region中Humongous区域专门存储大对象(超过Region大小的一半)
- 回收前,Region按照回收利益大小排序,优先处理回收利益大的(在特定的时间内)
收集步骤:
- 初始标记 InitialMarking:标记GCRoot能直接关联到的对象,并且修改TAMS指针的值
- 并发标记 Concurrent Marking:GCRoot开始对堆中的对象进行可达性分析,重新处理SATB记录
- 最终标记 Final Marking:对用户线程做另一个短暂的暂停,用于处理并发阶段结束后仍遗留下来的最后的少量的SATB记录。
- 筛选回收 Live Data Counting and Evacuation:更新Region的统计数据,对各个Region的回收价值和成本进行排序,指定回收计划。
注: - TAMS:(Top at Mark Start)每个Region上设置两个名为TAMS的指针
- SATB:原始快照方法