4款Java垃圾回收器——错误的选择导致糟糕的性能
现在已经是2014年了,但是对大多数开发人员而言有两件事情仍然是个谜——垃圾回收以及异性(码农又被嘲笑了)。由于我对后者也不是特别了解,我想我还是试着说说前者吧,尤其是随着Java 8的到来,这个领域也发生了许多重大的变化及提升,其中最重要的莫过于持久代(PermGen)的删除以及一些令人振奋的新的优化(后面会陆续提及这些)。
说起垃圾回收,许多人都了解它的概念,也在日常的编程中有所应用。尽管如此,仍有许多我们不太了解的东西,而这正是痛苦的根源。关于JVM最大的误解就是认为它只有一个垃圾回收器,而事实上它有四个不同的回收器,每个都各有其长短。JVM并不会自动地选择某一个,这事还得落在你我的肩上,因为不同的回收器会带来吞吐量及应用的暂停时间的显著的差异。
这四种回收算法的共同之处在于它们都是分代的,也就是说它们将托管的堆分成了好几个区域,它假设堆中的许多对象的生命周期都很短,可以很快被回收掉。介绍这块内容的已经很多了,因此这里我打算直接讲一下这几个不同的算法,以及它们的长处及短处。
1. 串行回收器
串行回收器是最简单的一个,你都不会考虑使用它,因为它主要是面向单线程环境的(比如说32位的或者Windows)以及比较小的堆。这个回收器工作的时候会将所有应用线程全部冻结,就这一点而言就使得它完全不可能会被服务端应用所采用。
如何使用它:你可以打开-XX:+UseSerialGC这个JVM参数来使用它。
2. 并行/吞吐量回收器
下一个是并行回收器( Parallel collector)。这是JVM的默认回收器。正如它的名字所说的那样,它的最大的优点就是它使用多个线程来扫描及压缩堆。它的缺点就是不管执行的是minor GC还是full GC它都会暂停应用线程。并行回收器最适合那些可以容许暂停的应用,它试图减少由回收器所引起的CPU开销。
3. 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回收器。
4. 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中的可能性。
每个回收器都有许多不同的开关和选项来进行调优,这可能会增加吞吐量,也可能会减少,这取决于你的应用的具体的行为了。在下一篇文章中我们会深入讲解配置这些算法的关键策略。
英文原文链接
分享到:
相关推荐
"Java垃圾回收机制详解" Java垃圾回收机制是Java语言的核心技术之一。垃圾收集(GC)是Java虚拟机(JVM)中的一种机制,用于清除不再使用的对象。GC通过确定对象是否被活动对象引用来确定是否收集该对象。 垃圾...
对象搜索算法与回收算法介绍了垃圾回收的基础算法,相当于垃圾回收的方法论。接下来就详细看看垃圾回收的...下面来看看各个收集器的特性Serial,是单线程执行垃圾回收的。当需要执行垃圾回收时,程序会暂停一切手上的
Java垃圾回收器在不同设备环境下的性能分析.pdf
Java是一种高性能、跨平台的面向...自动内存管理(垃圾回收): Java具有自动内存管理机制,通过垃圾回收器自动回收不再使用的对象,使得开发者不需要手动管理内存,减轻了程序员的负担,同时也减少了内存泄漏的风险。
在第二篇《》,我解释了JVM实际上是如何执行垃圾回收的,我们如何监控GC,以及那哪些具可以让我们的工作更快,更高效。在第三篇文章中,我们会基于实际的例子来解释一些优化GC的最佳实践。我认为在阅读本篇文章之前...
产品与服务:提供垃圾分类指导服务、垃圾回收服务、废品收购服务以及可回收物再利用服务。通过建立线上平台和线下回收网络,实现垃圾分类与资源回收的一体化服务。 定价策略:根据不同类型和规模的客户,制定相应的...
出于同样的理由,不同的人却又不同的看法,C程序员认为内存管理太重要了,所以不能由系统来管理,并且讥笑Lisp程序慢如乌龟的运行速度。的确,在那个对每一个Byte都要精心计算的年代GC的速度和对系统资源的大量占用...
"基于深度学习的可回收垃圾分类算法" 深度学习是基于人工神经网络的机器学习,通过学习数据表征来进行工作。深度学习架构包含有卷积神经网络、深度神经网络和循环神经网络等。深度学习是模仿人类大脑,通过学习数据...
在堆中分配的内存,由JAVA虚拟机的自动垃圾回收器来管理。在堆中产生了一个数组或对象后,还可以在栈中定义一个特殊的变量,让栈中这个变量的取值等于数组或对象在堆内存中的首地址,栈中的这个变量就成了数组或对象...
C++提出了一些更为深入的概念,它所支持的这些面向对象的概念容易将问题空间直接地映射到程序空间,为程序员提供了一种与传统结构程序设计不同的思维方式和编程方法。因而也增加了整个语言的复杂性,掌握起来有一定...
垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分...
垃圾回收器通过跟踪对象的引用来判断哪些对象不再被使用,当这些对象不再被引用时,垃圾回收器会自动回收这些对象的内存。垃圾回收器还可以进行内存整理和压缩,以提高内存的使用效率。 4. 解释一下Java中的多线程...
由Java语言与C/C++对象在内存管理方式的不同,引出了Java语言的优势技术——垃圾处理技术。通过对GC工作原理的阐述及对一些传统的垃圾收集器的分析,提出了一种新的垃圾处理算法,一定程度上改善和提高了Java垃圾...
HotSpot的垃圾回收机制采用分代回收,堆分为年轻代和老年代,非堆就是持久带。面对不同的代,采用不同的回收策略。 而年轻代又可以分为1个Eden和2个Survivor。对于Eden,大多数对象都是先在此区域开辟空间,存储年轻...
虽然本文是以.NET作为目标来讲述GC,但是GC的概念并非才诞生不久。早在1958年,由鼎鼎大名的图林奖得主John McCarthy所实现的Lisp语言就...出于同样的理由,不同的人却又不同的看法,C程序员认为内存管理太重要了,所
垃圾回收器通常是作为一个单独的低级别的线程运行,不可预知的情况下对内存堆中已经死亡的或者长时间没有使用的对象进行清楚和回收,程序员不能实时的调用垃圾回收器对某个对象或所有对象进行垃圾回收。回收机制有分...