4个Java垃圾收集器–错误的选择如何严重影响性能
在2014年,对于大多数开发人员来说,还有两件事仍然是个谜:垃圾收集和了解异性。 由于我对后者知之甚少,所以我认为我会对前者大吃一惊,特别是因为在该领域中,Java 8发生了一些重大变化和改进,尤其是去除了PermGen和一些新的令人兴奋的优化(有关此方面的更多信息,请参见结尾)。
当我们谈论垃圾收集时,我们中的绝大多数人都知道这个概念并将其应用于我们的日常编程中。 即使这样,很多事情我们还是不了解,那时候情况就变得很痛苦。 关于JVM的最大误解之一是它有一个垃圾收集器,实际上它提供了四种不同的垃圾收集器,每种垃圾都有其独特的优缺点。 使用哪种选择不是自动的,而是由您自己决定的,并且吞吐量和应用程序暂停之间的差异会很大。
这四种垃圾收集算法的共同点是它们是代生成的,这意味着它们使用古老的假设(即堆中的大多数对象是短寿命的,应Swift回收)将托管堆分成不同的段。 由于这也是一个覆盖面广的领域,因此,我将直接介绍不同的算法,以及它们的优缺点。
1.串行收集器
串行收集器是最简单的一种,您可能不会使用,因为它主要是为单线程环境(例如32位或Windows)和小型堆而设计的。 该收集器会在其工作时冻结所有应用程序线程,这使它无法出于所有意图和目的在服务器环境中使用。
使用方法:您可以通过打开-XX:+ UseSerialGC JVM参数来使用它,
2.并行/吞吐量收集器
接下来是并行收集器。 这是JVM的默认收集器。 就像它的名字一样,它的最大优点是使用多个线程来扫描并压缩堆。 并行收集器的不利之处在于,在执行次要或完全GC收集时,它将停止应用程序线程。 并行收集器最适合可以容忍应用程序暂停并试图优化以减少由收集器引起的CPU开销的应用程序。
3. CMS收集器
紧跟在并行收集器之后的是CMS收集器(“ current-mark-sweep ”)。 该算法使用多个线程(“并发”)在堆(“标记”)中进行扫描以查找可以回收(“清扫”)的未使用对象。 在两种情况下,该算法将进入“世界停止”(STW)模式:初始化根的初始标记(从线程入口点或静态变量可以访问的旧代对象),并且应用程序更改了状态在算法同时运行时将其堆放,迫使其返回并进行最后的修改以确保标记了正确的对象。
使用此收集器时,最大的问题是遇到升级失败 ,这是在收集年轻一代和老一代之间出现种族状况的情况。 如果收集器需要将年轻物体提升给老一代,但又没有足够的时间清理空间,则必须首先这样做,这将导致完整的STW收集-这就是CMS收集器的初衷阻止。 为确保不会发生这种情况,您可以增加旧一代的大小(或为此增加整个堆的大小),或者为收集器分配更多的后台线程,以供他与对象分配率竞争。
与并行收集器相比,此算法的另一个缺点是,它使用更多的CPU,以便通过使用多个线程来执行扫描和收集,从而为应用程序提供更高级别的连续吞吐量。 对于大多数长时间运行的服务器应用程序而言,这不利于应用程序冻结,这通常是一个不错的选择。 即使这样, 默认情况下也不会启用该算法。 您必须指定XX:+ USeParNewGC才能真正启用它。 如果您愿意分配更多的CPU资源以避免应用程序暂停,那么这可能是您可能要使用的收集器,假设您的堆大小小于4Gb。 但是,如果大于4GB,则可能要使用最后一种算法-G1收集器。
4. G1收藏家
JDK 7更新4中引入的Garbage first收集器(G1)旨在更好地支持大于4GB的堆。 G1收集器利用多个后台线程来扫描它划分为多个区域的堆,范围从1MB到32MB(取决于堆的大小)。 G1收集器旨在首先扫描那些包含最多垃圾对象的区域,并为其命名(垃圾优先)。 使用–XX:+ UseG1GC标志打开此收集器。
这种策略有可能在后台线程完成扫描未使用的对象之前耗尽堆,在这种情况下,收集器将不得不停止应用程序,这将导致STW收集。 G1还具有另一个优势,即它可以在移动过程中压缩堆,这是CMS收集器仅在完整STW收集期间执行的操作。
在过去的几年中,大堆一直是一个有争议的领域,许多开发人员从每台机器模型的单个JVM转移到每台机器具有多个JVM的更多微服务,组件化架构。 这是由许多因素驱动的,包括希望隔离不同的应用程序部分,简化部署并避免通常将应用程序类重新加载到内存中所带来的成本(在Java 8中已得到某些改进)。
即使这样,涉及JVM的最大驱动程序之一还是希望避免大堆发生的长时间“停止世界”暂停(在大型集合中可能要花费几秒钟)。 Docker之类的容器技术也加快了这一步,使您能够相对轻松地在同一台物理计算机上部署多个应用程序。
Java 8和G1收集器
Java 8 update 20刚刚推出的另一个漂亮的优化是G1收集器字符串重复数据删除 。 由于字符串(及其内部char []数组)占用了我们的大部分堆空间,因此进行了新的优化,使G1收集器可以识别在整个堆中重复多次的字符串,并更正它们以指向同一内部字符[]数组,以避免同一字符串的多个副本无效地驻留在堆中。 您可以使用-XX:+ UseStringDeduplication JVM参数来进行尝试。
Java 8和PermGen
Java 8中最大的更改之一是删除了堆中的permgen部分,该部分通常分配给类元数据,内部字符串和静态变量。 传统上,这要求开发人员使用的应用程序会加载大量的类(对于使用企业容器的应用程序很常见),以专门针对堆的这一部分进行优化和调整。 多年来,这已成为许多OutOfMemory异常的来源,因此(如果是非常不错的)添加JVM(主要是)要多加注意。 即使这样,它本身也可能不会减少开发人员将其应用程序分离到多个JVM中的浪潮。
这些收集器中的每一个都通过一系列的拨动开关进行不同的配置和调整,每一个都有增加或减少吞吐量的潜力,所有这些都取决于应用程序的特定行为。 在下一篇文章中,我们将探讨配置这些策略的关键策略。
同时,您最想了解关于不同收藏家之间差异的哪些事情? 在评论部分打我!
补充阅读
- 在InfoQ上对G1收集器进行了非常深入的深入审查。
- Java性能–权威指南。 我最喜欢的Java性能书 。
- 在CodeCentric 博客上可以找到有关字符串重复数据删除的更多信息。
翻译自: https://www.javacodegeeks.com/2014/09/garbage-collectors-serial-vs-parallel-vs-cms-vs-g1-and-whats-new-in-java-8.html