确定垃圾
引用计数法
在java中是通过引用来和对象进行关联的,可以通过引用计数来判断一个对象是否可以被回收。如果一个对象没有任何引用与之关联,那么这个对象就成为可被回收的对象了。这种方式称为引用计数法。
这种方式的特点是实现简单,而且效率较高,但是它无法解决循环引用的问题,因此在Java中并没有采用这种方式。
可达性分析
通过一系列的“GC Roots”对象作为起点进行搜索,如果在“GC Roots”和一个对象之间没有可达路径,则称该对象是不可达的,不过被判定为不可达的对象不一定就会成为可回收对象。被判定为不可达的对象要成为可回收对象必须至少经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上就真的成为可回收对象了。
使用OopMap记录并枚举根节点
HotSpot首先需要枚举所有的GC Roots根节点,虚拟机栈的空间不大,遍历一次的时间或许可以接受,但是方法区的空间很可能就有数百兆,遍历一次需要很久。更加关键的是,当我们遍历所有GC Roots根节点时,我们需要暂停所有用户线程,因为我们需要一个此时此刻的”虚拟机快照”,如果我们不暂停用户线程,那么虚拟机仍处于运行状态,我们无法确保能够正确遍历所有的根节点。所以此时的时间开销过大更是我们不能接受的。
基于这种情况,HotSpot实现了一种叫做OopMap的数据结构,这种数据结构在类加载完成时把对象内的偏移量是什么类型计算出,并且存放下位置,当需要遍历根结点时访问所有OopMap即可。
用安全点Safepoint约束根节点
如果将每个符合GC Roots条件的对象都存放进入OopMap中,那么OopMap也会变得很大,而且其中很多对象很可能会发生一些变化,这些变化使得维护这个映射表很困难。实际上,HotSpot并没有为每一个对象都创建OopMap,只在特定的位置上创建了这些信息,这些位置称为安全点(Safepoints)。
为了保证虚拟机中安全点的个数不算太多也不是太少,主要决定安全点是否被建立的因素是时间。当进行了耗时的操作时,比如方法调用、循环跳转等时会产生安全点。此外,HotSpot虚拟机在安全点的基础上还增加了安全区域的概念,安全区域是安全点的扩展。在一段安全区域中能够实现安全点不能达成的效果。
可作为GC Roots的对象包括:
- 虚拟机栈(栈帧中的本地变量表)中引用的对象
- 方法区中类静态属性引用的对象
- 方法区中常量引用的对象
- 本地方法栈中JNI(即一般说的Native方法)引用的对象
1. 第一次标记并进行一次筛选。
筛选的条件是此对象是否有必要执行finalize()方法。
当对象没有覆盖finalize方法,或者finzlize方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”,对象被回收。
2. 第二次标记
如果这个对象被判定为有必要执行finalize()方法,那么这个对象将会被放置在一个名为:F-Queue的队列之中,并在稍后由一条虚拟机自动建立的、低优先级的Finalizer线程去执行。这里所谓的“执行”是指虚拟机会触发这个方法,但并不承诺会等待它运行结束。这样做的原因是,如果一个对象finalize()方法中执行缓慢,或者发生死循环(更极端的情况),将很可能会导致F-Queue队列中的其他对象永久处于等待状态,甚至导致整个内存回收系统崩溃。
Finalize()方法是对象脱逃死亡命运的最后一次机会,稍后GC将对F-Queue中的对象进行第二次小规模标记,如果对象要在finalize()中成功拯救自己----只要重新与引用链上的任何的一个对象建立关联即可,譬如把自己赋值给某个类变量或对象的成员变量,那在第二次标记时它将移除出“即将回收”的集合。如果对象这时候还没逃脱,那基本上它就真的被回收了。
引用分类
- 强引用:类似Object obj = new Object();永远不会回收。
- 软引用:内存不足时回收对象,可以很好地解决OOM问题,适合用来实现缓存。
- 弱引用:垃圾回收时,无论内存是否充足,都会回收被弱引用关联的对象。
- 虚引用:任何时候都能被回收,
垃圾收集算法
标记清除法
标记阶段:标记出所有需要被回收的对象。
清除阶段:清除所有被标记的对象。
容易产生内存碎片,碎片过多会导致后续过程中需要大对象分配内存时无法找到足够大的空间而提前触发新的一次垃圾收集动作。
复制法
将内存按容量划分为大小相等的两块,每次使用其中一块,当其中一块内存用完了就将活着的对象复制到另一块中,然后一次清理掉这一块内存。
避免了内存碎片的问题,但内存使用率只有50%。如果存活对象很多,算法效率将大大降低。
标记整理法
标记阶段标记出所有需要被回收的对象,然后将存活对象都向一端移动,然后清理掉端边界以外的内存。
分代收集法
根据对象的存活的生命周期将内存划分为若干个不同的区域,一般划分为新生代和老年代,老年代每次垃圾收集时只有少量对象需要被回收,新生代每次垃圾回收都有大量的对象需要被回收。
新生代:采取复制法,每次垃圾回收要复制的对象比较少,且实际比例是8:1:1的Eden和两个Survivor,回收时将Eden和Survivor中的存活对象复制到另一个Survivor中,然后清理。
老年代:采用标记整理法。
永久代:堆区之外,用来存储class类,常量,方法描述等,对永久代的回收主要回收两部分内容:废弃常量和无用的类。
垃圾收集器
Serial | 1、新生代收集器,可以和Serial Old、CMS组合使用 2、采用复制算法 3、使用单线程进行垃圾回收,回收时会导致Stop The World,用户进程停止 4、Client模式新生代收集器 |
ParNew | 1、新生代收集器,可以和Serial Old、CMS组合使用 2、采用复制算法 3、使用多线程进行垃圾回收,回收时会导致Stop The World,其它策略和Serial一样 4、许多虚拟机Server模式的新生代收集器 |
Parallel Scavenge | 1、新生代收集器,可以和Serial Old、Parallel组合使用,不能和CMS组合使用 2、采用复制算法 3、使用多线程进行垃圾回收,回收时会导致Stop The World 4、关注吞吐量 |
Serial Old | 1、年老代收集器,可以和所有的年轻代收集器组合使用,Serial收集器的年老代版本 2、标记-整理算法,会对垃圾回收导致的内存碎片进行整理 3、使用单线程进行垃圾回收,回收时会导致Stop The World,用户进程停止 |
Parallel Old | 1、年老代收集器,只能和Parallel Scavenge组合使用,Parallel Scavenge收集器的年老代版本,Stop The World 2、多线程,采用标记-整理算法,会对垃圾回收导致的内存碎片进行整理 3、关注吞吐量的系统可以将Parallel Scavenge+Parallel Old组合使用 |
CMS | 1、年老代收集器,可以和Serial、ParNew组合使用 2、采用标记-清除算法,可以通过设置参数在垃圾回收时进行内存碎片的整理 3、CMS是并发算法,表示垃圾回收和用户进行同时进行,但是不是所有阶段都同时进行,在初始标记、重新标记阶段还是需要Stop the World。 4、CMS垃圾回收分这四个阶段,三次标记一次回收: ④ 并发清除,清除的同时用户进程会导致新的垃圾,时间长 5、适合于对响应时间要求高的系统,以最短回收停顿时间为目标 缺点: ① 对CPU资源非常敏感,并发且维护用户进程的代价 ② 无法处理浮动垃圾,清除时产生新垃圾 ③ 由于使用标记清除,故有空间碎片 |
G | 1、并行并发,使用多个CPU缩短STW的时间 2、分代收集,不需要其他收集器配合也能独立管理堆 3、空间整合,整体基于标记整理算法,局部两个Region基于复制 4、可预测停顿,可以指定时间段M内GC过程时间不超过N 5、较低停顿,停顿时间更加可控可预测 特殊点: ① 新生代和老年代不再物理隔离,都属于一部分Region的集合,将堆分为大小相等的Region。 ② G1跟踪各个Region垃圾的价值大小以及回收需要时间维护一个Region优先列表,每次先回收价值最大的Region,这是G1-Garbage First名字的由来 ③ G1运作分四个阶段,三次标记一次回收 初始标记,并发标记,最终标记,筛选回收 |
分类
根据线程分类 | |||
多线程 | 单线程 | ||
ParNew、Parallel Scavenge、Parallel Old、CMS、G1 | Serial、Serial Old | ||
根据年代分类 | |||
新生代 | 老年代 | ||
Serial、ParNew、 Parallel Scavenge | Serial Old、Parallel Old、CMS | ||
G1 | |||
根据算法分类 | |||
复制 | 标记清除 | 标记整理 | |
Serial、ParNew、Parallel Scavenge | CMS | Serial Old、G1、Parallel Old |