码迷,mamicode.com
首页 > 其他好文 > 详细

JVM垃圾回收机制

时间:2021-02-02 10:37:48      阅读:0      评论:0      收藏:0      [点我收藏+]

标签:引用   使用   class   方式   优先   nat   情况下   垃圾回收   info   

1.概述

技术图片

2.对象回收算法

  执行垃圾回收之前,要判断哪些内存需要回收,在JVM垃圾回收机制中主要体现为两种对象回收算法。

2.1 引用计数算法

给对象添加一个引用计数器,难以解决循环引用的问题,因此主流的java虚拟机基本上没有选用引用计数算法来管理内存的。

技术图片

  从图中可以看出,如果不小心直接把 Obj1-reference 和 Obj2-reference 置 null,左右两个对象的引用计数器分别减1,最后左右两个对象的引用计数器还是为1,不为0,因此不能回收。

2.2 可达性分析算法

通过一系列的 ‘GC Roots’ 的对象作为起始点,从这些节点出发所走过的路径称为引用链。当一个对象到 GC Roots 没有任何引用链相连的时候说明对象不可用。

技术图片

 那么哪些对象可以作为GC Roots的对象呢?

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

  对于可达性算法,并不是发生一次GC就会对不在GC Roots相连接的引用链的对象进行回收,有些对象可能处于“死缓”状态,一个对象的真正死亡至少要经历两次标记过程:

  如果对象在进行中可达性分析后发现没有与 GC Roots 相连接的引用链,那他将会被第一次标记并且进行一次筛选,筛选条件是此对象是否有必要执行 finalize() 方法。当对象没有覆盖 finalize() 方法,或者 finalize() 方法已经被虚拟机调用过,虚拟机将这两种情况都视为“没有必要执行”。

  对于被判定有必要执行finalize()方法的对象,GC会将该对象放到一个叫F-Queue的队列之中,并由虚拟机自动创建的一个Finalizer线程去执行各个对象的finalize()方法,在该过程即会进行第二次标记过程。

  finalize() 方法是对象逃脱死亡命运的最后一次机会,稍后 GC 将对 F-Queue 中的对象进行第二次小规模的标记,如果对象要在 finalize() 中成功拯救自己 —— 只要重新与引用链上的任何一个对象简历关联即可。
  finalize() 方法只会被系统自动调用一次。

3. 垃圾收集算法

3.1 标记-清除算法  

    标记-清除(Mark-Sweep)算法可以算是最基础的垃圾收集算法,该算法主要分为“标记”和“清除”两个阶段。先标记可以被清除的对象,然后统一回收被标记要清除的对象,这个标记过程采用的就是可达性分析算法。
    标记清除算法的缺点是在垃圾回收之后会产生大量的内存碎片,且效率不高。

  具体执行过程如下图所示:

技术图片

 

3.2 复制算法

    把空间分成两块,每次只对其中一块进行 GC。当这块内存使用完时,就将还存活的对象复制到另一块上面。
    这种算法最大的缺点是将原有内存分成了两块,每次只能使用区中一块,也就是损失了50%的内存空间,代价有点大。
    因为大多数新生代对象都不会熬过第一次 GC。所以没必要 1 : 1 划分空间。可以分一块较大的 Eden 空间和两块较小的 Survivor 空间,每次使用 Eden 空间和其中一块 Survivor。
    当回收时,将 Eden 和 Survivor 中还存活的对象一次性复制到另一块 Survivor 上,最后清理 Eden 和 Survivor 空间。大小比例一般是 8 : 1 : 1,每次浪费 10% 的 Survivor 空间。
    但是这里有一个问题就是如果存活的大于 10% 怎么办?这里采用一种分配担保策略:多出来的对象直接进入老年代。

  具体执行过程如下图所示:

技术图片

 

 

 3.3 标记—整理算法

  不同于针对新生代的复制算法,针对老年代的特点,创建该算法。
  标记过程仍然使用可达性算法来判断,后续步骤不是直接对可回收对象进行清理,而是让所有存活的对象向一端移动,然后直接清理边界以外的内存。

  具体执行过程如下图所示:

技术图片

 

3.4 分代回收算法

  当前商业虚拟机的垃圾回收都是采用“分代收集”(Generational Collection)算法,这种算法其实并没有什么新的思想,只是根据对象的存活周期的不同将内存划分为几块。
  一般把Java堆分为新生代和老年代,然后根据不同年代的特点采用适当的收集算法。
  例如在新生代,一般情况下每次垃圾收集都会有大量的对象死去,只有少量的存活,这时候一般使用复制算法。
  而对于年老代,由于对象存活率高,没有额外空间对它跟配担保,因此一般采用“标记-清理”或“标记-整理”算法来实现。

PS:现在大部分虚拟机基本都是采用分代收集算法来实现垃圾回收,而分代收集其实又是采用其他三种实现方式结合而成。

4. 垃圾收集器

  收集算法是内存回收的理论,而垃圾回收器是内存回收的实践。

技术图片

4.1 Serial收集器

  这是一个单线程收集器。意味着它只会使用一个 CPU 或一条收集线程去完成收集工作,并且在进行垃圾回收时必须暂停其它所有的工作线程直到收集结束,这对大多数应用(用户)是很难接受的。

技术图片

4.2 ParNew 收集器

ParNew收集器其实就是Serial收集器的多线程版本

技术图片

4.3 Parallel Scavenge 收集器

这是一个新生代收集器,也是使用复制算法实现,同时也是并行的多线程收集器。

  CMS 等收集器的关注点是尽可能地缩短垃圾收集时用户线程所停顿的时间,而 Parallel Scavenge 收集器的目的是达到一个可控制的吞吐量(Throughput = 运行用户代码时间 / (运行用户代码时间 + 垃圾收集时间))。
  作为一个吞吐量优先的收集器,虚拟机会根据当前系统的运行情况收集性能监控信息,动态调整停顿时间。这就是 GC 的自适应调整策略(GC Ergonomics)。

4.4 Serial Old 收集器

Serial收集器的老年代版本,单线程,使用 标记 — 整理算法。

4.5 Parallel Old 收集器

Parallel Old 是 Parallel Scavenge 收集器的老年代版本。多线程,使用 标记 — 整理算法

4.6 CMS 收集器

CMS(Concurrent Mark Sweep)收集器做到了部分动作可以和用户线程同时工作。它是基于标记清除算法实现的,运作过程相对于前面几种更负责。

分为4个步骤: 
初始标记(CMS initial mark):标记GC Roots能直接关联的对象,暂停用户线程

并发标记(CMS concurrent mark):进行GC RootsTracing,可以和用户线程同时执行。

重新标记(CMS remark):修正并发标记期间因为用户线程继续运行而导致标记变化的对象,暂停用户线程。

并发清除:清理经过重新标记后的对象内存,可以和用户线程同时执行。

由于耗时最长的并发标记和并发清除过程都可以和用户线程一起执行,所以从整体来说CMS收集器是可以和用户线程一起并发执行的。

技术图片

 缺点:对 CPU 资源敏感、无法收集浮动垃圾、标记 —— 清除 算法带来的空间碎片

4.7 G1收集器

面向服务端的垃圾回收器。

优点:并行与并发、分代收集、空间整合、可预测停顿。

运作步骤:

  1. 初始标记(Initial Marking)
  2. 并发标记(Concurrent Marking)
  3. 最终标记(Final Marking)
  4. 筛选回收(Live Data Counting and Evacuation)

 技术图片

 

 

参考:

https://blog.csdn.net/u010349644/article/details/82191822

https://blog.csdn.net/qq_41701956/article/details/81664921

 

JVM垃圾回收机制

标签:引用   使用   class   方式   优先   nat   情况下   垃圾回收   info   

原文地址:https://www.cnblogs.com/zccfrancis/p/14353424.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!