GC 垃圾回收机制

判断对象是否为垃圾的算法

引用计数算法

  • 通过判断对象的引用数量来决定对象是否可回收
  • 每个对象实例都有一个引用计数器,被引用则+1,完成引用则-1

优点:执行效率高,程序执行受影响较小

缺点:无法监测出循环引用的情况,导致内存泄露

由于其缺点的存在,主流JVM基本不使用此方式。

可达性分析算法

通过判断对象的引用链是否可达来就决定对象是否可以被回收

对内存中的整个对象图进行遍历,从GC Root开始,回收器将所有访问到的对象标记为存活,完成遍历后不可达的对象会被作为垃圾对象而清除。

image-20210117221639931

可以作为GC Root的对象

  • 虚拟机栈中引用的对象(栈帧中的本地变量表)
  • 方法区中的常量引用的对象
  • 方法区中的类静态属性引用的对象
  • 本地方法栈中JNI(Native方法)的引用对象
  • 活跃线程的引用对象

垃圾回收算法

标记-清除算法(Mark and Sweep)

将回收分为两个阶段

  • 标记:使用可达性算法,从根集合进行扫描,对存活的对象进行标记。

  • 清除:对堆内存从头到尾进行线性遍历,回收不可达对象内存。同时将可达标记清除,以便进行下一次回收。

image-20210117222308264
缺点:
  • 碎片化:由于标记清除不需要进行对象的移动,并且仅对不存活的对象进行处理,故清除后会产生大量不连续的内存碎片,内存空间碎片太多可能会无法找到足够连续内存空间从而触发下一次清除,知道OutOfMemory

复制算法(Copying)

  • 将可用的内存按容量和一定比例划分为两块或多个块,并选择其中一块或者两块作为对象面,其他则作为空闲面。
  • 对象在对象面上创建
    • 当对象面的快上的内存被用完后就将存活的对象从对象面复制到空闲面
    • 将对象面所有对象内存清除
image-20210117222959656

优点:

  • 解决了碎片化问题

  • 顺序分配内存,简单高效

  • 适用于对象存活率低的场景

目前主流JVM均采用此算法来回收年轻代,因为研究发现年轻代每次回收都只有10%左右的对象存活。

缺点:

  • 在存活率高的场景不适用,要进行较多的复制操作,降低效率。
  • 所以在老年代不用这种算法。

标记-整理算法(Compacting)

比较适用于老年代的对象回收

采用和标记-清除算法类似的标记方式对对象进行标记,但在清除时不同

  • 标记:从根集合进行扫面,对存活的对象进行标记
  • 清除:移动所有存活的对象,且按照内存地址次序依次排列,将末端内存地址以后的内存全部回收。

与标记-清除算法相比多了一部复制,成本更高但解决了碎片的问题

image-20210117223650487

特点:

  • 避免了内存的不连续
  • 不用设置两块内存互换
  • 适用于存活率高的场景(如老年代)

分代收集算法(Generational Collector)

  • 垃圾回收算法的组合拳
  • 按照对象生命周期的不同划分区域以采用不同的垃圾回收算法
  • 目的:提高JVM的回收效率

JDK6,JDK7:

image-20210117224019727

分为年轻代,老年代,永久代

JDK8及其以后的版本:

image-20210117224114389

取消了永久代

年轻代存活率低,采用复制算法,老年代存活率高,采用标记-清除算法或标记-整理算法

GC的分类

  • Minor GC

    • 发生在年轻代中的垃圾回收动作 ,采用的是复制算法
    • 当Eden空间不足的时候触发
  • Full GC

    • 发生在老年代

    • 触发条件:

      • 老年代空间不足
  • 永久代空间不足(JDK7及以前的版本)

    • CMS GC时出现promotion failed concurrent mode mode failure
  • minor GC晋升到老年代的平均大小大于老年代的剩余空间

    • 调用System.gc() ——提醒作用
    • 使用rmi来进行rpc或管理jdk应用 每小时执行一个full gc

年轻代

尽可能快速地收集掉那些生命周期短的对象

  • Eden(伊甸园)区
    • 当对象刚被创建出来,其内存空间会被首先分配在Eden区(若Eden区放不下刚创建的对象,其也有可能会被放到Survivor区甚至是老年代中)
  • 两个Survivor区
    • 分别被定义为from区和to区
    • from和to并不固定,随着垃圾回收而相互转换
image-20210117224810176

比例为8:1:1

每次使用Eden和其中一块survivor,每次进行垃圾回收时候,将Eden和Survivor中存活的对象一次性复制到另一块Survivor空间中去,最后清理掉Eden区和用过的Survivor空间。

当Survivor空间不够用时,则需要依赖老年代。

对象出生时年龄是0,每经历一次回收年龄+1,当年龄达到某个值(默认15岁),对象会进入老年代。

对象如何晋升到老年代

  • 经历一定Minor次数依然存活的对象
  • Survivor区中放不下的对象
  • 新生成的大对象(-XX:+PretenuerSizeThreshold)

常用的调优参数

  • -XX:SurvivorRatio:Eden和Survivor的比值,默认8:1
  • -XX:NewRatio:老年代和年轻代的内存大小比例
  • -XX:MaxTenuringThreshold:对象从年轻代晋升到老年代经过的GC次数的最大阈值

老年代:

存放生命周期较长的对象

  • Full GC比Minor GC慢,但执行频率低

Stop-the-World

  • 指JVM由于要执行GC而停止了应用程序的执行
  • 任何一种GC算法中都会发生
  • 多数GC优化通过减少Stop-the-World发生时间来提高程序性能

Safepoint

  • 分析过程中对象引用关系不会发生变化的点
  • 产生Safepoint的地方:方法调用;循环跳转;异常跳转等
  • 安全点数量得适中

GC只能发生在安全点Safepoint,否则对象引用还在不停变动无法进行GC

JVM的运行模式

  • Server

    • 重量级虚拟机,对程序采用了更多的优化,启动慢。
  • Client

    • 轻量级虚拟机,启动快。

java -version命令即可查出本机的运行模式

image-20210117231840670