不同的垃圾回收器的比较

标签: 垃圾回收 | 发表时间:2014-09-13 06:35 | 作者:
出处:http://it.deepinmind.com

4款Java垃圾回收器——错误的选择导致糟糕的性能

现在已经是2014年了,但是对大多数开发人员而言有两件事情仍然是个谜——垃圾回收以及异性(码农又被嘲笑了)。由于我对后者也不是特别了解,我想我还是试着说说前者吧,尤其是随着Java 8的到来,这个领域也发生了许多重大的变化及提升,其中最重要的莫过于持久代(PermGen)的删除以及一些令人振奋的新的优化(后面会陆续提及这些)。

说起垃圾回收,许多人都了解它的概念,也在日常的编程中有所应用。尽管如此,仍有许多我们不太了解的东西,而这正是痛苦的根源。关于JVM最大的误解就是认为它只有一个垃圾回收器,而事实上它有四个不同的回收器,每个都各有其长短。JVM并不会自动地选择某一个,这事还得落在你我的肩上,因为不同的回收器会带来吞吐量及应用的暂停时间的显著的差异。

这四种回收算法的共同之处在于它们都是分代的,也就是说它们将托管的堆分成了好几个区域,它假设堆中的许多对象的生命周期都很短,可以很快被回收掉。介绍这块内容的已经很多了,因此这里我打算直接讲一下这几个不同的算法,以及它们的长处及短处。

  1. 串行回收器

串行回收器是最简单的一个,你都不会考虑使用它,因为它主要是面向单线程环境的(比如说32位的或者Windows)以及比较小的堆。这个回收器工作的时候会将所有应用线程全部冻结,就这一点而言就使得它完全不可能会被服务端应用所采用。

如何使用它:你可以打开-XX:+UseSerialGC这个JVM参数来使用它。

  1. 并行/吞吐量回收器

下一个是并行回收器( Parallel collector)。这是JVM的默认回收器。正如它的名字所说的那样,它的最大的优点就是它使用多个线程来扫描及压缩堆。它的缺点就是不管执行的是minor GC还是full GC它都会暂停应用线程。并行回收器最适合那些可以容许暂停的应用,它试图减少由回收器所引起的CPU开销。

  1. CMS回收器

并行回收器之后就是CMS回收器了(concurrent-mark-sweep)。这个算法使用了多个线程(concurrent)来扫描堆并标记(mark)那些不再使用的可以回收(sweep)的对象。这个算法在两种情况下会进入一个”stop the world”的模式:当进行根对象的初始标记的时候 (老生代中线程入口点或静态变量可达的那些对象)以及当这个算法在并发运行的时候应用程序改变了堆的状态使得它不得不回去再次确认自己标记的对象都是正确的。

使用这个回收器最大的问题就是会碰到promotion failure,这是指在回收新生代及年老代时出现了竞争条件的情况。如果回收器需要将年轻的对象提升到年老代中,而这个时候年老代没有多余的空间了,它就只能先进行一次STW(Stop The World)的full GC了——这种情况正是CMS所希望避免的。为了确保这种情况不会发生,你要么就是增加老生代的大小(或者增加整个堆的大小),要么就是给回收器分配一些后台线程以便与对象分配的速度进行赛跑。

这个算法的另一个缺点就是和并行回收器相比,它使用的CPU资源会更多,它使用了多个线程来执行扫描和回收,这样才能让应用持续提供更高级别的吞吐量。对于大多数长期运行的程序而言,应用的暂停对它们是很不利的,这个时候可以考虑使用CMS回收器。尽管如此,这个算法也不是默认开启的。你得指定XX:+UseConcMarkSweepGC来启用它。假设你的堆小于4G,而你又希望分配更多的CPU资源以避免应用暂停,那么这就是你要选择的回收器。然而,如果堆大于4G的话,你可能更希望使用最后的这个——G1回收器。

  1. G1回收器

G1( Garbage first)回收器在JDK 7update 4中首次引入,它的设计目标是能更好地支持大于4GB的堆。G1回收器将堆分为多个区域,大小从1MB到32MB不等,并使用多个后台线程来扫描它们。G1回收器会优先扫描那些包含垃圾最多的区域,这正是它的名字的由来(Garbage first)。这个回收器可以通过-XX:UseG1GC标记来启用。

这一策略减少了后台线程还未扫描完无用对象前堆就已经用光的可能性,而那种情况回收器就必须得暂停应用,这就会导致STW回收。G1的另一个好处就是它总是会进行堆的压缩,而CMS回收器只有在full GC的时候才会干这事。

过去几年里,大堆一直都是一个充满争议的领域,很多开发人员从单机器单JVM模型转向了单机器多JVM的微服务,组件化的架构。这是许多因素所驱动的,包括隔离程序的组件,简化部署,避免重新加载应用类到内存所产生的开销(Java 8中这点已经得到了改善)。

尽管如此,这么做最主要还是希望能避免大堆的GC中长时期的”stop the world”的暂停(在一次大的回收中需要花费数秒才能完成)。像Docker这样的容器技术也加速了这一进程,它们使得你可以很轻松地在同一台物理机上部署多个应用。

Java 8及G1回收器

Java 8 update 20所引入的一个很棒的优化就是G1回收器中的字符串去重(String deduplication)。由于字符串(包括它们内部的char[]数组)占用了大多数的堆空间,这项新的优化旨在使得G1回收器能识别出堆中那些重复出现的字符串并将它们指向同一个内部的char[]数组,以避免同一个字符串的多份拷贝,那样堆的使用效率会变得很低。你可以使用-XX:+UseStringDeduplication这个JVM参数来试一下这个特性。

Java 8及持久代

Java 8中最大的改变就是持久代的移除,它原本是用来给类元数据,驻留字符串,静态变量来分配空间的。这在以前都是需要开发人员来针对那些会加载大量类的应用来专门进行堆比例的优化及调整。许多年来都是如此,这也正是许多OutOfMemory异常的根源,因此由JVM来接管它真是再好不过了。即便如此,它本身并不会减少开发人员将应用解耦到不同的JVM中的可能性。

每个回收器都有许多不同的开关和选项来进行调优,这可能会增加吞吐量,也可能会减少,这取决于你的应用的具体的行为了。在下一篇文章中我们会深入讲解配置这些算法的关键策略。

英文原文链接

相关 [垃圾回收] 推荐:

jvm垃圾回收

- Cano - 淘宝共享数据平台 tbdata.org
在jvm中堆空间划分为三个代:年轻代(Young Generation)、年老代(Old Generation)和永久代(Permanent Generation). 年轻代和年老代是存储动态产生的对象. 永久带主要是存储的是java的类信息,包括解析得到的方法、属性、字段等等. 我们这里讨论的垃圾回收主要是针对年轻代和年老代.

JVM 垃圾回收算法

- - 码蜂笔记
《深入理解Java虚拟机:JVM高级特性与最佳实践》-笔记. 垃圾回收,Garbage Collection,简称GC. 判断对象是否存活一般有两种方式:. 引用计数:每个对象有一个引用计数属性,新增一个引用时计数加1,引用释放时计数减1,计数为0时可以回收. 此方法简单,无法解决对象相互循环引用的问题.

Java中的垃圾回收

- - Java译站
前文中对标记删除算法的介绍更多还是偏理论性质的. 实践中,为了更好地满足现实的场景及需求,还需要对算法进行大量的调整. 举个简单的例子,我们来看下JVM需要记录哪些信息才能让我们得以安全地分配对象空间. 碎片及整理(Fragmenting and Compacting). JVM在清除不可达对象之后,还得确保它们所在的空间是可以进行复用的.

Java垃圾回收调优

- - 编程语言 - ITeye博客
在Java中,通常通讯类型的服务器对GC(Garbage Collection)比较敏感. 通常通讯服务器每秒需要处理大量进出的数据包,需要解析,分解成不同的业务逻辑对象并做相关的业务处理,这样会导致大量的临时对象被创建和回收. 同时服务器如果需要同时保存用户状态的话,又会产生很多永久的对象,比如用户session.

谈谈ActionScript垃圾回收(下)

- Tomyail - Kevin Cao's Blog
前文我们介绍了GC的工作机制和帮助GC更好工作的最佳实践. 其实只要我们遵守谁创建谁清理的原则来管理对象,就能基本上避免回收失败,也就是我们通常说的内存泄漏问题. 但是在实际项目中我们还会看到各种原因引起的内存泄漏,接下来就让我们一起来找出病因. 首先我们需要观察症状,也就是内存的使用曲线. 排查的方法是反复执行一些创建和删除对象的方法、反复加载和卸载子文件.

谈谈ActionScript垃圾回收(上)

- Jia - Kevin Cao's Blog
在《给AS程序员的一点建议一文》中我提到了释放资源的重要性. 最近在一些项目过程中我又对这方面有了更多的理解,在此希望能够分享给大家. 首先让我们来回顾一下关于垃圾回收(Garbage Collection,下文简称GC)的一些知识. 要阅读本文,你需要对GC机制有些基本认识. 在ActionScript中,我们没有API可以直接删除一个对象,也不能控制Player进行GC.

JVM垃圾回收(GC)原理

- kill - yiihsia[互联网后端技术]_yiihsia[互联网后端技术]
引用计数(Reference Counting). 原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数. 垃圾回收时,只用收集计数为0的对象. 此算法最致命的是无法处理循环引用的问题. 标记-清除(Mark-Sweep). 第一阶段从引用根节点开始标记所有被引用的对象,第二阶段遍历整个堆,把未标记的对象清除.

HotSpot 垃圾回收算法实现

- - 码蜂笔记
《深入理解Java虚拟机:JVM高级特性与最佳实践》-笔记. 在可达性分析期间整个系统看起来就像被冻结在某个时间点上,不可以出现分析过程中对象引用关系还在不断变化的情况. 一致性要求导致GC进行时必须停顿所有Java执行线程. 即使在号称不会发生停顿的CMS收集器中,枚举根节点时也是必须停顿的. HotSpot使用的是准确式GC,当执行系统停顿下来后,并不需要一个不漏地检查完所有执行上下文和全局的引用位置,这是通过一组称为OopMap的数据结构来达到的.

Erlang进程堆垃圾回收机制

- - CSDN博客推荐文章
原文: Erlang进程堆垃圾回收机制. 作者:http://blog.csdn.net/mycwq. 每个Erlang进程创建之后都会有自己的PCB,栈,私有堆. erlang不知道他创建的进程会用到哪种场合下,所以一开始分配的内存比较小. 如果分配的空间不够了,erlang gc会动态调整堆大小以满足需求,如果分配的空间大了,就会收缩堆,回收内存.