《深入理解JAVA虚拟机》读书笔记系列之:垃圾回收机制。
垃圾回收基础
- 什么是垃圾:内存中不在被使用到的内存空间就是垃圾。
引用计数法
在对象中添加一个引用计数器,每当有一个地方引用它时,计数器值就加一;当引用失效时,计数器值就减一;任何时刻计数器为零的对象就是不可能再被使用的。
- 优点:实现简单、效率高;
- 缺点:不能解决对象之间的循环引用。
可达性分析法(根搜索法)
通过一系列称为“GC Roots”的根对象作为起始节点集,从这些节点开始,根据引用关系向下搜索,搜索过程所走过的路径称为“引用链”,如果某个对象到GC Roots间没有任何引用链相连,则证明此对象是不可能再被使用的。
能够作为GCroots的对象:
在虚拟机栈(栈帧局部变量)中引用的对象、方法区类静态属性的引用对象、方法区中常量引用的对象、本地方法栈中引用的对象。具体实现上的优化
HotSpot中使用了OopMap的结构,保证准确快速完成GCroot的枚举。并且为了防止引用关系发生变化,设置了安全点,在安全点上进行记录和GC操作。到达安全点的方法也可以分为抢先式中断和主动式中断。抢先式式全部暂停,没有到达安全点的继续运行一下,这个方法几乎不被使用。主动式设置了标志位,在每个安全点出都查询标志位。并且还要检查Java堆的大小,确认是否有足够的内存分配资源。另外,存在安全区域,保证代码在这段区域中,引用关系不会变化。
记忆集:用于记录从非收集区域指向收集区域的指针集合的抽象数据结构。记录跨代引用的情况。具体实现,卡表法。
写屏障:维护卡表状态。在引用类型字段赋值前后,执行写屏障,更新卡表。
引用分类
强软弱虚
强引用:类似Object a = new A()无论任何情况下,只要强引用关系还存在,垃圾收集器就永远不会回
收掉被引用的对象。软引用:还有用但是并不必须。只被软引用关联着的对象,在系统将要发生内存溢出异常前,会把这些对象列进回收范围之中进行第二次回收,如果这次回收还没有足够的内存才会抛出内存溢出异常。
弱引用:非必须对象,被弱引用关联的对象只能生存到下一次垃圾收集发生为止。当垃圾收集器开始工作,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。
虚引用:一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的只是为了能在这个对象被收集器回收时收到一个系统通知
判断垃圾的步骤
- 实例垃圾
- 根搜索算法判断不可用
- 看看是否有必要执行finalize方法,只有第一次被回收时执行。
- 两个步骤完成没人使用,属于垃圾。进行垃圾回收。
- 类垃圾(同时满足)
- JVM中该类的全部实例被回收
- 加载该类的CLassLoader被回收
- 没有任何地方引用该类的Class对象,无法在任何地方通过反射访问这个类
可以进行类的卸载。
GC类型
- Minor GC:发生在新生代的GC
- MajorGC:发生在老年代的GC
- MixedGC:发生在新生代和部分老年代,G1
- FullGC:收集整个Java堆和方法区的GC
尽量减少Stop-the-world的时间。
跨代引用
- 什么是跨代引用: 一个代的对象引用其他代中的对象。老年代中的对象引用新生代的对象。
- 跨代引用假设:跨代引用相对于同代引用对象是极少数。
- 隐含推论:相互引用关系的两个对象,倾向于同时生存或者同时消亡。
- 优化解决方法:记忆集。
垃圾收集类型:
- 串行收集:GC单线程内存回收,暂停所有用户进程 Serial
- 并行收集:多个GC线程并发工作,但是还是会暂停用户线程 Parallel
- 并发收集:用户线程和GC线程同时(不一定并行,可能交替),用户线程不需要停顿。 CMS
垃圾收集算法
基础算法
- 标记清除法:
标记所有垃圾,然后清理。
优点:简单
缺点:执行效率低,一个一个标记;内存空间碎片化
- 标记复制算法:
一份为二,GC时候把存活的对象复制到一边,然后清空半区。用于回收新生代,新生代分为一块较大的Eden空间和两块较小的 Survivor空间,每次分配内存只使用Eden和其中一块Survivor。发生垃圾搜集时,将Eden和Survivor中仍然存活的对象一次性复制到另外一块Survivor空间上,然后直接清理掉Eden和已用过的那块Survivor空间。8:1:1。当Survivor空间不足以容纳一次Minor GC之后存活的对象时,就需要依赖其他内存区域(实际上大多就是老年代)进行分配担保。 - *缺点**:浪费了一半空间。
- 标记整理法:
让所有存活的对象都向内存空间一端移动,然后直接清理掉边界以外的内存。移动存活对象并更新所有引用这些对象的地方将会是一种极为负重的操作,而且这种对象移动操作必须STW才能进行。老年代一般采用
分配担保
在发生Minor GC之前,虚拟机必须先检查老年代最大可用的连续空间是否大于新生代所有对象总
空间:
- 如果这个条件成立,那这一次Minor GC可以确保是安全的。
- 如果不成立,则虚拟机会先查看是否允许担保失败;
- 如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,
- 如果大于,将尝试进行一次Minor GC,尽管这次Minor GC是有风险的;
- 如果小于,或者不允许冒险,那这时就要改为进行一次Full GC。
- 如果允许,那会继续检查老年代最大可用的连续空间是否大于历次晋升到老年代对象的平均大小,
HotSpot收集器
Serial 串行收集器
是一个单线程工作的收集器,但它的“单线程”的意义并不仅仅是说明它只会使用一个处理器或一条收集线程去完成垃圾收集工作,更重要的是强调在它进行垃圾收集时,必须暂停其他所有工作线程,直到它收集结束。
简单,所有收集器中额外内存消耗最小的,默认client模式下新生代收集器。 -XX:+UseSerialGC
开启。新生代是复制算法,老年代是标记整理算法。
PaNew 并行收集器
是Serial收集器的多线程并行版本。服务端Server模式下首选的新生代收集器。和CMS配合。
Parallel Scavenge收集器/Parallel Old
Parallel Scavenge收集器的特点是它的关注点与其他收集器不同,CMS等收集器的关注点是尽可能
地缩短垃圾收集时用户线程的停顿时间,而Parallel Scavenge收集器的目标则是达到一个可控制的吞吐量。
停顿时间越短就越适合需要与用户交互或需要保证服务响应质量的程序,良好的响应速度能提升用户体验;而高吞吐量则可以最高效率地利用处理器资源,尽快完成程序的运算任务,主要适合在后台运算而不需要太多交互的分析任务。
CMS收集器(并发收集器)
CMS(Concurrent Mark Sweep)收集器是一种以获取最短回收停顿时间为目标的收集器。CMS收集器是基于标记-清除算法。-XX:UseConcMarkSwepGC
,使用ParNew+CMS+Serial Old组合。
- 四个阶段:
- 初始标记: 只是标记一下GCRoots能直接关联到的对象,速度很快。需要STW。
- 并发标记:从GC Roots的直接关联对象开始遍历整个对象图的过程,这个过程耗时较长但是不需要停顿用户线程,可以与垃圾收集线程一起并发运行
- 重新标记:修正并发标记期间,因用户程序继续运作而导致标记产生变动的那一部分对象的
标记记录。STW - 并发清除:最后是并发清除阶段,清理删除掉标记阶段判断的已经死亡的对象,由于不需要移动存活对象,所以这个阶段也是可以与用户线程同时并发的。
- 优缺点:
优点:并发收集、低停顿。
缺点:对处理器资源敏感,并发执行的原因;无法处理浮动垃圾,也是因为并发(并发失败采用Serial old);空间碎片化,大对象容易FullGC。
G1收集器
G1是一款主要面向服务端应用的垃圾收集器。新的服务端模式下的默认GC。在延迟可控的情况下获得尽可能高的吞吐量,但是G1收集器要比其他的传统垃圾收集器有着更高的内存占用负担。
- 特点:
- 把连续的Java堆划分为多个大小相等的独立区域(Region)。
- 保留了分代的思想,但是不再是物理隔离,而是一部分region 的集合。每一个Region都可以根据需要,扮演新生代的Eden空间、Survivor空间,或者老年代空间。Region中还有一类特殊的Humongous区域,专门用来存储大对象。
- 充分利用多CPU,尽量缩短STW。
- 整体上是标记整理算法,局部是复制算法。不会产生内存碎片。
- 停顿可预测。每次寻找最多垃圾的region回收,每次收集到的内存空间都是Region大小的整数倍(mixedGC)
- 四个步骤
- 初始标记:标记一下GC Roots能直接关联到的对象,STW
- 并发标记:GC Root
- 最终标记:STW,最终标记。
- 筛选回收:在给定的时间内,寻找最大的价值region。STW
- 使用参数
-XX:+UseG1GC
开启G1,默认就是G1.-XX:MaxGCPauseMilis = n
最大Gc停顿时间。-XX:InitiatingHeapOccupancyPercet = n
占用多少空间比例时候,出发GC-XX:NewRatio = n
默认为2 新生代和老年的比例-XX:SurvivorRatio = n
默认为8-XX:MaxTenuringThreshold = n
默认15,新生代到老年代的岁数。
其他收集器
- ZGC收集器:暂停时间低;支持Tb级别内存,吞吐量影响下。
GC性能与参数设置
- 吞吐量:代码时间/GC时间。 反过来就是GC负荷
- STW时间
- GC频率:发生GC的次数。
- 反应速度:从对象称为垃圾到被回收的时间
- 低延迟:影响交互的时间最小。
新生代的不能太小,容易导致频繁YGC,且进入老年代之后容易FGC。新生代的大小是整个堆的3/8到一半。
老年代要适当,太小容易内存碎片,或者高频率FGC;太大了回收时间长。
对象,对象在新生代Eden区中分配。当Eden区没有足够空间进行分配时,虚拟机将发起
一次Minor GC。大文件直接进入老年代,长期存活的对象直接进入老年代。
动态年龄判断:如果在Survivor空间中相同年龄所有对象大小的总和大于Survivor空间的一半,年龄大于或等于该年龄的对象就可以直接进入老年代
- 经验:
新生代的不能太小,容易导致频繁YGC,且进入老年代之后容易FGC。新生代的大小是整个堆的3/8到一半。
老年代要适当,太小容易内存碎片,或者高频率FGC;太大了回收时间长。
对于吞吐量优先的应用,设置大新生代和小老年代,尽快回收短期对象。
对象优先再新生代分配,长时间存活对象进入老年代。
合理选择,内存分配,收集器方法。