首页 > 代码库 > JVM垃圾回收

JVM垃圾回收

一.如何确定某个对象是“垃圾”?

二.典型的垃圾收集算法

三.典型的垃圾收集器

一.如何确定某个对象是“垃圾”?

在java中是通过引用来和对象进行关联的,也就是说如果要操作对象,必须通过引用来进行,那么显然一个简单的办法就是通过引用计数来判断一个对象是否可以被回收。如果一个对象没有任何引用与之相关联,则说明改对象基本不太可能在其他地方被使用到,那么这个对象就称为可被回收的对象了,这种方法称为引用计步法

该方法实现简单,而且效率高,但是无法解决循环引用问题,因此在Java中并没有采用该方法。

public class Main {
    public static void main(String[] args) {
        MyObject object1 = new MyObject();
        MyObject object2 = new MyObject();

        object1.object = object2;
        object2.object = object1;
        //虽然赋值了null,但是两个对象相互引用,计步器都不是0,垃圾垃圾回收器就不会回收
        object1 = null;
        object2 = null;
    }
}
 
class MyObject{
    public Object object = null;
}

为了解决这个问题,在Java中采用了可达性分析法。

  该方法基本思想是通过一系列的"GC Roots"对象作为起点进行搜索,如果和一个对象没有路径可达,则该对象称为不可达对象,不可达对象不一定会回收,不可达对象要成为可回收对象必须要经历两次标记过程,如果在这两次标记过程中仍然没有逃脱成为可回收对象的可能性,则基本上真的成为可回收对象了。

  

标记-清除算法

技术分享

 

缺点

  1. 标记与清除效率低;
  2. 清除之后内存会产生大量碎片;

标记-整理算法

不直接对可回收对象进行清理,而是让所有可用的对象都向一端移动。然后直接清理掉边界意外的内存。

技术分享

很显然,整理这一下需要时间,所以与标记清除算法相比,这一步花费了不少时间,但从长远来看,这一步还是很有必要的。

该算法可谓“道德高尚,自己栽树,后人乘凉”

copying算法

首先将内存分为大小相等的两部分(假设A、B两部分),每次呢只使用其中的一部分(这里我们假设为A区),等这部分用完了,这时候就将这里面还能活下来的对象复制到另一部分内存(这里设为B区)中,然后把A区中的剩下部分全部清理掉。

这样一来每次清理都要对一半的内存进行回收操作,这样内存碎片的问题就解决了,可以说简单,高效。

技术分享

但是呢,肯定发现了,本来挺大一片地方,现在只能用一半,搞得挺不爽的,世界上本来没有免费的饭菜,就算是用空间换取时间吧。

分代的垃圾回收

新生代,每次垃圾收集器都发现有大批对象死去,只有少量存活,采用复制算法,只需要付出少量存活对象的复制成本就可以完成收集,新生代区分为Eden区和两个相同大小的Survivor区,其中所有的新创建对象都分配在Eden区,当Eden区满后会触发minor GC将Eden区中仍然存活的对象复制到其中一个Survivor区,另一个Survivor区中的存活对象也复制到这个Survivor中,并始终保持一个SUrvivior区时空中。当对象从这块内存区域消失时,我们说发生了一次“minor GC”。

老年代,由于老年代中对象存活率高,没有额外的空间对他进行分配,就必须用“标记-清除-压缩”算法进行回收。新创建的对象分配在新生代中,如果对象经过几次回收之后仍然存活,那么就把这个对象划分到老年代。老年代存放新生代区Survivor满后处罚minor GC后仍然存活的对象,当Eden区满后会将存活的对象放入Survivor区,如果Survivor区放不下这些对象,GC收集器就会将这个对象直接放入老年代,如果Surivivor区中的对象足够老,也直接放入到老年代,如果老年代区满了,就会处罚Full GC回收整个堆内存。对象从老年代消失时,我们说“major GC”(或“full GC”)发生了。

方法区(method area):他存储class对象和字符串常量。所以这块内存区域绝对不是永久的存放从老年代存活下来的对象的。在这块内存中有可能发生垃圾回收。发生在这里垃圾回收也被称为major GC。

 

JVM垃圾回收