垃圾收集器(garbage
collector)是一种动态存储分配器,它自动释放程序不再需要的内存块,这些块也称为垃圾
。自动回收垃圾的过程则称为垃圾收集(garbage collection)
。在一个支持垃圾收集的语言中,程序显式地申请内存,但从不需要显式的释放它们。垃圾收集器会定期识别垃圾块,并将垃圾块放回空闲链表中。
垃圾回收策略
引用计数法
当对象被引用时程序计数器 +1,释放时 -1。当为 0 时证明对象未被引用,可以回收。
但是这个算法有明显的缺陷,对于循环引用的情况下,循环引用的对象就不会被回收。例如下图:对象 A与 B 循环引用,即便没有其他的对象引用 A 和 B,A 和 B 也都不会被回收。
可达性分析
通过一系列称之为 GC Roots
的对象作为起点,从此起点向下搜索,所走过的路径称之为引用链。当一个对象到
GC Roots 没有任何引用链相连接,代表此对象不可达。
在 Java 可以作为 GC Roots 的对象包括:
- 虚拟机栈(帧栈中的局部变量表)中的引用对象;
- 方法区中类静态属性(static)引用的对象;
- 方法区中常量(constant)引用的对象;
- 本地方法栈中 JNI(即一般说的 Native 方法)的引用对象;
垃圾回收算法
标记-清除算法(Mark-Sweep)
首先利用可达性去遍历内存,把存活对象和垃圾对象进行标记。标记结束后统一将所有标记的对象回收掉。
- 标记阶段:标记出所有需要回收的对象;
- 清除阶段:回收被标记的对象所占用的空间。
优点:实现简单
缺点:效率较低,并且会产生大量不连续的内存空间碎片,后续可能发生大对象不能找到可利用空间的问题
标记-整理算法(Mark-Compact)
标记后不是清理对象,而是将存活对象移向内存的一端。然后清除端边界外的对象。
复制算法(Copying)
按内存容量将内存划分为等大小的两块。每次只使用其中一块,当这一块内存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉。
优点:实现简单,运行高效,不易产生碎片
缺点:可用内存缩小为了原来的一半,浪费空间。若存活对象增多的话,该算法的效率会大大降低
分代收集算法(Generational Collection)
分代收集法是目前大部分 JVM 所采用的方法,GC 堆划分为新生代(Young Generation)和老生代(Tenured/Old Generation)。新生代和老年代的默认比例为 1 : 2;新生代又分为 Eden 区, from Survivor 区(简称 S0 ),to Survivor 区(简称 S1 ),三者的比例为 8: 1 : 1。
我们把新生代发生的 GC 称为 Young GC(也叫 Minor GC ),老年代发生的 GC 称为 Old GC(也称为 Full GC )。新老生代会使用不同的垃圾回收算法,一般情况下
- 新生代使用复制算法
- 老年代使用标记清除算法或者标记整理算法
在新生代中,每次垃圾收集时都有大批对象死去,只有少量存活,使用复制算法比较合适,只需要付出少量存活对象的复制成本就可以完成收集。老年代对象存活率高,适合使用标记-清理或者标记-整理算法进行垃圾回收。
对象的内存分配主要在新生代的 Eden Space
和 Survivor 区的
From Space
(Survivor
目前存放对象的那一块),少数情况会直接分配到老生代。当新生代的 Eden
Space 和 From Space 空间不足时就会发生一次 GC,进行 GC 后,Eden Space 和
From Space 区的存活对象会被挪到 To Space,然后将 Eden Space 和 From
Space 进行清理。如果 To Space
无法足够存储某个对象,则将这个对象存储到老生代。在进行 GC 后,使用的便是
Eden Space 和 To Space 了,如此反复循环。当对象在 Survivor 区躲过一次 GC
后,其年龄就会+1。默认情况下年龄到达 15 的对象会被移到老生代中。