深入理解Java虚拟机读书笔记 - 垃圾收集算法

概述


垃圾收集即GC。经过半个多世纪的发展,目前内存的动态分配与内存回收技术已经相当成熟,一切看起来都进入了“自动化”时代,那我们为什么还要去了解GC和内存分配呢?答案很简单:当需要排查各种内存溢出,内存泄漏问题时,当垃圾手机成为系统达到更高并发的瓶颈时,我们就需要对这些“自动化”的技术实施必要的监控和调节。

GC瞄准谁


我们已知程序计数器,虚拟机栈以及本地方法栈都是随着线程而生,随着线程而亡。所以这部分跟随线程生死的运行时区域在线程结束时内存也自然的被回收了。这一部分的区域就不需要过多的考虑内存的回收的问题。而Java堆和方法区则不一样。一个接口中的多个实现类可能需要的内存相差很大,一个方法的多个分支需要的内存也可能不一样。我们只有在程序运行阶段才会知道会创建哪些对象,这部分的内存分配及回收都是动态的。垃圾收集器关注的也正是这部分内存。

哪些内存需要回收


引用计数算法

给对象添加一个引用计数器,当有一个地方应用它时计数器加1;当引用失效时,计数器减1;任何时刻计数器为0的对象不可能再被使用。

优点:

实现简单,判定效率高。适用于大部分情况,但Java并没有选择其来管理内存。

缺点:很难解决对象间循环引用问题。

例子:

public class ReferenceCountingGC {

    public Object instance = null;

    private static final int _1MB = 1024 * 1024;

    private byte[] bigSize = new byte[2 * _1MB];

    public static void testGC() {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;

        objA = null;
        objB = null;

      // 该程序是无意义的相互引用且不能再被访问
      // 此处若发生GC,引用计数算法是无法通知GC收集器回收他们的
      // 但在Java中会将他们回收,因为Java中并不是通过该算法来管理内存的
        System.gc();
    }

    public static void main(String[] args) {
        testGC();
    }
}

可达性分析算法

通过一系列的称为“GC Root”的对象作为起始点,从这些节点开始向下搜索,搜索所走的路径称为引用链(Reference Chain),当一个对象到GC Root不可达时,则证明这个对象是不可用的。如下图,对象object5、 object6、 object7,虽然相互有关联但是它们到GC Root不可达,所以它们被判定为是可回收的对象。

在Java语言中,可作为GC Root对象包括以下几种:

  • 虚拟机栈(栈帧中的本地变量表)中的引用对象。
  • 方法区中的静态属性或常量引用的对象。
  • 本地方法栈中JNI(即一般说的Native方法)引用的对象。

后来Java又对引用的概念进行了扩充。引用分为强引用(Strong Reference)、软引用(Soft Reference)、弱引用(Weak Reference)、虚引用(Phantom Reference)4种,这4种引用强度依次逐渐减弱。

  • 强引用就是指在程序代码之中普遍存在的,类似“Object obj = new Object()”这类的引用,只要强引用还存在,垃圾收集器永远不会回收掉被引用的对象。
  • 软引用是用来描述一些还有用但并非必需的对象。对于软引用关联着的对象,在系统将要发生内存溢出异常之前,将会把这些对象列进回收范围之中进行第二次回收。如果这次回收还没有足够的内存,才会抛出内存溢出异常。在JDK 1.2之后,提供了SoftReference类来实现软引用。
  • 弱引用也是用来描述非必需对象的,但是它的强度比软引用更弱一些,被弱引用关联的对象只能生存到下一次垃圾收集发生之前。当垃圾收集器工作时,无论当前内存是否足够,都会回收掉只被弱引用关联的对象。在JDK 1.2之后,提供了WeakReference类来实现弱引用。
  • 虚引用也称为幽灵引用或者幻影引用,它是最弱的一种引用关系。一个对象是否有虚引用的存在,完全不会对其生存时间构成影响,也无法通过虚引用来取得一个对象实例。为一个对象设置虚引用关联的唯一目的就是能在这个对象被收集器回收时收到一个系统通知。在JDK 1.2之后,提供了PhantomReference类来实现虚引用。

非死不可吗?

要真正宣告一个对象的死亡至少要经历两次标记过程。如果对象在进行可达性分析后发现没有与GC Roots相连接的引用连,那他将会被第一次标记并且进行一次筛选,筛选的条件是此对象是否有必要执行finalize()方法。当对象没有覆盖或者finalize()方法已经被虚拟机调用过,虚拟机将这两种情况都视为没有必要执行了。

如果该对象被判定为有必要执行finalize()方法。那么这个对象将会被放置在F-Quere队列之中。 并会被虚拟机创建的,低优先级的Finalizer线程去执行该对象的finalize()方法。但并不会等待它结束,因为对象在finalize()方法执行中如果出现执行缓慢或者发生死循环。将会导致F-Queue队列中其他对象永久处于等待。甚至导致整个内存回收系统崩溃。之后GC将会对F-Queue之中的对象进行第二次标记。如果在第二次标记前这些对象在自己的finalize()方法中可以拯救自己(重新与引用链上的任何一个对象建立关联即可)也是可以成功存活下来并被移除“即将回收”的集合的。 如果此时还没有逃脱,那就真的要被回收了。

虽然其有点儿C/C++中析构函数的意思,但其其实并不是,相反却是Java程序员期初为了更好的被C/C++程序员所接受而做的一种妥协。

注意:finalize()方法的运行代价高昂,不确定性大,无法保证各个对象的调用顺序。所以尽量使用try-finally来代替它。

几种垃圾收集算法


标记—清除算法

算法分为标记和清除两个阶段:首先标记出所有需要回收的对象,在标记完成后统一回收所有被标记的对象,它的标记过程就是使用可达性算法进行标记的。
不足:

  • 效率问题,标记和清除两个过程的效率都不高
  • 空间问题,标记清除之后会产生大量不连续的内存碎片,导致以后分配较大对象时内存不足以至于不得不提前触发另一次垃圾收集动作。

复制算法

复制算法:将可用内存按照容量分为大小相等的两块,每次只使用其中的一块。当这一块的内存用完了,就将还存活着的对象复制到另一块上面,然后把已使用过的内存空间一次清理掉。

内存分配时不用考虑内存碎片问题,只要一动堆顶指针,按顺序分配内存即可,实现简单,运行高效。代价是将内存缩小为原来的一半。

实际应用中将内存分为一块较大的Eden空间和两块较小的Survivor空间,每次使用Eden和其中的一块Survivor。当回收时,将Eden和Survivor中还存活着的对象一次性复制到另一块Survivor空间上,最后清理掉Eden和刚才用过的Survivor空间。

Hotspot虚拟机中默认的Eden和Survivor的大小比例是8:1。

缺点:

  • 在对象存活率较高的情况下,效率变低
  • 有额外空间分配担保负担,无法应对被使用的内存100%对象存活的极端情况,致使老年代不能使用。

标记-整理算法

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

分代收集算法

根据对象存活周期的不同将内存分为几块。一般把Java堆分为新生代和老年代,根据各个年代的特点采用最合适的收集算法。在新生代中,每次垃圾收集时有大批对象死去,只有少量存活,可以选用复制算法。而老年代对象存活率高,使用标记清理或者标记整理算法。

什么时候回收?


当这三个分代的堆空间比较紧张或者没有足够的空间来为新到的请求分配的时候,垃圾回收机制就会起作用。有两种类型的垃圾回收方式:次收集和全收集。当新生代堆空间满了的时候,会触发次收集将还存活的对象移到年老代堆空间。当年老代堆空间满了的时候,会触发一个覆盖全范围的对象堆的全收集。

次收集

  • 当新生代堆空间紧张时会被触发
  • 相对于全收集而言,收集间隔较短

全收集

  • 当老年代或者持久代堆空间满了,会触发全收集操作
  • 可以使用System.gc()方法来显式的启动全收集
  • 全收集一般根据堆大小的不同,需要的时间不尽相同,但一般会比较长。不过,如果全收集时间超过3到5秒钟,那就太长了
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容