关于JVM:JVM垃圾收集器和垃圾收集算法详细介绍

2次阅读

共计 4533 个字符,预计需要花费 12 分钟才能阅读完成。

JVM 垃圾收集器

Serial 收集器

Serial 收集器是最根本,倒退最悠久的收集器,在 JDK1.3.1 之前是虚拟机新生代垃圾回收的惟一抉择。这个收集器是一个单线程的。它的单线程的意义并不仅仅阐明它只会应用一个 CPU 或者一条收集线程去实现收集工作,最重要的是,它进行垃圾收集时,其余工作线程会暂停,直到收集完结。这项工作由虚拟机在后盾主动发动和执行的,在用户不可见的状况下将所有工作线程全副停掉,这对于很多应用程序来说是不可容忍的。咱们能够构想一下,咱们的计算机在运行 1 个小时就要进行 5 分钟的时候,这是什么状况? 对于这种设计,虚拟机设计人员示意的也是十分冤屈,因为不可能边收集,这边还要一直的产生垃圾对象,这样是清理不完的。所以从 1.3 始终到当初,虚拟机的开发团队始终为缩小因垃圾回收而产生的线程进展所致力着,所呈现的虚拟机越来越优良,但直到现在,仍然没有齐全打消。

讲到这里,貌似 Serial 收集器曾经是 ” 食之无味弃之可惜 ” 了,但实际上,它仍然是虚拟机在 Client 模式下,新生代默认的垃圾收集器。它有绝对于其余垃圾收集器的劣势,比方因为没有线程之间切换的开销,分心做垃圾收集天然可能播种最高的线程利用效率。在用户桌面利用背景下,个别调配给虚拟机的内存不会太大,收集几十兆或者一两百兆的新生代对象,进展工夫齐全能够管制在几十毫秒到一百毫秒之间,这个是能够承受的,只有不是频繁产生。因而,Serial 收集器在 Client 模式下,对于新生代来说仍然是一个很好的抉择。

ParNew 收集器

ParNew 收集器其实就是 Serial 收集器的多线程版本,除了应用多线程进行垃圾回收之外,其余可控参数,收集算法,进行工作线程,对象分配原则,回收策略等与 Serial 收集器完全一致。

除了多线程实现垃圾收集之外,其余没有什么太多翻新之处,然而它的确 Server 模式下的新生代的首选的虚拟机收集器。其中一个重要的起因就是除了 Serial 收集器外,只有它能与 CMS 配合应用。在 JDK1.5 期间,HotSpot 推出了一款在强交互利用划时代的收集器 CMS,这款收集器是 HotSpot 第一款真正意义上的并发收集器,第一次实现了垃圾回收与工作线程同时工作的可能性,换而言之,你能够边净化,边收集。

不过 CMS 作为老年代的收集器,却无奈与 1.4 中公布的最新的新生代垃圾收集器配合应用,反之只能应用 Serial 或者 Parnew 中的一个。ParNew 收集器能够应用 -XX:+UseParNewGC 强行指定它,或者应用 -XX:+UseConcMarkSweepGC 选项后的默认新生代收集器。

ParNew 收集器在单 CPU 环境下相对不会有比 Serial 收集器更好的成果,甚至优于存在线程交互开销,该收集器在通过超线程技术实现的两个 CPU 的环境下都不能保障百分之百超过 Serial 收集器。当然,随着 CPU 数量的减少,对于 GC 时零碎的无效资源利用还是很有益处的。在 CPU 十分多的状况下,能够应用 -XX:ParallelGCThreads 来限度垃圾回收线程的数量。

Parallel Scavenge 收集器

Parallel Scavenge 收集器是一个新生代收集器,采纳复制算法,又是并行的多线程垃圾收集器。它的关注点与其它收集器的关注点不一样,CMS 等收集器的关注点在于缩短垃圾回收时用户线程进行的工夫,而 Parallel Scavenge 收集器则是达到一个可管制的吞吐量,所谓吞吐量就是 CPU 运行用户线程的工夫与 CPU 运行总工夫的比值,即 吞吐量 = (用户线程工作工夫)/(用户线程工作工夫 + 垃圾回收工夫),比方虚拟机总共运行 100 分钟,垃圾收集耗费 1 分钟,则吞吐量为 99%。进展工夫越短越适宜与用户交互的程序,良好的响应速度能进步用户体验,然而高吞吐量则能够高效率的利用 CPU 的工夫,尽快实现程序的运算工作,次要适宜在后盾运算而不须要太多交互的程序。

有两个参数管制吞吐量,别离为最大垃圾收集工夫:-XX:MaxGCPauseMills,间接设置吞吐量的大小: -XX:GCTimeRatio

-XX:+UseAdaptiveSizePolicy

自适应策略也是 Parallel Scavenge 收集器区别去 Parnew 收集器的重要一点

Serial Old 收集器

Serial Old 收集器是 Serial 收集器的老年代版本,它同样是一个单线程收集器,应用标记 - 整顿算法,这个收集器的次要目标也是在与给 Client 模式下应用。如果在 Server 模式下,还有两种用处,一种是在 jdk5 以前的版本中配合 Parallel Scavenge 收集器应用,另一种用处作为 CMS 的备用计划,在并发收集产生 Concurrent Mode Failure 时应用。

Parallel Old 收集器

Parallel Old 收集器是 Parallel Scavenge 收集器的老年代版本,应用多线程和标记 - 整顿算法,这个收集器在 jdk6 中才开始应用的,在此之前 Parallel Scavenge 收集器始终处于比拟难堪的阶段,起因是,如果新生代采纳了 Parallel Scavenge 收集器,那么老年代除了 Serial Old 之外,别无选择,因为老年代 Serial 在服务端的连累,使得应用了 Parallel Scavenge 收集器也未必能达到吞吐量最大化的成果,因为单线程的老年代无奈充分利用服务器多 CPU 的解决能力,在老年代很大而且硬件比拟高级的环境中,这种组合的吞吐量甚至不如 Parallel Scavenge 收集器 + CMS。

直到 Parallel Old 收集器呈现后,” 吞吐量优先收集器 ” 终于有了货真价实的组合,在重视吞吐量优先和 CPU 资源敏感的场合,能够采纳 Parallel Scavenge 收集器 + Parallel Old 收集器。

CMS 收集器

CMS 收集器是一种以获取最短进展工夫为指标的收集器。从名字 (Concurrent Mark Sweep) 上就能够看出,采纳的标记 - 革除算法,它的过程分为 4 个步骤:

只有初始标记和从新标记须要暂停用户线程。

(1)初始标记 — 仅仅关联 GC Roots 能间接关联到的对象,速度很快;

(2)并发标记 — 进行 GC Roots Tracing 的过程;

(3)从新标记 — 为了修改并发标记期间,因用户程序运作而导致标记产生变动的那一部分对象的标记记录;

(4)并发革除

因为整个过程中耗时最长的并发标记和并发革除过程收集器都能与用户线程一起工作,所以总的来说,CMS 的内存回收过程与用户线程一起并发执行的

CMS 收集器的三大毛病:

(1)CMS 收集器对 CPU 资源十分敏感

(2)无奈解决浮动垃圾

(3)因为基于标记革除算法,所以会有大量的垃圾碎片产生 -XX:+UseCMSCompactAtFullCollection

G1 收集器

首先,G1 的设计准则就是简略可行的性能调优

-XX:+UseG1GC -Xmx32g -XX:MaxGCPauseMillis=200

其中 -XX:+UseG1GC 为开启 G1 垃圾收集器,-Xmx32g 设计堆内存的最大内存为 32G,-XX:MaxGCPauseMillis=200 设置 GC 的最大暂停工夫为 200ms。如果咱们须要调优,在内存大小肯定的状况下,咱们只须要批改最大暂停工夫即可。

(1)内存调配

(2)Young 垃圾回收

(3)Mix 垃圾回收

常见设置参数:

垃圾收集算法

垃圾收集算法有标记 - 革除算法、复制算法、标记真谛算法、分代收集算法四种,上面咱们具体介绍。

标记 - 革除算法

最根底的收集算法是标记 - 革除算法,如同它的名字一样,分为标记和革除两个阶段。第一步标记出所要回收的对象,在标记实现后对立回收所有被标记的对象。如何标记曾经在下面说过了,之所以说它是最根本的垃圾收集算法,起因在于其余的算法也是基于这种思路并对其有余做以改良失去的。

次要问题有两个:

第一个是效率问题,标记和革除的效率都不高。

第二个是空间调配问题,标记革除后会产生大量的不间断的内存空间,空间碎片太多可能会导致当前程序在运行过程中须要给较大对象调配空间时,无奈找到足够的内存空间,而不得不提前进行一次垃圾收集动作。如图所示,会产生大量的垃圾碎片,导致空间的利用率不高。

复制算法

为了解决效率问题,一种称为复制的收集算法呈现了,它将可用内存分为大小相等的两块,每次只应用其中的一块,当这一块内存区域用完了,就将还存活的对象复制到另一块内存中,而后再把已应用的空间一次性清理掉,这样每次都是对半个区域进行回收,内存调配时也就不必思考碎片等问题了,只有挪动堆顶指针,按程序分配内存即可,实现简略,运行高效。

只是这种做法将原来的内存放大为一半,代价太高了。

当初的商用虚拟机都采纳这种办法来回收新生代,IBM 专门钻研表明,新生代中的对象 98% 都是 ” 朝生夕死 ” 的,所以并不需要依照 1:1 划分内存区域,而是将内存分为一块较大的区域给 Eden 和两块较小的区域给 Survivor, 当回收时,将 Eden 和 Survivor 区中还存活的对象一次性复制到另一块 Survivor 区,而后将 Eden 和 Survivor 区进行一次性清理。Hotspot 区默认的 Eden 和 Survivor 的比例为 8:1,也就是说新生代的可用内存为 90%,只有 10% 的内存会被划分为保留内存。当然,大多数状况下是 98%,但咱们不能保障每次回收的存活对象都小于 10%,当 Survivor 区不够用时,须要依赖其余区域进行调配担保。如果另外一块 Survivor 区曾经不够用了,对象可通过内存担保机制间接进入到老年代。

标记整顿算法

复制算法在存活对象比例比拟高的状况下要进行较多的复制操作,效率将会变低,更要害的是,如果不想节约 50% 的区域,则须要额定的空间进行调配担保,以应答内存中 100% 对象都存活的极其状况,所以老年代个别不选用这种算法。

依据老年代的特点,有人提出了另一种标记 - 整顿算法,标记过程与标记 - 革除算法统一,但后续步骤不是对可回收对象间接进行清理,而是让所有存活对象都向一端挪动,而后间接清理掉边界外的对象。示意图如下:

分代收集算法

以后商用的垃圾收集器都采纳的是分代垃圾回收,这种算法没有什么新的思维,只是依据对象的存活周期将内存分为几块,个别是将 java 堆分为新生代和老年代,这样就能够依据各个代的对象特点选用最适当的回收算法。在新生代,每次垃圾回收都有大量的对象死去,只有大量存活,这样就适宜采纳复制算法。只须要付出大量的对象复制老本就能够实现垃圾回收,而老年代因为存活率高,没有其余内存进行调配担保,就必须应用标记 - 清理或者标记 - 整顿进行回收。

  1. 分代分为年老代和老年代,年老代外头又分为 Eden 区和 Survivor 区,通常默认的比例为 8:1:1, 每次只保留 10% 的空间用作预留区域,而后将 90% 的空间能够用作新生对象。
  2. 每一次垃圾回收之后,存活的对象年龄对应 +1,当经验 15 次还仍然存活的对象,咱们让它间接进入到老年代;
  3. 另外一种进入到老年代的形式是内存担保机制,也就是当新生代的空间不够的时候,对象间接进入到老年代;
  4. 新生代的垃圾回收叫 Minor GC,老年代的叫 Full GC。

参考文章:
jvm 垃圾收集器有哪些?深度解说
垃圾收集算法有哪些? 图文具体介绍

正文完
 0