三、垃圾收集器与内存分配策略

116 阅读3分钟

一 | 概述

让我们回顾一下运行时内存区

    • 内存回收的主要区域

    • 只有处于运行时才能确定创建哪些对象,创建多少对象

  • 方法区

    • 《Java虚拟机规范》并不要求在方法区回收垃圾

    • 回收的成本较高,因为很多回收需要满足条件的条件难以达成

    • 也就只能回收:废弃的常量和不再使用的类型

  • 栈(虚拟机栈,本地方法栈)

    • PC,虚拟机栈,本地方法栈随线程生而生,灭而灭

    • 随着方法的调用和返回,入栈和出栈

    • 一个栈帧中分配多少内存在类结果确定下来后就能确定

二 | 回收堆

怎么判断对象已死?

引用计数法

  • 借用额外内存空间来维护一个引用计数变量,速度快

  • 问题:形成孤立的互相引用,无法成功回收垃圾

可达性分析法

  • 当前虚拟机所采用的方法

  • GC Roots Set出发,根据图论的方法检查对象是否可达,是否可达即是否是垃圾即是否需要回收

  • GC Roots Set有哪些?

    • 「虚拟机栈」、「本地方法栈」中引用的对象

    • 「类的静态变量」,「常量」所引用的对象

    • 被同步锁所持有的对象(synchronized关键字)

    • 虚拟机内部的引用,基本数据类型的「Class对象」,「常驻异常对象」,「系统类加载器」

    • 反应虚拟机内部运行情况的「JM XBean」、「JVM TI中注册的回调」、「本地代码缓存」

    • 其他对象可能临时性加入

引用

  • 强引用

    • 普遍存在的引用赋值

    • 不会被垃圾回收

    • 内存不足时,不会回收这些对象,抛出OutOfMemoryError: heap space

  • 软引用

    • 还有用,非必需的对象

    • 内存足时不会回收,内存不足时才回收(在抛出OutOfMemoryError: heap space异常前,执行二次回收,若内存还不足,则抛出OutOfMemoryError: heap space异常)

  • 弱引用

    • 非必需的对象

    • 内存足时也会回收,只能存活到下一次垃圾收集前

  • 虚引用

    • 无法通过虚引用来获得对象实例

    • 一个对象设置虚引用的唯一目的是为了能在这个对象被收集器回收时收到一个系统通知

To be or not to be

  • 不可答对象就要被回收吗? 不是

  • 经历两次标记过程:

    • if 「覆盖了finalize()方法且没有执行」:放入F-Queue队列,虚拟机自动建立一低优先级的Finalizer线程去执行finalize()方法
    • else:直接回收
  • 不推荐使用的语法:运行代价高,不确定性大,无法保证调用顺序

三 | 回收方法区

  • 《Java虚拟机规范》并不要求在方法区回收垃圾

  • 回收的成本较高,因为很多回收需要满足条件的条件难以达成

  • 也就只能回收:「废弃的常量」和「不再使用的类型」

  • 判断一个类型是否时不再使用的类型?

    • 该类所对应的实例全部回收

    • 类加载器(ClassLoader)已回收

    • 该类对应的java.lang.Class对象没有在任何地方被引用,不能通过反射访问该类方法

  • 一个类型被判定为不再使用的类型后,「被允许」回收,不是必然回收

四 | 垃圾回收算法

假说

该假说是经验法则,指导垃圾收集算法的设计原则

  • 对象朝生息灭
  • 熬过越多垃圾收集的对象越难消亡
  • 跨代应用占极少数

名次解释

  • Partial GC

    • yong GC, minor GC

    • old GC,major GC

    • mixed GC

  • full GC

标记-清除

  • 产生内存碎片
  • 因为是清除,所以效率不稳定,效率随对象增多而下降

标记-复制

  • 可以内存缩小到一半

标记-整理

  • 不会有内存碎片问题