给对象添加一个引用计数器,每当有一个地方引用它时,计数器加1,引用失效计数器减1,任何时刻计数器为0的对象就是不可能再被使用的。
应用场景:
ActionScript3
的FlashPlayer
缺点:
通过一系列GC Roots
的对象作为起始点,从这些节点往下搜索,搜索走过的路径称为引用链 (Reference Chain),当一个对象到GC Roots
没有任何引用链相连时,则证明此对象不可用。
可作为GC Roots
对象包括以下几种
JNI
(native方法)引用的对象算法分为“标记”和“清除”两个阶段,首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,一般采用可达性分析算法。
缺点:
1. 效率低,标记和清除两个过程效率都不高
2. 空间问题,标记清除之后会产生大量不连续的内存碎片,可能会导致无法分配较大对象而触发GC
为了解决效率问题,一种称为“复制”(Copying)的收集算法出现了,它将可用内存按容量分为大小相等的两块,每次使用一块。当一块内存用完了,就将存活的对象复制到另一块,然后将原来的那块全部回收,也就解决了内存碎片的问题,只要移动堆顶指针,按顺序分配内存即可,实现简单,运行高效。
缺点:内存只有一半可用,土豪算法。
不过这个内存比例并不一定是1:1
的,现代的商业虚拟机都采用这种算法来回收新生代
,IBM公司研究表明,98%的对象都是朝生夕死
的。一般新生代被分为一个较大的Eden和两个较小的survivor,一般比例是8:1:1
,新生代可用的内存区域是Eden和一块survivor,也就是新生代内存利用率为90%。
我们不能保证每次回收都只有不多于10%的对象存活,此时需要依赖其他内存(老年代)进行担保分配。
复制算法在对象存活率高时,复制效率会降低,更关键的是,如果不想浪费50%的空间,就需要有额外的担保空间进行分配担保,以应对被使用额内存中所有对象都100%存活的极端情况,所以老年代一般不能直接使用复制算法。
整理过程是让存活的对象往一端移动,然后将存活端边界以外的内存进行清理。
当代商业虚拟机的垃圾收集都采用“分代收集”算法,这种算法没有引入新的思想,只是根据对象的存活周期将内存划分为几块。
一般将Java堆分为新生代和老年代,这样就可以根据各个年代的特点采用最适当的收集算法。
在新生代中,每次垃圾收集都发现有大批对象死去,只有少量存活,那就选用“复制算法”,只需要复制少量的存活对象就可以完成收集。
在老年代中,因为存活率极高,没有额外的空间担保分配,就必须使用“标记-清理”或“标记-整理”算法来进行回收。
从可达性分析中从GC Roots
节点查找引用链这个操作为例,可作为GC Roots
的节点主要在全局性的引用(常量/类静态属性...)与执行上下文(例如栈帧中的本地变量表)中,现在很多应用仅仅方法区就数百兆,如果要逐个检查这里面的引用,那么必然会消耗很多时间。
另外,可达性分析对执行时间的敏感还体现在GC停顿上,因为分析时必须在一个能确保一致性的快照中进行。这里的“一致性”的意思是指在整个分析过程,整个执行系统看起来就像冻结在某个时间点上,不可用出现对象引用关系还在不断变化的情况,否则分析结果无法保证准确性。
要实现“快照”,必须停顿所有的Java执行线程(SUN称为“Stop The World”)的其中一个重要原因,即使在号称几乎不会发生停顿的CMS收集器中,枚举根节点也是必须停顿的。
由于目前主流的Java虚拟机使用的都是准确式GC,所以当执行系统停顿下来后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,虚拟机应当有办法直接得知哪些地方存放着对象引用。
在Hotspot实现中,使用了OopMap数据结构。在类加载完成时,Hotspot就把对象内什么偏移量上是什么类型的数据计算出来,在JIT编译过程中,也会在特定的位置记录下栈和寄存器中哪些位置是引用。
在OopMap的协助下,Hotspot可以快速完成GC Roots枚举,但是当引用关系变化,或者说OopMap内容变化的指令非常多,如果为每一条指令都生产对于的OopMap,那将会需要大量的额外空间。
GC发生时如何让所有执行线程都运行到最近的安全点上再停顿下来,有两种方案实现:
抢占式中断
首先中断全部线程,如果有线程没到安全点,就恢复线程,让他执行到安全点。
主动式中断
当GC需要中断线程时,不直接对线程操作,仅仅做个标志,各个线程执行时主动轮询这个标志,发现中断标志为真时就把自己挂起。
似乎安全点解决了如何进入GC的问题,但是并不,当某线程不执行的时候,例如处于block/sleep状态时,此时需要安全区域解决。
安全区域是指在一段代码片段之中,引用关系不会发生变化。在这个区域内的任意地方开始GC都是安全的。也可以将安全区域看成安全点的扩展。
单线程收集器,执行收集时,暂停其他全部线程。
Client模式的虚拟机下的默认新生代收集器。
Serial收集器的多线程版本,依然要停止用户线程,只是新生代多线程收集。
Server模式下首选的新生代收集器,因处理Serial收集器外,唯一一个可以与CMS收集器配合工作。
吞吐量优先收集器,可以指定一定内完成收集工作,收集器将尽可能保证完成任务,不过这种速度是利用新生代空间换来的,如果时间调小了,意味着新生代空间变小,意味着可能导致频繁触发GC。
当配置了-XX:UseAdaptiveSizePolicy
参数时,就不需要手动配置新生代的分配比例、晋升老年代对象大小等细节参数了,虚拟机会根据系统运行状况收集性能监控信息动态调整这些参数,这种调节方式称为GC的自适应调节策略(GC Ergonomics)。
自适应策略也是其与ParNew收集器重要区别。
Serial收集器的老年代版本,采用“标记-整理”算法。
Parallel Scavenge的老年代版本,使用多线程和“标记-整理”算法。JDK1.6开始发布,在1.6之前,Parallel Scavenge收集器极其尴尬,除了Serial Old(PS MarkSweep)别无选择。
CMS收集器是一种以获取最短回收停顿时间为目标的收集器。
适用于B/S系统的服务器上。
标记过程:
缺点:
CMS收集器对CPU资源非常敏感
CMS收集器无法收集浮动垃圾,可能出现Concurrent Mode Failture
失败而导致另一次Full GC的产生。
-XX:CMSInitiatingOccupancyFraction
基于标记-清除算法带来的大量内存碎片
-XX:+UseCMSCompactAtFullCollection
开关参数(默认开启),用于在CMS收集器顶不住要进行FullGC时开启内存碎片整理。-XX:CMSFullGCCsBeforeCompaction
G1收集器是当今收集器最前沿的成果之一,它是面向服务端的垃圾收集器。
特点:
G1
不需要其他收集器配合就可以独自完成收集,但它能够采用不同的方式去处理新创建的对象和已经存活了一段时间、熬过多次GC的旧对象以获取更好的收集效果。运作流程:
-XX:+PrintGCDetails:打开内存回收日志