写点什么

架构师训练营 W9 心得

用户头像
telliex
关注
发布于: 2020 年 08 月 05 日

JVM 垃圾回收

GC 机制对 JVM 中的记忆体进行标记,确定哪些记忆体需要回收,再根据回收策略,自动的回收记忆体。作法常是将物件设定为 null 或者呼叫 System.gc() 显式清理记忆体(耗效能)。其实無需特意清理,应由垃圾回收器自动进行清理。



回收前,要先判断哪些对象已经“死去”,需要进行回收。

计数算法 (无法解决对象互相引用的问题) 

给对象加计数器,对象对其引用,则计数器加一,引用失效的时候,计数器减一。为零的对象就是不再使用

可达性分析 (Reachability Analysis) 算法 

通过一系列称为 GC Roots 的对象作为起始点,然后向下搜索,走过的路径为引用链,如果一个对象到GC Roots 没有任何引用链,则说明此对象是不可达(不可用)的。

以下物件可作为 GC Roots:

  1. 本地变量表(虚拟机栈)中引用的物件

  2. 方法区中静态变数引用的物件

  3. 方法区中常量引用的物件

  4. Native 方法引用的物件

从 JDK 1.2 版本开始,对象的引用被划分为 4 种级别,由高到低依次为:强引用、软引用、弱引用和虚引用。

1.强引用(StrongReference)

当内存不足时,存在的引用,绝对不会回收,直到强引用的对象不使用或者超出对象的生命周期范围。

Object strongReference = new Object();


2.软引用(SoftReference)

  1. 内存空间充足时,垃圾回收器不会回收。

  2. 内存空间不足时,就会尝试回收这些对象。虚拟机会尽可能优先回收长时间闲置不用的软引用对象。对那些刚构建的或刚使用过的较新的软对象会被虚拟机尽可能保留。只要垃圾回收器没有回收它,该对象就可以被程序使用

// 强引用
String strongReference = new String("abc");
String str = new String("abc");

// 软引用
SoftReference<String> softReference = new SoftReference<String>(str);


3.弱引用(WeakReference)

一旦发现了只具有弱引用的对象,不管当前内存空间足够与否,都会回收它的内存。

String str = new String("abc");
WeakReference<String> weakReference = new WeakReference<>(str);

// 消除强引用
str = null;


4.虚引用(PhantomReference)

如果一个对象仅持有虚引用,那么它就和没有任何引用一样,在任何时候都可能被垃圾回收器回收。

String str = new String("abc");
ReferenceQueue queue = new ReferenceQueue();
// 创建虚引用,要求必须与一个引用队列关联
PhantomReference pr = new PhantomReference(str, queue);


垃圾收集算法

1.标记-清除算法





标记-清除算法分为“标记”和“清除”两个阶段,执行过程如下图所示:

  1. 标记:首先标记出所有需要回收的对象

  2. 清除:在标记完成后统一回收所有被标记的对象

问题:

  1. 效率问题,标记和清除的两个过程效率都不高

  2. 标记-清除会产生大量不连续的内存碎片,这会导致在后面需要分配连续的大对象时,无法找到足够大的连续内存而导致不得不提前触发另一次垃圾收集动作

2.复制算法





  1. 首先将可用内存分为大小相等的两块,每次只使用其中的一块。

  2. 当这一块的内存用完了,就将还存活的对象连续复制到另一块上面,然后把使用过的内存空间一次清理掉

代价 

复制算法的代价就是将内存缩小为原来的一半。

3.标记-整理算法 (压缩)





分为“标记”和“整理”两个阶段

  1. 标记:首先标记出所有需要回收的对象

  2. 整理:让所有的存活的对象都向一端移动,然后直接清除掉边界以外的内存

4.分代收集算法

降 Java 堆分为新生代和老年代,根据其各自的特点采用最适当的收集算法。

  1. 新生代中大批对象死去,只有少量存活,就选用复制算法

  2. 老年代中对象存活几率高,没有额外的空间对它进行分配担保,就必须使用标记-清除或者标记-整理算法。

垃圾回收器





依发展历程大致可以分为以下四个阶段: Serial(串行)收集器 -> Parallel(并行)收集器 -> CMS(并发)收集器 -> G1(并发) 收集器

1.Serial 类收集器 - 单线程的收集器

  1. 它只会用单个收集线程去进行垃圾回收的工作

  2. 它在进行垃圾收集的时候会 “Stop The World” 暂停其他所有的工作表线程,直到它收集结束

  3. Serial 收集器采取复制算法在新生代进行单线程的回收工作

  4. Serial Old 收集器采取标记-整理算法在老年代进行单线程的回收工作

2.4.2 Parallel 类收集器 - Serial 收集器的多线程版本

  1. 它使用多个收集线程取进行垃圾回收工作

  2. 它在进行垃圾收集的时候也会 “Stop The World” 暂停其他所有的工作表线程,直到它收集结束

  3. Parallel New 收集器采取复制算法在新生代进行多线程的回收工作

  4. Parallel Scavenge 收集器也是一个新生代收集器,不过它被称为“吞吐量优先”收集器

  5. Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,使用多线程和标记-整理算法在老年代进行垃圾回收。

3.CMS 收集器 - 以获取最短回收停顿时间为目标

基于标记-清除算法实现的,运作过程分为4个步骤:

  1. 初始标记 (CMS initial mark): 需要 “Stop The World”,仅仅只是标记下 GC Roots 能直接关联到的对象,速度很快

  2. 并发标记 (CMS concurrent mark): CMS 线程与应用线程一起并发执行,从 GC Roots 开始对堆中对象进行可达性分析,找出存活对象,耗时较长

  3. 重新标记 (CMS remark):重新标记就是为了修正并发标记期间因用户线程继续运作而导致标记产生变动的那一部分对象的标记记录,可以多线程并行

  4. 并发清除 (CMS concurrent sweep):CMS 线程与应用线程一起并发执行,进行垃圾清除

CMS收集器优点:并发收集、低停顿

4.G1 收集器 - 也是关注最小停顿时间的垃圾回收器





基于复制+标记-整理算法

  1. 初始标记 (Initial Marking): 需要 “Stop The World” ,仅仅只是标记下 GC Roots 能直接关联到的对象,速度很快

  2. 并发标记(Concurrent Marking): G1 线程与应用线程一起并发执行,从 GC Roots 开始对堆中对象进行可达性分析,找出存活对象,耗时较长

  3. 最终标记(Final Marking): 最终标记阶段则是为了修正在并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录,需要 “Stop The World”,可以多线程并行

  4. 筛选回收(Live Data Counting and Evacuation): 对各个Region的回收价值和成本进行排序,根据用户所期待的GC停顿时间制定回收计划。具体地,在后台维护一个优先队列,每次根据允许的收集停顿时间,优先回收价值最大的 Region。

  5. G1 是基于标记-整理算法,不会产生空间碎片,在分配大的连续对象是不会因为无法得到连续空间而不得不提前触发一次 Full GC

  6. 停顿时间可控,G1 可以通过设置停顿时间来控制垃圾回收时间

  7. 并行与并发,G1 能更充分的利用 CPU,多核环境下的硬件优势来缩短 stop the world 的停顿时间

参考

  1. JVM垃圾回收原理



用户头像

telliex

关注

还未添加个人签名 2018.03.26 加入

还未添加个人简介

评论

发布
暂无评论
架构师训练营 W9 心得