对象的引用类型


  • 强引用(Strong Reference):只要存在强引用,直到OOM也不会回收。如Object obj = new Object
  • 软引用(Soft Reference):内存充足时不回收,内存不足(将要发生OOM)时才会进行回收。常用于实现内存敏感的高速缓存。
  • 弱引用(Weak Reference):无论内存是否充足,只要发生GC就会被回收。
  • 虚引用(Phantom Reference):目的是为了在对象被回收时收到一个系统通知。常用于配合引用队列跟踪堆外内存的释放(如ByteBufferCleaner)。

垃圾判定算法


引用计数法

为每一个对象添加一个引用计数器。每有一个对该对象的引用,计数器加一;引用失效时,计数器减一。计数为0时可以进行回收。
缺点:无法解决依赖循环问题,导致内存泄漏。因此Hotspot不使用该方法。

可达性分析算法

以一系列被称为GC Roots的根对象作为起点,从这些节点出发根据引用关系向下搜索,走过的路径称为“引用链”。如果一个对象与任何GC Roots都没有引用链相连,说明该对象不可达。
可以作为GC Roots的对象:

  1. 虚拟机栈中栈帧的局部变量表中引用的对象(如方法参数、局部变量)。
  2. 方法区中静态字段引用的对象(如public static Object obj = new Object();)。
  3. 方法区中常量引用的对象(如public static final修饰的引用)。
  4. 本地方法栈中JNI引用(Native方法)的对象
  5. Java虚拟机内部的引用
    1. 基本类型的Class对象
    2. 常驻的异常对象(如NullPointerExceptionOutOfMemoryError等)。
    3. 系统类加载器
  6. 所有被同步锁(synchronized)持有的对象

二次标记和finalize()自救

在判断对象不可达后,如果对象重写了finalize()且未被执行过,对象会被放入F-Queue队列,等待一个低优先级的Finalizer线程执行。如果对象在finalize()中将自己的引用(this)赋值给某个引用变量,在第二次小规模标记中就能避免被回收。

Warning

任何对象的finalize()只会被虚拟机调用一次。因其执行时间不可控,已经被废弃,不能用于资源释放。

垃圾回收算法


分代收集假说

  • 弱分代假说:绝大多数对象都是朝生夕灭的(即大多数对象创建后很快就不再使用)。
  • 强分代假说:经过越多次垃圾回收的对象,就越难死亡。
  • 跨代引用假说:跨代引用相对于同代引用来说只占极少数。

三种算法

标记-清除算法(Mark-Sweep)标记-复制 (Copying)标记-整理 (Mark-Compact)
原理标记存活对象、回收未标记对象将内存分成两块,
回收时将存活对象复制到另一块,
清空原区域
标记存活对象,
将标记的对象全部移至一边,
清理边界外的区域
优点实现简单、不需要移动对象没有内存碎片、按序分配、效率高没有内存碎片、不会浪费内存空间
缺点会产生大量内存碎片存在内存浪费、存活率高时复制开销大移动对象需要更新所有引用,
STW(Stop The World)时间较长
适用区域CMS的老年代新生代(Appel 式 8:1:1)Serial Old、Parallel Old的老年代

垃圾回收的范围

Minor GC(Young GC):只回收新生代(Eden + S0/S1)。
Major GC(Old GC):只回收老年代(目前只有 CMS 收集器有单独的老年代收集行为)。
Mixed GC:回收整个新生代以及部分老年代(G1 收集器特有)。
Full GC:回收整个 Java 堆和方法区。

回收过程

设最初使用的Survivor区为S0。

  1. 新对象不断在Eden分配。
  2. 当Eden满时,触发Minor GC
    1. 扫描Eden和S0。
    2. 标记存活对象。
    3. 将存活的对象复制到S1。
    4. 将存活对象的年龄+1(记录在对象头中)。
    5. 清空Eden和S0。
    6. 交换S0和S1的角色。
  3. 晋升机制
    1. 长期存活的对象:年龄达到阈值(默认15)时进入老年代。
    2. 大对象直接晋升老年代:大小超过一定阈值的对象直接进入老年代,如很长的字符串或很大的byte数组。避免在Eden和Survivor之间进行大量内存复制。
    3. 内存分配担保:Minor GC存活的对象无法完全放入目标Survivor区时,通过内存分配担保借用老年代内存。
    4. 动态对象年龄判定:Survivor区中,如果存在一个最小年龄N,使得从1到N的对象大小总和超过了阈值(默认50%),则年龄大于等于N的对象晋升为老年代。
      相关参数
  • -XX:MaxTenuringThreshold:晋升老年代年龄阈值,默认为15(CMS、G1),最大值为15。
  • -XX:PretenureSizeThreshold:大对象直接进入老年代的阈值。适用于Serial和ParNew。