垃圾回收与算法
GC要做的三件事:
- 哪些内存需要回收?(what)
- 什么时候回收?(when)
- 怎么回收?(how)
1 如何确定垃圾
由于引用计数法的循环引用问题,Java 使用的是可达性分析的方法。
1.1 引用计数法(Reference-Counting)
每个对象有一个引用计数器,当对象被引用一次则计数器加1,当对象引用失效一次则计数器减1,对于计数器为0的对象意味着是垃圾对象,可以被GC回收。
1.2 可达性分析(GC Roots Tracing)
从 GC Roots 作为起点开始搜索,那么整个连通图中的对象便都是活对象。如果在 GC Roots 和一个对象之间没有可达路径,则称该对象是不可达的。
不可达对象不等价于可回收对象,不可达对象变为可回收对象至少要经过两次标记过程。两次标记后仍然是可回收对象,则将面临回收。
2 回收垃圾的算法
2.1 标记清除算法(Mark-Sweep)
最基础的垃圾回收算法,分为两个阶段:
- 标记
标记出所有需要回收的对象 - 清除
回收被标记的对象所占用的空间
缺点:内存碎片化严重,后续可能发生大对象找不到可利用空间的问题。
2.2 复制算法(Copying)
为了解决标记清除算法内存碎片化的缺陷而提出的算法。
按内存容量将内存划分为等大小的两块,每次只使用其中一块。
当这一块内存存满后将尚存活的对象复制到另一块上去,把已使用的内存清掉:
优点:实现简单,内存效率高,不易产生碎片。
缺点:可用内存被压缩到了原来的一半。存活对象增多时,复制算法的效率会大大降低。
2.3 标记整理算法(Mark-Compact)
结合了以上两个算法而提出。
- 标记
标记出需要清理的垃圾。 - 整理
标记后不是清理对象,而是将存活对象移向内存的一端,然后清除端边界外的对象。
2.4 分代收集算法
分代收集法是目前大部分 JVM 所采用的方法。
其核心思想是根据对象存活的不同生命周期将内存划分为不同的域,一般情况下将 GC 堆划分为老生代(Tenured/Old Generation)和新生代(Young Generation)。
老生代的特点是每次垃圾回收时只有少量对象需要被回收,新生代的特点是每次垃圾回收时都有大量垃圾需要被回收,因此可以根据不同区域选择不同的算法。
2.4.1 新生代与复制算法
目前大部分 JVM 的 GC 对于新生代都采取复制算法,因为新生代中每次垃圾回收都要回收大部分对象,即要复制的操作比较少。
但通常并不是按照 1:1 来划分新生代。一般将新生代划分为一块较大的 Eden 空间和两个较小的 Survivor 空间(From Space, To Space),每次使用Eden 空间和其中的一块 Survivor 空间,当进行回收时,将该两块空间中还存活的对象复制到另一块 Survivor 空间中。
详见JVM运行时内存
2.4.2 老年代与标记整理算法
老年代因为每次只回收少量对象,因而采用标记整理算法。
详见JVM运行时内存