继续参考《深入理解java虚拟机》来回顾下java的垃圾收集器相关的知识(第三章)
垃圾回收
垃圾回收可以说时java中的一大特色,那么java时如何自动判断什么样的对象应该被回收的呢? 常用的算法时下面两种
- 引用计数法
- 可达性分析法
引用计数法
引用计数法的实现比较简单:给对象添加一个引用计数器,每当有一个地方引用对象,计数器值就加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分配
- 大对象直接进入老年代
- 长期存活对象进入老年代
- 动态对象年龄判定
- 空间分配担保