【深入理解JVM】垃圾回收

对象是否还活着

只有判断对象是否还有引用指向它才能决定是否回收,所以回收的第一步就是判断:“对象是否还活着?

判断对象的存活状态有两种方式

引用计数器

有一个引用指向该对象计数器就加 1,一个引用失效计数器就减 1,当且仅当一个对象的计数器为 0 的时候,被下一次 GC 回收。

引用计数器法简单粗暴,但是有一个不足的地方,那就是碰到“相互循环引用”时,引用无法进行回收。

相互循环引用,两个对象的属性中都含有对方的引用,但是除此之外再无其他引用。这样的情况下虽然外界已经无法引用到这两个对象,但是他们的各自的计数器不为 0,不能被回收。

可达性分析算法

该判断一个对象的存活状态的方式可以解决“相互循环引用”的问题。

  1. 通过创建一组 GCRoot 指针来管理当前被引用的对象,被外界引用的对象 A 就挂在 GCRoot 指针上,如果 A 的属性中引用了对象 B,就将 B 挂在 A 的后面,以此类推,形成树形结构,我们叫做 GCRoot 树(以 GCRoot 为树根的树结构)。
  2. 如果一个被外部引用的对象跟其他任何一个 GCRoot 树中的节点都没关系,就创建一个新的 GCRoot 指针,组成一棵新的树。
  3. 如果外界引用减少了一个,就从对应的 GCRoot 树中撤去一条树枝。
  4. 当一棵树的树根不是 GCRoot 对象的时候,那么就不存在外界的任何一个引用,因此该树上的所有对象都为死亡状态

垃圾回收是判断一个对象是否存在的原则就是:该对象到 GCRoot 指针是否可达,如下图, Object 5 就是一个不可达的例子,下一次回收的时候会将其回收(包括 Object6 和 Object7)。

可达性分析

对象的引用

一个对象的引用是一个对象存在的根本。只有对引用有了良好的等级划分,才能使得垃圾回收变得“智能”。引用分为四类:强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)和虚引用(Phantom Reference)

强引用

该引用关系最强,代表对象现在富有活力,不能被垃圾回收机制回收,比如 Object strong = new Object();

软引用

只有但内存空间不足的时候,GC 才会将这块内存收回

弱引用

在下一次 GC 时被回收

虚引用

这是最弱的一种关系,不会对对象的生命周期产生任何影响,也无法通过该引用获得对象

finalize方法

一个对象不可达,可以说“行将就木”的时候,并非是“非死不可”,它只是被判了死刑,还没有执行。在这段期间,如果通过 finalize 方法,或许能够让它逃过 GC 的鬼门关

public class OneObject {

    private static OneObject hook = null;

    ...

    protected void finalize() {
        super.finalize();
        // 挽救它
        OneObject.hook = this;
    }
}

该方法只有被重写并且没有被标记执行过的时候,才有可能执行,否则永远不会被执行。JVM 中会维护一个 F-Queue,所有未被标记的 finalize 方法将会放入该队列中,由一个低优先级的 finalize 线程执行该队列中的方法,被执行的(并不一定执行完)方法将被标记,移除队列。

但是 GC 不会等着该类的 finalize 方法完全执行完才会进行垃圾回收。

原因是:如果该 finalize 方法执行的时间比较长,甚至是一个死循环,那么将会影响其他对象的回收,最终是的 GC 崩溃。

因此如果使用 finalize 方法做一些操作是很不安全的,最好的选择是 try...finally 语句块。

方法区回收

方法区都是一些生命周期比较长的数据,回收一次获得的空闲内存不是很大。主要是回收不使用的常量无用的类的元信息

判断一个类还有没有使用相当严格的(因为一个类的加载是很耗资源的),需要满足如下的所有条件:

  • 该类的实例全部被回收
  • 该类的 ClassLoader 已经被回收
  • 该类的 Class 对象没有任何地方引用

垃圾回收算法

确定了那些是“垃圾”,就开始回收吧。

标记清除算法

将无用的对象进行标记,一次性清除所有被标记的对象。

这样的缺点就是产生了大量的碎片,不利于内存管理。但是它是所有回收算法的“鼻祖”,其他的算法大多是对它的一个改进。

复制算法

将内存划分为相等的两块,一块使用,另一块备用。当使用块用完了,将使用块中的 Active 对象规规整整复制到另一块中,清空使用块。这样往复使用。

这样就造成了 4G 内存,实际只有 2G 了,代价太大。但是不会产生内存碎片的问题。

针对代价巨大的缺点,有许多改进的方案。

因为大多数对象的生命周期都比较短,所以将内存分为 3 块(一大两小,大的叫做 Eden,小的叫做 Survivor)。

使用的时候使用 Eden 和一个 Survivor,复制的时候将 Eden 和 Survivor 中的 Active 对象复制到另外一个 Survivor 中,然后进行清除。

标记整理

将零碎的 Active 对象整理成规整的一块内存,清除最后一个对象存储块后面的所有数据。

分代收集

根据对象的生命周期不同(新生代、老年代),将内存划分为块。针对不同“代”使用不同的收集策略。比如新生代的生命周期短,就用复制算法;老年代的生命周期长,就用标记整理算法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值