Java垃圾回收机制(JVM-GC)

一、什么是JVM的垃圾回收机制

释放垃圾占用的空间,防止内存泄漏。有效的使用可以使用的内存,对内存堆已经死亡或者长时间没有使用的对象进行清除和回收。

二、怎么定义垃圾

1、引用计数法

在堆中存储对象时,在对象头处维护一个counter计数器,如果一个对象增加了一个引用与之相连,则将counter++。如果一个引用关系失效则counter–。如果一个对象的counter变为0,则说明该对象已经被废弃,不处于存活状态。

Java垃圾回收机制(JVM-GC)插图

1)优点

逻辑简单

2)缺点

引用和去引用伴随加法和减法,影响性能

3)致命的缺陷

对于循环引用的对象无法进行回收

Java垃圾回收机制(JVM-GC)插图2
最右边那张图,循环引用的计数器都不为0,但是他们对于根对象都已经不可达了,但是无法释放。

循环引用的例子:

public class Object {

    java.lang.Object field = null;

    public static void main(String[] args) {
               Object objectA = new Object();
               Object objectB = new Object();//位置1
                objectA.field = objectB;
                objectB.field = objectA;//位置2
                objectA = null;
                objectB = null;//位置3
    }
}

当执行到位置1的时候,两个对象的引用计数器是1
执行到位置2,两个对象的引用计数器是2
执行到位置3,两个对象各自释放一个,引用计数器变成1
上面代码如果采用引用计数法,则两个对象就不能回收(实际上可以回收)。

2、可达性分析

通过一系列的名为“GC Root”的对象作为起点,从这些节点向下搜索,搜索所走过的路径称为引用链(Reference Chain),当一个对象到GC Root没有任何引用链相连时,则该对象不可达,该对象是不可使用的,垃圾收集器将回收其所占的内存。所以JVM判断对象需要存活的原则是:能够被一个根对象到达的对象。

什么是可达呢?就是对象A中引用了对象B,那么就称A到B可达。

Java垃圾回收机制(JVM-GC)插图4

哪些可以做GC root

  1. java虚拟机栈中的引用的对象。
  2. 方法区中的类静态属性引用的对象。 (一般指被static修饰的对象,加载类的时候就加载到内存中。)
  3. 方法区中的常量引用的对象。
  4. 本地方法栈中的JNI(native方法)引用的对象。

三、垃圾回收算法

1、标记-清除

标记-清除算法,将垃圾回收分为两个阶段:标记阶段和清除阶段。

一种可行的实现是,在标记阶段,首先通过根节点,标记所有从根节点开始的可达对象。因此,未被标记的对象就是未被引用的垃圾对象;然后,在清除阶段,清除所有未被标记的对象。

Java垃圾回收机制(JVM-GC)插图6
通过可达性分析,标记出可以回收的内存
Java垃圾回收机制(JVM-GC)插图8

这种方式清理出来的空闲内存是不连续的

2、复制算法

将原有的内存空间分为两块,每次只使用其中一块,在垃圾回收时,将正在使用的内存中的存活对象复制到未使用的内存块中,之后,清除正在使用的内存块中的所有对象,交换两个内存的角色,完成垃圾回收。

Java垃圾回收机制(JVM-GC)插图10
  • 与标记-清除算法相比,复制算法是一种相对高效的回收方法
  • 不适用于存活对象较多的场合,如老年代(复制算法适合做新生代的GC)
  • 堆得内存利用率不高,只能用一半

如果在对象存活率较高时就要进行较多的复制操作,效率将会变低。更关键的是,如果不想浪费50%的空间,就需要有额外的空间进行分配担保,以应对被使用的内存中所有对象都100%存活的极端情况,所以在老年代一般不能直接选中这种算法。

3、标记-整理

标记过程仍与”标记-清除”中标记的过程一致,但后续步骤不是直接对可回收对象进行清理,而是让所有存活对象都向一端移动,然后直接清理掉端边界(除存活对象)以外的内存。

一句话:先标记,再整理,最后删除。最终避免内存碎片的产生。

4、分代收集算法:(新生代的GC+老年代的GC)

只是根据对象的存活周期的不同将内存划分为几块儿。一般是把Java划分为新生代和老年代:短命对象归为新生代,长命对象归为老年代

  • 少量对象存活,适合复制算法:在新生代中,每次GC时都发现有大批对象死去,只有少量存活,那就选用复制算法,只需要付出少量存活对象的复制成本就可以完成GC。
  • 大量对象存活,适合用标记-清理/标记-整理:在老年代中,因为对象存活率高、没有额外空间对他进行分配担保,就必须使用“标记-清理”/“标记-整理”算法进行GC。

发表评论