《深入理解Java虚拟机》读书笔记(三)

BF,技术帖,Java Virtual Machine 2017-05-09

第二部分 自动内存管理机制
第三章 垃圾收集器与内存分配策略

GC算法与GC

目录

  • 概述
  • 对象与引用
    • 引用计数法
    • 可达性分析算法
    • Java的4种引用类型
    • 对象回收流程
    • 方法区回收
  • GC算法
  • HotSpot算法实现
  • GC分类
  • 内存分配与回收策略
    1074438-20161220195718745-515949846.jpg
概述 GC(Garbage Collection)是垃圾收集的简称,比Java的历史更加久远。经过半个多世纪的发展,已经实现了自动化。作为学习,我们需要搞清楚GC的三件问题:
  1. 哪些内存需要回收?
  2. 什么时候回收?
  3. 如何回收?

前面的文字记录了Java运行时区域,其中程序计数器,Java虚拟机栈和本地方法栈是线程隔离的,随线程生而生,随线程灭而灭。每一个栈帧中分配多少内存基本上是在类结构确定下来的时候就已知了,这几个区域内存分配和回收具备确定性,方法或者线程结束时,自然就跟随着回收了。而堆的和方法区不一样,同一个接口的不同实现类需要的内存不同,只有程序运行期 才知道创建哪些对象,这部分内存的分配具有动态性,GC所关注的也是这部分区域。如下图所示:
1074438-20161220195806823-1032115869.png

对象与引用

为了解决“哪些内存需要回收”的问题,需要确定哪些对象是“有用不可回收”的,而哪些对象是“无用可回收”的。通常存在以下两种判断算法。

引用计数法

算法原理:给对象添加一个引用计数器,每当一个地方引用它时,计数器值就加1;每当一个引用时效时,计数器值就减1;当引用计数为0时,表示该对象不再使用,可以回收。

应用:微软COM/ActionScript3/Python

优势:实现简单,判定效率高,通常情况下是个不错的算法。

不足:很难解决循环引用的问题。

可达性分析算法

算法原理:以称作“GC Roots”的对象作为起点向下搜索,搜索所走过的路径成为引用链,当一个对象到GC Roots不可达时,表示该对象不再使用,可以回收。

应用:Java/C#/Lisp

优势:可以解决循环引用的问题

不足:算法略复杂

以下图为例,无法通过已知的途径获取对象C和D,则有

  • 若使用引用计数算法判定,但是由于它们相互引用,导致引用计数不为0,因此无法回收掉。
  • 若使用可达性分析算法,C和D到GC Roots不可达,则可回收。

1074438-20161220195747948-604817748.png

Java的4种引用类型

JDK1.2之后定义了4种引用,分别为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)虚引用(Phantom Reference),引用强度依次减弱。

  • 强引用

我们经常使用的引用,形如Object o = new Object();或者String s = "Hello world!";等。只要强引用存在,被引用对象就不会被回收掉。

  • 软引用

描述一些还有用但是并非必须的对象。系统发生内存溢出之前,会把这类对象列入回收范围,如果这次回收还没有足够内存,才抛出内存溢出异常。使用java.lang.ref.SoftReference类来表示。

  • 弱引用

描述非必须对象。被引用对象只能存活到下一次GC之前。使用java.lang.ref.WeakReference类来表示。

  • 虚引用
  • 称为幽灵引用或者幻影引用,是最弱的一种引用关系。一个对象的虚引用根本不影响其生存时间。为一个对象设置虚引用的唯一目的就是这个对象被GC时收到一条系统通知。使用java.lang.ref.PhantomReference类来表示。

关于四种引用的具体实例和应用场景,参考

  • http://www.cnblogs.com/dolphin0520/p/3784171.html
  • https://my.oschina.net/ydsakyclguozi/blog/404389
  • http://droidyue.com/blog/2014/10/12/understanding-weakreference-in-java/

对象回收流程

不可达对象并非立即被回收,还需要经过两次标记过程后才被死亡:

  • 如果对象与GC Roots没有连接的引用链,则它会被第一次标记。随后进行一次是否执行finalize()方法的判定。
  • 如果有必要执行,则给对象被放置到一个叫做F-Queue的队列中,稍后由虚拟机建立低优先级的Finalizer线程去执行,但并不承诺等待它运行结束。
  • 如果没有必要执行(对象没有覆盖finalize()方法或者finalize()已经被执行过一次),则它会被第二次标记。

附上书中的例子:

/**
 * 代码演示了两点:
 * 1.对象在被GC时可以自我拯救
 * 2.这种自救的机会只有一次,因为一个对象的finalize()方法最多被系统执行1次
 */
public class FinalizeEscapeGC {
    public static FinalizeEscapeGC SAVE_HOOK = null;

    public void isAlive() {
        System.out.println("yes, i am still alive :)");
    }

    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        System.out.println("Finalize method Invoked!");
        FinalizeEscapeGC.SAVE_HOOK = this;
    }

    public static void main(String[] args) throws InterruptedException {
        SAVE_HOOK = new FinalizeEscapeGC();

        // 第一次拯救,成功
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(1000);

        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("Oh, i am dead!  :(");
        }

        // 第二次拯救,失败
        SAVE_HOOK = null;
        System.gc();
        Thread.sleep(1000);

        if (SAVE_HOOK != null) {
            SAVE_HOOK.isAlive();
        } else {
            System.out.println("Oh, i am dead!  :(");
        }
    }

}

输入结果为:

Finalize method Invoked!
yes, i am still alive :)
Oh, i am dead!  :(

另外,finalize()方法是诞生时使C/C++程序员接受它所做的妥协,不建议使用。

方法区回收

方法区GC主要回收两部分内容:废弃常量和无用类。判断是否为“废弃常量”与堆中对象类似,而判断一个类为“无用类”必须满足下面三个条件:

  • 该类所有实例都被回收
  • 加载该类的ClassLoader已被回收
  • 该类对应的java.lang.Class对象没有被引用

GC算法

HotSpot算法实现

GC分类

内存分配与回收策略


本文由 BF 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

楼主残忍的关闭了评论