Java GC 笔记

Java程序不用像C++程序在程序中自行处理内存的回收释放。这是因为Java在JVM虚拟机上增加了垃圾回收(GC)机制,用以在合适的时间触发垃圾回收,将不需要的内存空间回收释放,避免无限制的内存增长导致的OOM。

回收前的准备

垃圾回收器在对堆进行回收前,第一件事情就是要确定这些对象哪些还“存活”,哪些已经“死去”。

引用计数算法

给对象中添加一个引用计数器,每当有一个地方引用它,计数器加1,当引用失效时,计数器值减1;任何时刻计数器为0的对象就是不可能在被使用的。它实现简单,判定效率高,但主流的Java虚拟机都不用它来管理内存,其中最主要的原因是它很难解决对象之间相互循环引用的问题。

可达性反洗算法

通过一系列称为GC Roots的对象作为起始点,从这些节点开始向下搜索,搜索走过的路径称为引用链,当一个对象到GC Roots没有任何引用链相连,即从GC Roots到这个对象不可达,则证明此对象是不可用的。即使有的对象相互关联,只要GC Roots不可达,均被判定为可回收对象。这也是主流的实现方法。

Java中可作为GC Roots的对象:

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

对象的自救

在可达性算法中判断为不可达的对象时,它们不会立即被回收,真正宣告一个对象死亡,还需要判断该对象是否有必要执行finalize()方法,当对象没有覆盖该方法,或者已经执行过该方法,虚拟机会将这两种情况视为没有必要执行。如果对象有必要执行该方法,该对象会被放在名为F-Queue的队列中,虚拟机会建立一个低优先级的FInalizer线程去执行该方法,并不保证等它运行结束,如果一个对象在finalize()方法中执行缓慢,或者发生了死循环,很可能会导致F-Queue队列一直等待。如果在执行中重新与引用链上的任何一个对象建立关联,如把自己复制给某个类变量或者对象的成员变量,那么就可以逃脱这一次回收。

回收算法

标记-清楚算法

首先标记出所有需要回收的对象, 在标记完成后统一回收。如图:

mark-sweep

不足:标记和清除两个过程效率都不高;标记清除后产生大量不连续的内存碎片,导致以后运行的时候要分配较大对象时,无法找到足够的连续内存而不得不再一次垃圾回收。

复制算法

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

标记-整理算法

同标记清理算法一样,首先标记对象,但后续步骤不是直接清理,而是将所有存活的对象都向一端移动,然后直接清理掉边界以外的内存。
mark-compact

分代收集

Java堆主要分为新生代和老年代。对于新生代,每次收集都有大量对象死去,所以采用“复制算法”,将少量存货对象副职即可完成收集;对于老年代,其中的对象存活率高、没有额外空间对它们进行分配担保,必须使用“标记清除算法”或“标记整理算法”来收集。

文章目录
  1. 1. 回收前的准备
    1. 1.1. 引用计数算法
    2. 1.2. 可达性反洗算法
    3. 1.3. 对象的自救
  2. 2. 回收算法
    1. 2.1. 标记-清楚算法
    2. 2.2. 复制算法
    3. 2.3. 标记-整理算法
    4. 2.4. 分代收集
|