垃圾收集(GC)在许多现代编程语言中执行动态内存管理。对于开发人员来说,复杂的垃圾收集减轻了担心内存管理的负担。 本文比较了Java垃圾收集器,并解释了如何使用应用程序的吞吐量、延迟和占用空间要求来选择适合您需要的合适的垃圾收集器。 选择垃圾收集器 应用程序在定义和使用变量时动态分配和释放内存。在Java中,JVM从操作系统分配内存,并在每次请求新变量时将其提供给应用程序。在一个或多个后台线程中运行的垃圾收集确定应用程序仍引用内存的哪些部分,并回收未引用的内存以供应用程序重用。 Java提供了许多垃圾收集器来满足不同的应用程序需求。为您的应用程序选择合适的垃圾收集器主要影响其性能。基本标准是:吞吐量:有用的应用程序活动所花费的总时间与内存分配和垃圾收集的百分比。例如,如果您的吞吐量为95,这意味着应用程序代码在95的时间内运行,而垃圾收集在5的时间内运行。您希望为任何高负载业务应用程序提供更高的吞吐量。延迟:应用程序响应能力,受垃圾收集暂停的影响。在与人或某个活动进程(例如工厂中的阀门)交互的任何应用程序中,您都希望尽可能降低延迟。足迹:进程的工作集,以存储空间和缓存来衡量。 不同的用户和应用程序有不同的要求。有些人想要更高的吞吐量并可以承受更长的交换延迟,而其他人则需要低延迟,因为即使非常短的暂停时间也会对他们的用户体验产生负面影响。在物理内存有限或有许多进程的系统上,占用空间可能决定了可扩展性。在接下来的部分中,我们将使用这些应用程序要求来讨论和比较以下垃圾收集器:SerialcollectorParallelcollectorGarbagefirst(G1)collectorZcollectorShenandoahcollectorConcurrentMarkSweep(CMS)collector(deprecated)串行收集器Serialcollector 这个垃圾收集器在一个线程上执行它的所有工作。使用单个线程可以提高效率,因为多个线程之间没有通信开销。 串行收集器最适合单处理器机器,因为多处理器机器可以从多线程中受益。对于具有小数据集的应用程序,也可以在多处理器机器上使用串行收集器。对于可以容忍暂停并创建非常小的堆的应用程序,此收集器可能是最佳选择。 串行收集器是分代垃圾收集器。如本系列的第1部分所述,一代是一组年龄相似的对象。分代垃圾收集器将所有对象的集合分成几代,并在一次传递中收集一个或多个代中的所有对象。 启用串行收集器:XX:UseSerialGC 在某些硬件和操作系统配置上默认选择串行收集器,您可以使用以下命令显式启用收集器XX:UseSerialGC编译器选项。并行收集器Parallelcollector 并行收集器也称为吞吐量收集器,因为当吞吐量比延迟更重要时,它通常是最佳选择。当可以接受长时间的暂停时,您可以使用并行收集器,例如批量数据处理、批处理作业等。 并行收集器与串行收集器一样,是一种分代垃圾收集器。它们之间的主要区别在于并行收集器运行多个线程来加速垃圾收集。 如果应用程序要求实现最高吞吐量并且可以接受一秒或更长时间的暂停,则并行收集器可能是合适的。并行收集器可用于具有在多处理器或多线程机器上运行的大中型数据集的应用程序。 启用并行收集器:XX:UseParallelGC 使用XX:UseParallelGC选项以启用此收集器。并行收集器还允许您通过其他编译器选项配置其多个参数:XX:ParallelGCThreadsn指定垃圾收集器线程的数量。XX:MaxGCPauseMillisn指定最大暂停时间的目标(以毫秒为单位)。默认情况下,暂停时间没有限制,但使用此选项,暂停时间为n预计或更少毫秒。XX:GCTimeRation有助于实现应用程序的吞吐量目标。此选项以1(1设置用于垃圾收集的时间量n)的比率。例如,XX:GCTimeRatio24将目标设置为125,因此总时间的4用于垃圾收集。默认值为99,这会导致1的时间花在垃圾收集上。垃圾优先(G1)收集器 G1是为具有大量内存的多处理器机器设计的服务器式收集器。收集器试图实现高吞吐量和短暂停时间,同时需要很少的调整。在某些硬件和操作系统上默认选择G1,并且可以通过XX:UseG1GC选项。 G1被称为主要并发收集器,因为它与应用程序同时执行一些昂贵的工作。G1也是一个区域化和分代垃圾收集器,这意味着堆被划分为多个大小相等的区域。启动时,JVM会设置区域大小,根据堆大小的不同,区域大小从1MB到32MB不等。目标是不超过2048个区域。Eden、survivor和oldgeneration(在描述本系列的第1部分中)是这些区域的逻辑集合,并且不连续。 G1收集器可以为满足以下一项或多项标准的应用程序实现高吞吐量和低延迟:大堆大小:具体来说,超过6GB,其中超过50被活动对象占用。在应用程序运行期间,垃圾收集代之间的分配和提升率可能会有很大差异。堆中有大量碎片。需要将暂停限制在几百毫秒内。 字符串重复数据删除:XX:UseStringDeduplication 从JDK8update20开始,G1收集器通过提供了另一种优化字符串重复数据删除,这可以将应用程序的堆使用量减少约10。这XX:UseStringDeduplication编译器选项会导致G1收集器查找重复字符串并在对重复项执行垃圾收集时保留对一个字符串的单个活动引用。目前没有其他Java垃圾收集器支持字符串重复数据删除。 我建议您在测试环境中使用这些选项运行您的应用程序,以查看它们是否实现了内存使用量的减少,然后在生产中启用这些选项。 额外的G1编译器选项 以下是与G1收集器相关的选项摘要:XX:UseG1GC启用G1垃圾收集器。XX:UseStringDeduplication启用字符串重复数据删除。XX:PrintStringDeduplicationStatistics如果使用上一个选项运行,则打印详细的重复统计信息。XX:StringDeduplicationAgeThresholdn导致达到的年龄的字符串对象n个垃圾回收周期被视为重复数据删除的候选对象。默认值为3。 欲了解更多关于G1垃圾收集器,请参阅介绍G1垃圾回收和收集和阅读G1垃圾收集日志。您还可以阅读G1收集器调优以获取G1性能改进建议。Z垃圾收集器(ZGC) ZGC是一种低延迟垃圾收集器,适用于非常大(数TB)的堆。与G1一样,ZGC与应用程序同时工作。ZGC是并发、单代、基于区域、NUMA感知和压缩。它不会停止应用线程的执行超过10毫秒。 此收集器适用于需要非常短暂停时间的具有大量内存的应用程序。Z垃圾收集器作为一项实验性功能提供,并通过XX:UnlockExperimentalVMOptionsXX:UseZGC命令行选项。 使用ZGC时,设置最大堆大小非常重要,因为收集器的行为取决于分配率差异和数据集的活跃程度。ZGC在更大的堆上工作得更好,但浪费不必要的内存也是低效的,因此您需要调整内存使用和可用于垃圾收集的资源之间的平衡。Shenandoah收集器 Shenandoah是另一个暂停时间很短的垃圾收集器。它通过与应用程序同时执行更多垃圾收集工作来减少暂停时间,包括并发压缩。Shenandoah的暂停时间与堆大小无关。垃圾收集2GB堆或200GB堆应该有类似的短暂停行为。 Shenandoah最适合需要响应性和短暂停时间的应用程序,而不管堆大小要求。您可以通过XX:UseShenandoahGC编译器选项。并发标记扫描收集器(已弃用) ConcurrentMarkSweep(CMS)收集器自JDK9起已被弃用(在两个JDK增强提案JEP291和JEP363中进行了讨论),建议改用G1收集器。 该CMS收集器已经优选的,这需要很短的垃圾收集暂停时间和可与垃圾收集器共享处理器资源的应用程序运行时的应用程序。当长寿命年老代很高并且应用程序在具有两个或更多可用处理器的机器上运行时,此收集器提供更多好处。CMS收集器可以通过XX:UseConcMarkSweepGC编译器选项。 CMS是一个分代垃圾收集器,收集老年代。通过与应用程序线程同时执行垃圾收集(特别是标记和清除操作),它可以确保应用程序中的暂停时间较短。但是,如果CMS收集器无法在老年代填满之前清除未引用的对象,或者如果对象分配不能满足老年代的可用空间,CMS将停止所有应用程序线程执行垃圾收集。CMS垃圾收集器无法并发完成垃圾收集的状态称为并发模式失败,这表明的重要性调整CMS收集器参数。 当CMS抛出OutOfMemoryError时 如果超过98的应用程序总时间花在垃圾收集上,而在连续五个垃圾收集周期中回收的堆不到2,则CMS会抛出一个OutOfMemoryError错误。此功能旨在防止应用程序长时间运行而由于堆太小而进展甚微或没有进展。如果需要,您可以通过添加选项来禁用错误XX:UseGCOverheadLimit到命令行。 CMS已被弃用,以加速中其他垃圾收集器的开发HotSpot。消除CMS将减轻GC代码库的维护负担并加速新的开发。因此,通过使用CMSXX:UseConcMarkSweepGCJDK9中的选项会导致以下警告消息:JavaHotSpot(TM)64BitServerVMwarning:IgnoringoptionUseConcMarkSweepGC;supportwasremovedinversion 从长远来看,G1垃圾收集器旨在替代ConcurrentMarkSweep收集器的大多数用途。较新的Z和Shenandoah收集器也可以与最新的JDK而不是CMS一起使用。如果这些收集器都不能满足您的应用程序要求,您仍然可以使用并发标记扫描,只要它在早期版本中仍然受支持。结论 选择正确的垃圾收集器在很大程度上取决于您的应用程序的要求及其行为。本文根据吞吐量、延迟和占用空间对比了六种Java垃圾收集器。您可以使用此信息来选择最适合您的应用程序的垃圾收集器。有许多垃圾收集器可用,因此您需要在对预期的生产负载进行适当测试后做出选择。