JVM-垃圾回收算法

JVM——垃圾回收算法

垃圾:运行程序中没有任何指针指向的对象

1. 标记阶段

  1. 引用计数法:每个对象保存一个引用计数器,记录对象被引用的情况

    • 优点:实现简单

    • 缺点:计数器增加了存储空间开销;计数器更新增加了时间开销;无法处理循环引用

  2. 可达性分析:以GCRoots集合为起始点,所有存活对象都会被GCRoots集合直接或间接连接

    GCRoots:一组必须活跃的引用(虚拟机栈中的引用对象、本地方法栈中的引用对象、方法区中的静态变量和常量、被synchronized所持有的对象……)

2. finalize()方法

若一个对象obj到GCRoots没有引用链,则开始判断:

  • 若obj没有重写finalize()方法 或 finalize()方法已被调用过,则obj被判为不可触及,准备清除;
  • 若obj重写了finalize()方法 且 未执行过,则将obj插入 F-Queue中执行它的finalize()方法,这是它的最后一次逃脱死亡的机会,若执行finalize()方法过程中被引用则逃离死亡,这之后若再出现没有引用的情况就会直接被判为不可触及

3. 清除阶段

3.1 标记-清除算法

从GCRoots出发,标记所有被引用的对象,然后对堆内存遍历,回收那些未被标记的对象。

缺点:效率不高;gc时需停止整个应用程序;有内存碎片

3.2 标记-复制算法

将内存空间分为两块,每次只使用其中一块,gc时将存活对象复制到另一块内存,然后清空当前内存块

优点:不会出现内存碎片

缺点:需要两倍的内存空间;若大量对象存活,则需复制的对象较多,效率低

3.3 标记-整理算法

从GCRoots出发,标记所有被引用的对象,然后将所有存活对象压缩到内存的一端,然后清理边界外的空间

优点:消除了“标记-清除”中的内存碎片,内存地址连续;消除了“标记-复制”中内存减半的代价

缺点:效率低于复制算法;移动对象需调整引用地址

3.4 JVM新生代垃圾回收如何避免全堆扫描?

新生代GC时,老年代的对象可能引用了新生代对象,按理说老年代也应该作为GC Roots进行扫描。但是老年代通常比新生代大好几倍,全扫一遍效率太低。

JVM把老年代划分成很多小块,每块512字节,称为 。用一个字节数组来记录每个卡的状态,这个数组就称为 卡表。当老年代的对象引用了新生代对象时,通过写屏障把对应的卡标记为脏。新生代GC时,只扫描脏卡对应的老年代区域,不用扫描整个老年代。(写屏障本质就是一个AOP,在对应引用字段赋值时插入一段代码,判断是不是跨代引用,如果是就更新卡表)

image-20260306165129299

  • 卡表要解决的核心问题:跨代引用
  • 在分代垃圾回收模型中,堆内存被划分为新生代和老年代
  • 理想情况:垃圾回收器可以独立回收新生代(Minor GC),而不用去管老年代。
  • 现实问题:存在跨代引用。即一个老年代中的对象,可能持有一个新生代对象的引用。为什么这是个问题?在MinorGC时,为了正确判断一个新生代对象是否存活,必须知道是否有老年代对象引用它。如果没有卡表,就必须扫描整个老年代来找出这些引用,这会让Minor GC的效率变得极低,几乎等同于一次Full GC。
  • 卡表的目的就是:让Minor GC在扫描跨代引用时,无需扫描整个老年代。

4. Stop-the-World

STW,指gc发生过程中产生应用程序的停顿

5. 引用

  • 强引用Strong Reference:代码中普遍存在的赋值引用,只要强引用关系还在,对象就不会被回收
  • 软引用Soft Reference:系统将要发生内存溢出时,才将这些对象纳入回收范围,回收后内存还是不足才报错OOM
  • 弱引用Weak Reference:弱引用对象只能存活到下一次gc
  • 虚引用Phantom Reference:一个对象是否有虚引用存在,不会对其生存时间产生影响,设置虚引用的唯一目的:这个对象被回收后收到一个系统通知

JVM-垃圾回收算法
http://example.com/2025/05/16/JVM-垃圾回收算法/
作者
Kon4tsu
发布于
2025年5月16日
许可协议