继续参考《深入理解java虚拟机》来回顾下java的垃圾收集器相关的知识(第三章)


垃圾回收

垃圾回收可以说时java中的一大特色,那么java时如何自动判断什么样的对象应该被回收的呢? 常用的算法时下面两种

  1. 引用计数法
  2. 可达性分析法

引用计数法

引用计数法的实现比较简单:给对象添加一个引用计数器,每当有一个地方引用对象,计数器值就加1;当引用失效计数器就减1;计数器为0的对象就是不可能再被使用的。 但是主流的java虚拟机里没有采用这种算法来管理内存,因为该算法很难解决对象之间循环引用的问题。

可达性分析法

目前主流的Java虚拟机都采用该算法。这个算法的基本思路是通过一系列的“GC Roots”的对象作为起始点,从这些节点开始向下搜索,搜索所走过的路径称为引用链(reference chain),当一个对象到“GC Roots”没有任何引用链时,则证明该对象时不可用的。 java语言中可以作为“GC Roots”的对象是

  • 虚拟机栈中的引用的对象
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象
  • 本地方法栈中JNI引用的对象

java 引用说明

上面两种算法最终判定对象是否存活都与“引用”有关,那么JAVA时如何定义引用的呢。 早期JDK中对引用的定义很传统:如果reference类型中的数据中存储的数值代表的时另外一块内存的起始地址,就称这块内存代表一个引用 JDK1.2之后,java对引用的概念进行了扩充,将引用分为

  • 强引用(Strong Reference)
  • 软引用(Soft Reference)
  • 弱引用(Weak Reference)
  • 虚引用(Phanton Reference)

垃圾收集算法

可以识别需要回收的对象后,我们还需要可以收集清理它们的算法

  • 标记-清除算法:清除后内存不连续,当有大对象进行分配时会遇到内存不足频繁full Gc
  • 复制算法:效率高,内存利用率不高
  • 标记-整理算法:效率

HotSpot的算法实现

前面介绍了如何识别对象是否生存和垃圾收集算法,而在虚拟机上实现这些算法时,必须对算法的执行效率有严格的考量,才能保证每次垃圾回收的准确和高效。

枚举根节点

要进行可达性分析就需要先收集到所有的“GC Roots”节点,目前很多应用仅仅方法区就有数百兆,如果要逐个检查这里面的引用,那么必然=会消耗很多时间。而且为了“GC Roots”数据的准备进行该动作时必须停顿所有java执行线程(Stop The World)。 为了减少枚举时间,hotspot的实现中,存在一组被称为OopMap的数据结构来达到这个目的,在类加载完成的时候,HotSpot就把对象内什么偏移量上是什么类型的数据计算出来。

安全点(safe point)

在OopMap的协助下,HotSpot可以快速准确的完成GC Roots枚举,但是如果为每一条指令都生成对应的OopMap,将会需要大量的额外空间,这样GC的成本将会变得非常高。

所以,虚拟机只有在指令序列复用的地方(方法调用,循环跳转,异常跳转)会产生一个被称为安全点的记录,OopMap只会在该位置记录,也就时程序只有到达安全点才可以执行GC。

安全区(Safe Region)

安全点基本解决了所有的GC进入问题,但是如果当前线程被中断或者挂起要如何GC呢?为了解决这种情况,虚拟机中还有安全区的概念。 安全区:在一段代码片段中,引用关系不会发生变化。


垃圾收集器

根据不同的应用,所产生的内存对象不同,要满足的业务场景也不一样,因此没有完美的垃圾收集器,需要根据实际情况去选择.

垃圾收集器时之前列举的垃圾收集算法的具体实现。

java的堆内存根据对象的生命周期也会分为新生代和老年代。新生代的对象朝生夕死,回收率高,老年代的对象存活时间长,回收率低。 两者往往会使用不同的垃圾收集器。

  • 新生代收集器
    • Serial 收集器:单线程,简单,Client模式下的首选
    • ParNew收集器:Serial的多线程版本
    • Parallel Scavenge收集器:吞吐量优先,适合后台运算多交互少的任务
  • 老年代收集器
    • Serial Old收集器:“标记-整理”,作为CMS收集失败后的后备方案
    • Parallel Old收集器:多线程+“标记-整理”,吞吐量优先
    • CMS收集器:“Concurrent Mark Sweep” 最短停顿时间,server端首选,“标记清除”
      • 初始标记(Stop The World)
      • 并发标记
      • 重新标记(Stop The World)
      • 并发清除
  • G1收集器

垃圾收集器参数总结

参数 描述
UseSerialGC 虚拟机client模式的默认配置,使用Serial+Serial Old收集器
UseParNewGC 采用ParNew+Perial Old收集器组合
UseConcMarkSweepGC 采用ParNew+CMS+Serial OLD收集器组合
UseParallelGC 虚拟机server模式下默认值,采用Parallel Scavenge + Serial OLD收集器组合
UseParallelOldGC 采用Parallel Scavenge + Parallel Old收集器组合
SurvivorRatio 新生代中Eden区域与Survivor区域的容量配比,默认是8
PretenureSizeThreshold 直接晋升老年代的对象大小
MaxTenuringThreshold 晋升到老年代的对象年龄。每个对象在坚持过一次Minor GC之后,年龄就+1
UseAdaptiveSizePolicy 动态调整Java堆中各个区域的大小以及进入老年代的年龄
HandlePromotionFailure 是否允许担保失败,即老年代的剩余空间不足以应付新生代的整个Eden和Survivor区的所有对象
ParallelGCThreads 设置并行执行GC的线程数
GCTimeRatio GC时间占总时间的比率,默认值为99,即允许1%的GC时间,仅在Parallel Scavenge收集器时有效
MaxGCPauseMillis 设置GC的最大停顿时间,仅在Parallel Scavenge收集器时有效
CMSInitiatingOccupancyFraction 设置CMS收集器在老年代空间被使用多少后触发垃圾回收,默认值68%
UseCMSCompactAtFullCollection 设置CMS收集器在完成垃圾收集后是否要进行一次内存整理
CMSFullGCsBeforeCompacation 设置CMS收集器在进行若干次垃圾收集后再启动一次内存整理

内存分配与回收策略

  • 对象优先在Eden分配
  • 大对象直接进入老年代
  • 长期存活对象进入老年代
  • 动态对象年龄判定
  • 空间分配担保