垃圾回收概述

垃圾收集(Garbage Collection, 下文简称GC)需要完成的三件事情:
哪些内存需要回收?
什么时候回收?
如何回收?

java垃圾回收的优缺点:

  • 优点:

a.不需要考虑内存管理,
b.可以有效的防止内存泄漏,有效的利用可使用的内存,
c.由于有垃圾回收机制,Java中的对象不再有”作用域”的概念,只有对象的引用才有”作用域”

  • 缺点:

java开发人员不了解自动内存管理, 内存管理就像一个黑匣子,过度依赖就会降低我们解决内存溢出/内存泄漏等问题的能力。

判断对象是否已死算法

引用计数算法

给每个创建的对象添加一个引用计数器,每当此对象被某个地方引用时,计数值+1,
引用失效时-1,所以当计数值为0时表示对象已经不能被使用。

优点:
实现简单,执行效率高,很好的和程序交织。
缺点:
无法检测出循环引用。

可达性分析算法

概述

通过“GC Roots”的对象作为起始点,从起始点开始向下搜索到对象的路径。
搜索所经过的路径称为引用链(Reference Chain),当一个对象到任何GC Roots都没有引用链时,则表明对象“不可达”,即该对象是不可用的

可以作为GC Root的对象:

  • 栈帧中的局部变量表中的reference引用所引用的对象
  • 方法区中static静态引用的对象
  • 方法区中final常量引用的对象
  • 本地方法栈中JNI(Native方法)引用的对象
  • Java虚拟机内部的引用, 如基本数据类型对应的Class对象, 一些常驻的异常对象(比如 NullPointExcepiton、OutOfMemoryError) 等, 还有系统类加载器。
  • 所有被同步锁(synchronized关键字) 持有的对象。

JVM之判断对象是否存活

finalize()方法最终判定对象是否存活:

可达性分析算法中判定为不可达的对象,需要通过finalize()方法判断,才会进入垃圾回收队列,等待回收。
具体过程:

  1. 第一次标记:如果对象在进行可达性分析后发现没有与GC Roots相连接的引用链, 会被第一次标记。随后,基于是否有必要执行finalize()方法,进行一次筛选
    1. 有必要:该对象重写了finalize()方法,并且该方法未执行过
      1. 在finalize()方法中重新建立引用关系—逃过本次垃圾回收
      2. 在finalize()方法中未建立引用—继续被回收
    2. 没有必要:对象没有覆盖finalize()方法,或者finalize()方法已经被虚拟机调用过—继续被回收

image.png

引用类型

  • 强引用StrongReference
    • 最普遍的引用,不会被垃圾回收器回收
    • A a = new A();
  • 软引用SoftReference
    • 只具有软引用的对象,内存空间足够,垃圾回收器不会回收它;如果内存空间不足,就会回收该对象的内存
    • SoftReference
  • 弱引用WeakReference
    • 当垃圾收集器开始工作时,无论当前内存是否足够, 都会回收掉只被弱引用关联的对象
  • 虚引用
    • 如果一个对象仅持有虚引用,在任何时候都可能被垃圾回收器回收。
    • 虚引用主要用来跟踪对象被垃圾回收器回收的活动。

软引用、弱引用、虚引用都可以设置一个相关的引用队列,在被垃圾回收器回收前,会将该引用加入到相关的队列中去。

垃圾收集算法

分代收集理论

据对象的生命周期将内存划分,然后进行分区管理。

建立在两个分代假说之上:
1) 弱分代假说(Weak Generational Hypothesis) : 绝大多数对象都是朝生夕灭的。
2) 强分代假说(Strong Generational Hypothesis) : 熬过越多次垃圾收集过程的对象就越难以消亡。

标记-清除算法


算法分为“标记”和“清除”两个阶段:

  1. 首先标记出所有需要回收的对象
  2. 在标记完成后,统一回收掉所有被标记的对象, 也可以反过来, 标记存活的对象, 统一回收所有未被标记的对象。

image.png

缺点

  1. 执行效率不稳定 ,如果堆中包含大量对象,其中大部分需要回收,需要进行大量标记和清除动作
  2. 内存空间的碎片化

标记-复制算法

产生原因:为解决标记-清除算法面对大量可回收对象时执行效率低的问题

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

image.png

缺点:

  1. 需要提前预留一半的内存区域用来存放存活的对象,导致可用的对象区域减小一半,总体的GC更加频繁
  2. 如果出现存活对象数量比较多的时候,需要复制较多的对象,成本上升,效率降低
  3. 如果99%的对象都是存活的(老年代),那么老年代是无法使用这种算法的。

标记-整理算法


产生原因:针对老年代对象存活率较高的特征,针对性提出的算法

概述:首先标记出存活的对象,让所有存活的对象都向内存空间一端移动, 然后直接清理掉边界以外的内存

image.png

是否移动对象都存在弊端, 移动则内存回收时会更复杂, 不移动则内存分配时会更复杂。 从垃圾收集的停顿时间来看, 不移动对象停顿时间会更短, 甚至可以不需要停顿, 但是从整个程序的吞吐量来看, 移动对象会更划算。

垃圾收集器

垃圾收集器

垃圾回收器与垃圾回收算法


垃圾回收算法分类两类:

  1. 判断对象生死算法
    1. 引用计数算法
    2. 可达性分析算法
  2. 收集死亡对象算法
    1. 标记-清除算法
    2. 标记-复制算法
    3. 标记-整理算法

垃圾收集器是算法的落地实现

垃圾收集器分类

image.png

七种垃圾收集器及其组合关系

根据分代思想,我们有7种主流的垃圾回收器
image.png

新生代垃圾收集器:Serial 、 ParNew 、Parallel Scavenge
老年代垃圾收集器:Serial Old 、 Parallel Old 、CMS
整理收集器:G1

垃圾收集器的组合关系
image.png

JDK8中默认使用组合是: Parallel Scavenge GC 、ParallelOld GC
JDK9默认是用G1为垃圾收集器

GC性能指标

吞吐量:即CPU用于运行用户代码的时间与CPU总消耗时间的比值
(吞吐量 = 运行用户代码时间 / ( 运行用户代码时间 + 垃圾收集时间 ))
例如:虚拟机共运行100分钟,垃圾收集器花掉1分钟,那么吞吐量就是99%
暂停时间:执行垃圾回收时,程序的工作线程被暂停的时间
内存占用:java堆所占内存的大小
收集频率:垃圾收集的频次

Serial收集器

ParNew收集器

Parallel Scavenge收集器

SerialOld收集器

Parallel Old收集器

CMS收集器

G1收集器