目录
一、低延迟垃圾收集器
衡量垃圾收集器的三项最重要的指标是:内存占用、吞吐量和延迟。
Shenandoah收集器
Shenandoah的目标是实现一种能在任何堆内存大小下都可以把垃圾收集的停顿时间限制在十毫秒以内的垃圾收集器
工作阶段
· 初始标记(Initial Marking):与G1一样,首先标记与GC Roots直接关联的对象,这个阶段仍 是“Stop The World”的,但停顿时间与堆大小无关,只与GC Roots的数量相关。 ·
并发标记(Concurrent Marking):与G1一样,遍历对象图,标记出全部可达的对象,这个阶段 是与用户线程一起并发的,时间长短取决于堆中存活对象的数量以及对象图的结构复杂程度。 ·
最终标记(Final Marking):与G1一样,处理剩余的SATB扫描,并在这个阶段统计出回收价值 最高的Region,将这些Region构成一组回收集(Collection Set)。最终标记阶段也会有一小段短暂的停 顿。 ·
并发清理(Concurrent Cleanup):这个阶段用于清理那些整个区域内连一个存活对象都没有找到 的Region(这类Region被称为Immediate Garbage Region)。 ·
并发回收(Concurrent Evacuation):并发回收阶段是Shenandoah与之前HotSpot中其他收集器的 核心差异。在这个阶段,Shenandoah要把回收集里面的存活对象先复制一份到其他未被使用的Region之 中。复制对象这件事情如果将用户线程冻结起来再做那是相当简单的,但如果两者必须要同时并发进 行的话,就变得复杂起来了。其困难点是在移动对象的同时,用户线程仍然可能不停对被移动的对象 进行读写访问,移动对象是一次性的行为,但移动之后整个内存中所有指向该对象的引用都还是旧对 象的地址,这是很难一瞬间全部改变过来的。对于并发回收阶段遇到的这些困难,Shenandoah将会通 过读屏障和被称为“Brooks Pointers”的转发指针来解决(讲解完Shenandoah整个工作过程之后笔者还要 再回头介绍它)。并发回收阶段运行的时间长短取决于回收集的大小。 ·
初始引用更新(Initial Update Reference):并发回收阶段复制对象结束后,还需要把堆中所有指 向旧对象的引用修正到复制后的新地址,这个操作称为引用更新。引用更新的初始化阶段实际上并未 做什么具体的处理,设立这个阶段只是为了建立一个线程集合点,确保所有并发回收阶段中进行的收 集器线程都已完成分配给它们的对象移动任务而已。初始引用更新时间很短,会产生一个非常短暂的 停顿。 ·
并发引用更新(Concurrent Update Reference):真正开始进行引用更新操作,这个阶段是与用户 线程一起并发的,时间长短取决于内存中涉及的引用数量的多少。并发引用更新与并发标记不同,它 不再需要沿着对象图来搜索,只需要按照内存物理地址的顺序,线性地搜索出引用类型,把旧值改为 新值即可。 ·
最终引用更新(Final Update Reference):解决了堆中的引用更新后,还要修正存在于GC Roots 中的引用。这个阶段是Shenandoah的最后一次停顿,停顿时间只与GC Roots的数量相关。 ·
并发清理(Concurrent Cleanup):经过并发回收和引用更新之后,整个回收集中所有的Region已 再无存活对象,这些Region都变成Immediate Garbage Regions了,最后再调用一次并发清理过程来回收 这些Region的内存空间,供以后新对象分配使用。
只要抓住其中 三个最重要的并发阶段(并发标记、并发回收、并发引用更新),就能比较容易理清Shenandoah是如 何运作的了。
转发指针
Shenandoah用以支持并行整理的核心概念 :转发指针
转发指针内容:在原有对象布局结构的最前面统一增加一个 新的引用字段,在正常不处于并发移动的情况下,该引用指向对象自己。
转发指针加入后带来的收益自然是当对象拥有了一份新的副本时,只需要修 改一处指针的值,即旧对象上转发指针的引用位置,使其指向新对象,便可将所有对该对象的访问转 发到新的副本上。这样只要旧对象的内存仍然存在,未被清理掉,虚拟机内存中所有通过旧引用地址 访问的代码便仍然可用,都会被自动转发到新对象上继续工作。
Brooks形式的转发指针在设计上决定了它是必然会出现多线程竞争问题的。Shenandoah收集器是通过比较并交换操作来保证并发时对象的访问正 确性的。
为了实现Brooks Pointer,Shenandoah在读、写屏障中都加入了额外的转发处理,尤其是使用 读屏障的代价,这是比写屏障更大的。
停顿时间比其他几款收集器确实有了质的飞跃,但也并未实现最大停顿时间控制在十毫秒以内 的目标,而吞吐量方面则出现了很明显的下降,其总运行时间是所有测试收集器中最长的。
二、选择合适的垃圾收集器
1、Epsilon收集器
这是一款以不能够进行垃圾 收集为“卖点”的垃圾收集器
2、收集器的权衡
3、虚拟机及垃圾收集器日志
4、垃圾收集器参数总结
三、内存分配与回收策略
Java技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以 及自动回收分配给对象的内存
1、对象优先在Eden分配
大多数情况下,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起 一次Minor GC。
2、大对象直接进入老年代
大对象就是指需要大量连续内存空间的Java对象,最典型的大对象便是那种很长的字符串,或者 元素数量很庞大的数组
比遇到一个大对象更加坏的消息就是遇到一群“朝生夕灭”的“短命大对 象”,我们写程序的时候应注意避免
大对象就意味着高额的内存复制开销。HotSpot虚拟机提供了-XX:PretenureSizeThreshold 参数,指定大于该设置值的对象直接在老年代分配,这样做的目的就是避免在Eden区及两个Survivor区 之间来回复制,产生大量的内存复制操作。
注意
-XX:PretenureSizeThreshold参数只对Serial和ParNew两款新生代收集器有效
3、长期存活的对象将进入老年代
虚拟机给每个对象定义了一个对 象年龄(Age)计数器,存储在对象头中,当它的年龄增加到一定程 度(默认为15),就会被晋升到老年代中。对象晋升老年代的年龄阈值,可以通过参数-XX: MaxTenuringThreshold设置。
4、动态对象年龄判定
如果在Survivor空间中相同年龄所有对象大小的总和大于 Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代,无须等到-XX: MaxTenuringThreshold中要求的年龄。
5、空间分配担保
流程图