GC 算法
GC 即 Garbage Collection 垃圾回收。JVM 中的 GC 99%发生在堆中,而 Java 堆中采用的垃圾回收机制为分代收集算法。即将堆分为新生代和老年代,根据不同的区域使用不同的垃圾回收算法。
1. 确认垃圾的算法
1.1 引用计数法
给每个对象设置一个引用计数器,每有一个地方引用此对象时,计数器 +1,引用失效计数器 -1。当对象计数器为0时,将会被垃圾回收。
引用计数器无法解决循环引用的问题。当AB两个对象都无其他对象引用,且 A 对象引用 B 对象,B 对象也引用 A 对象时,两个对象本应被回收,但计数器都不为0,无法垃圾回收。
因此主流虚拟机都未采用此算法。
1.2 可达性算法(引用链法)
从一个被视为 GC Roots 的对象开始向下搜索,如果一个对象到 GC Roots 没有任何引用链相连时,说明此对象不可用,可以被垃圾回收。
Java 中可作为 GC Roots 的对象如下:
虚拟机栈中引用的对象
方法区类静态属性引用的对象
方法区常量池引用的对象
本地方法栈 JNI 引用的对象
当一个对象不可达 GC Roots 时,这个对象不会立马被回收,而是处于死缓的阶段,若要真正被回收需要经历两次标记。
被第一次标记的对象会经历一次筛选,筛选是否有必要进行finalize()
方法,对象如果已被虚拟机调用过或认定没必要执行finalize()
方法,则立即回收。
如果需要执行finalize()
方法,则把对象放入 F-Queue 队列中,等待Finalize()
线程执行,该线程为低优先级,且不被 JVM 承诺执行完毕。GC对 F-Queue 队列中的对象进行二次标记,被标记后等待回收。
2. 垃圾回收算法
2.1 标记清除算法 Mark-Sweep
最基础的垃圾回收算法,分两个阶段,标记,清除。标记所需回收的对象,清除被标记的对象。
看似简单,但该算法最大的问题在于内存碎片化严重,如果清理后不整理内存,整块内存就会被存活的对象分割成一块块较小的连续空间,当需要创建较大对象时会难以在内存中找到合适的连续空间。
节约空间
需要进行两次扫描,耗时严重
内存碎片化严重
老年代标记清除,标记压缩结合使用
2.2 复制算法 Copying
为了解决标记清除算法内存碎片化的缺陷而被提出。
按内存容量将内存划分为两块,每次只使用其中一块,当一块内存存满后,将存活的对象复制至另一块,清空当前块内的内存,然后两边互换使用。
算法实现简单,内存效率高,不易产生碎片
使用空间被压缩,浪费了一点空间,如果存活对象增多,算法效率会大大降低
2.3 标记整理算法 Mark-Compact
结合标记清除算法和复制算法被提出。
算法思想:
标记死亡对象
将存活对象移动至内存另一端
清除端边界外所有对象
特点:
内存连续,不产生碎片
效率不高,要标记存活对象,整理对象引用地址,效率低于复制算法
老年代标记清除,标记压缩结合使用