Java中的垃圾回收

垃圾回收

确定对象是否存活?

两种确定对象是否存活:

  • 引用计数算法
  • 可达性分析算法

引用计数算法

给对象中添加引用计数器,若有一个引用,则计数值加一,若引用失效,计数值减一。
存在的问题:难以解决循环引用问题。

可达性分析算法

通过GC Roots作为起始节点,根据引用关系向下开始搜索,所有搜索走过的路径称为“引用链”。如果一个对象没有到达GC Roots的引用链,则为不可达,可以认为该对象可以被回收。

GC Roots对象

  • 虚拟机栈中引用的对象(方法中使用参数,局部变量,临时变量)
  • 方法区中类静态属性引用的对象
  • 方法区中常量引用的对象(字符串常量池中的引用)
  • 本地方法栈中引用的对象
  • 虚拟机内部的引用(基本数据类型对象的Class对象)
  • synchronized持有的对象。

引用

分类

  • 强引用 :引用不会被垃圾收集器回收。通过 new 创建的引用。
  • 软引用 :在内存不足时,对于这些进行回收。通过 SoftReference 创建软引用。
  • 弱引用 :对象存活到下一次垃圾收集。通过 WeakReference 创建弱引用。
  • 虚引用 :不影响对象的生存周期,无法通过虚引用获得对象实例。用于在对象被回收时收到系统通知。

回收方法区

主要是回收:

  • 废弃的 常量
  • 不再使用的 类型 

判断一个类是否可以被回收的条件

  • 该类的所有实例对象已被回收。(堆中不存在该类及其子类的实例)
  • 加载该类的类加载器已被回收。
  • 对应的Class对象没有被引用。

注:大量使用 反射 , 动态代理 的场景中,需要有类型卸载的能力。


垃圾收集算法

Java堆的划分:

  • 新生代:对象创建的区域,每次垃圾收集会回收大量的对象。
  • 老年代:保存着生存周期较长的对象。

垃圾收集分类:

  • Minor GC/Young GC:目标为新生代的垃圾收集。
  • Major GC/Old GC:目标是老年代的垃圾收集。(CMS)
  • Mixed GC:收集整个新生代和部分老年代的垃圾收集(G1)

三种主要的垃圾收集算法

  • 标记-清除算法
  • 标记-复制算法
  • 标记-整理算法

标记-清除算法

分为两个阶段:

  • 标记处所有 需要回收 的对象。
  • 回收所有被标记的对象。

存在的问题

  • 被回收的对象很多时,效率较低。
  • 会导致内存碎片,内存利用率低。

标记-复制算法

流程:

将内存划分成两块,每次只使用其中一块。当这一块用完时,将存活的对象复制到另外一块中,然后回收这一块。
优化:将新生代分为:

  • Eden区
  • From Survivor
  • To Survivor

Eden : Survivor = 8 : 2
每次使用Eden区和其中一个Survivor区。当内存不足时,将存活的对象复制到另外一个Survivor区,然后将Eden区和Survivor区进行回收。(Survivor区不足容纳存活的对象时,会使用老年区)

不足

每次使用内存的一部分,利用率低。

标记-整理算法

不会产生空间碎片。

流程:

  • 标记所有可以回收的对象。
  • 将所有存活的对象向内存空间一端移动。
  • 直接清理边界以外的内存。

不足

移动对象带来的引用的更新导致整体效率较低,必须暂停用户线程才能执行。
关注吞吐量 --> 标记-整理算法
关注延迟 --> 标记-清除算法


垃圾收集器

  • 新生代:
    • Serial收集器
    • ParNew收集器
    • Parallel Scavenge收集器
  • 老年代:
    • Serial Old 收集器(MSC)
    • Parallel Old收集器
    • CMS收集器
  • 整个堆:G1收集器

Serial收集器

  • 单线程
  • 回收新生代
  • 采用标记-复制算法回收
  • 暂停用户线程(Stop The World)
  • 客户端默认

ParNew收集器

  • 多线程并行收集
  • 回收新生代
  • 需要暂停所有用户线程
  • 采用标记-复制算法
  • 服务端默认

Parallel Scavenge收集器

  • 回收新生代
  • 多线程并行收集
  • 基于标记-复制算法
  • 暂停用户线程
  • 目标:达到可控的吞吐量(用户代码运行时间与处理器总消耗时间之比),高效率完成垃圾回收任务,适用于后台运算不需要太多交互的任务。

Serial Old收集器

  • 回收老年代
  • 单线程收集
  • 基于 标记-整理算法
  • 暂停所有用户线程

Parallel Old收集器

  • 回收老年代
  • 多线程并发收集
  • 采用 标记-整理算法
  • 暂停用户线程
  • 吞吐量优先的垃圾收集器,可与Parallel Scavenge搭配使用。

CMS收集器

  • 以获得最短停顿时间为目标,重视响应速度
  • 回收老年代
  • 采用 标记-清除算法
  • 分为四个阶段
    • 初始标记
    • 并发标记
    • 重新标记
    • 并发清除

流程:

  • 初始标记:需要暂停用户线程,使用一个线程标记GC Roots能直接关联的对象。
  • 并发标记:单个线程,与用户线程并发执行,标记所有可可达对象。
  • 重新标记:需要暂停用户线程,多线程并发执行,用来修正由于用户线程执行导致的标记变动。
  • 并发清除:与用户线程并发执行,使用一个线程清除所有不可达的对象。

不足:

  • 对于CPU资源敏感,占用部分线程,导致响应变慢。
  • 无法处理浮动垃圾。由于GC线程与用户线程并发执行,对于新产生的垃圾无法回收,所以必须预留空间给用户线程。
  • 采用 标记-清除算法 ,可能导致内存碎片。

G1收集器

特点:

  • 回收的范围是整个新生代和老年代
  • 将Java堆分成大小相等的区域(Region)
  • 通过对每个Region计算其回收价值(获得的空间和花费的时间),在后台维护一个优先队列,根据允许的收集时间,优先回收价值最大的Region。

流程:

  • 初始标记
  • 并发标记
  • 最终标记
  • 筛选回收:暂停用户线程,多线程并发回收。
    • 首先对每个Region进行按照回收价值和成本进行排序。
    • 再根据用户的期望停顿时间制定回收计划。
原文地址:https://www.cnblogs.com/truestoriesavici01/p/13662901.html