ZGC、Shenandoah和G1中的改进使开发人员比以往任何时候都更接近无暂停时间。

在过去的六个月中,一些最令人兴奋的发展是JDK垃圾收集器(GC)的不断发展,首先,我们将介绍Shenandoah,它是一种低延迟GC,主要与应用程序同时运行;我们还将介绍作为jdk12部分的ZGC(java11在引入并发GC时的低延迟)的最新改进;我们将详细解释Java9 Start作为G1默认GC的两个改进。

Shenandoah

Shenandoah作为新GC的JDK 12部分。实际上,Shenandoah的开发工作也被移植回JDK8U和11u版本的改进。

Shenandoah团队已经将重点放在SpecJBB的基准测试上,发布了一个针对暂停延迟的基准测试分数,该基准测试将延迟与JDK 9在G1时代做了一个比较。结果证明这是一个很大的进步。

新版JDK中的垃圾收集器:Shenandoah、ZGC和改进的G1插图

Shenandoah stay G1以上的主要进展是在运行应用程序线程时完成更多的垃圾收集周期工作。G1只能在应用程序暂停(移动对象)时清空堆区域,Shenandoah可以在应用程序运行的同时重新定位对象。

为了实现并发重新定位,它使用了所谓的Brooks指针。指针是Shenandoah堆中的每个对象都有一个附加字段,它指向对象本身。Shenandoah这是因为,当它移动一个对象时,它还需要修复堆中引用该对象的所有对象。当Shenandoah将一个对象移动到一个新位置时,指针将保持在原来的位置,这会将引用转发到对象的新位置。引用对象时,应用程序将跟随指向新位置的前向指针。最后,需要清除带有前向指针的旧对象,但通过将清除操作与移动对象本身的步骤分离,Shenandoah更容易同时重新定位对象。

从您开始使用Shenandoah到Java 12,请使用以下选项启用它:

-XX:+UnlockExperimentalVMOptions -XX:+UseShenandoahGC

如果您不能升级到Java12,但您对试用Shenandoah感兴趣,那么您可以使用Java8和Java11的反向移植。值得注意的是,Oracle附加的JDK没有在构建Shenandoah中启用,但是发布者启用的其他OpenJDK。。。默认情况下,Shenandoah。

同时在GC方面,Shenandoah不是唯一的选择。ZGC是OpenJDK,它附带的另一个GC(包括Oracle构建),并且已经在JDK 12中得到了改进。因此,如果您的应用程序遇到垃圾收集暂停,并且您正在考虑尝试Shenandoah,那么我们也应该关注ZGC,我们将在下面讨论它。

使用并发类卸载ZGC

我们的主要目标是低延迟、可扩展性和易用性。因此,ZGC允许Java应用程序继续运行,同时执行除线程堆栈扫描之外的所有其他垃圾收集操作。它可以从数百MB扩展到TB大小的Java堆,同时,始终保持非常低的暂停时间(通常在2毫秒以内)。

对于应用程序开发人员和系统架构师来说,意外的低暂停时间可能会产生深远的影响。开发人员将不再需要担心设计复杂的方法来避免垃圾收集暂停。而且,系统架构师不需要专业的GC性能调优专业知识来实现​​ 低可靠的暂停时间,这对于许多用例来说非常重要。这使得ZGC非常适合于大型内存需求(例如,大数据)的应用程序。但是,对于需要可预测且极短暂停时间的较小堆,ZGC也是一个不错的选择。

ZGC已添加到JDK 11中作为实验函数。保持JDK 12不变,ZGC增加了对并发类卸载的支持,以便允许Java应用程序在卸载未使用的类期间继续运行,而不是暂停执行。

执行并发类卸载是非常复杂的,因此,传统上,类卸载是在整个JVM中完成的—它是在世界停止时的暂停中完成的。首先,确定不再使用的类集,首先,需要执行引用处理。然后是终结者处理——这就是我们所说的系统实现Object.finalize()的意思。作为引用处理的一部分,您必须遍历终结器可访问的对象集,因为终结器可以通过无限链接链传递类,从而保持类处于活动状态。不幸的是,访问终结器可以访问的所有对象可能需要很长时间。在最坏的情况下,您可以访问整个Java pill up.ZGC,并且Java应用程序可以同时运行引用处理(因为ZGC中引入了JDK 11)。

参考处理完成后,ZGC知道哪些类不再需要。下一步是清除由于这些类的死亡而包含过时和无效数据的所有数据结构。清除从活动数据结构到无效或无效数据结构的链接。为此,取消链接操作需要采用的数据结构包括几个内部JVM数据结构,例如,代码缓存(包含所有JIT编译的代码)、类加载器数据图、字符串表、符号表、配置文件、数据等。在断开无效数据结构的链接后,将再次遍历无效数据结构以删除它们,以便最终回收内存。

到目前为止,所有JDK GC都是通过停止操作来完成的,这导致Java应用程序存在延迟问题。对于低延迟GC,存在一个问题。因此,ZGC现在使用Java,应用程序同时运行所有这些函数,因此,不会因支持类卸载而造成延迟损失。实际上,引入执行并发类卸载的机制进一步改善了延迟。

ZGC目前可以作为一个实验GC,用于Linux/x86 64位平台,从Java13开始,可以在Linux/Aarch上层找到使用。您可以使用以下命令行选项启用它:

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC

G1改进

G1还做了一些改进。G1收集器将其垃圾收集周期划分为不同的暂停时间。

分配对象后,最初,对象被视为一代的“年轻”部分。当它们在多个垃圾收集周期中存活下来时,它们最终将“保留”,然后被视为“已使用”。G1地图中的不同区域仅包含一代对象,因此可以称为年轻区域或旧区域。

为了使G1达到暂停时间目标,它需要能够确定在暂停时间目标内可以完成的工作,并在暂停期结束前完成工作。G1有一套复杂的启发式算法来确定正确的工作量,这些启发式算法擅长预测所需的工作时间,但它们并不总是准确的。G1你不能只收集一部分年轻的区域,这使得情况更加复杂。它通过垃圾收集通行证收集所有年轻地区。

保持Java12状态,通过添加abort G1收集暂停来改善这种情况。G1跟踪其启发式算法的准确性,以预测要收集的区域数,并且仅在需要时停止垃圾收集。通过放置收集集(将在一个周期内进行垃圾收集的区域集),它们被分为两组:强制区域和可选区域。

强制区域始终为GC收集。。。在循环中。在时间允许的情况下收集可选区域,如果您在未收集可选区域的情况下超时,则收集通道将停止。强制性区域都是较年轻的区域,也可能有一些较老的区域。根据两个条件将旧区域添加到此集合。添加一些以确保可以继续排空对象,并添加一些以耗尽预期的暂停时间。

启发式计算可以通过将集合候选中的区域数除以-XX:G1MixedAccountTarget的值来计算要添加的区域数。如果G1预测将有更多的时间收集更多的旧区域,它还将向强制区域集添加更多区域,直到可用暂停时间预计将耗尽80%。

这项工作的结果意味着您可以停止或结束混合GC循环。这将导致较低的GC暂停等待时间,并且G1很可能更频繁地达到其暂停时间目标。

快速返回未使用的内存

是的,Java最常见的批评之一是它是一种内存消耗,而不是现在。

有时候,选择。。。JVM从命令行分配了超出其需要的内存。如果没有提供与内存相关的命令行选项,那么JVM可能分配了比所需更多的内存。分配未使用的RAM这是一种浪费,尤其是在云环境中,所有资源都经过测量和正确计算。但是,可以做些什么来解决这种情况,并且可以在资源消耗方面进行改进

一种常见的情况是,必须处理的工作负载随着时间的推移而变化:有时需要更多的内存,有时需要更少的内存。实际上,这通常是不相关的,因为JVM往往在启动时分配大量内存,即使在不需要的时候也会贪婪地保留它。在理想情况下,可以将未使用的内存从JVM访问回操作系统,以便其他应用程序或容器可以使用它。

从Java12开始,现在可以返回未使用的内存。

G1它具有释放未使用内存的功能,但只能在完成垃圾收集过程中释放。完整的垃圾收集通道通常很少,而且也不希望发生,因为它们会导致应用程序暂停很长时间。将JDK 12留在G1中,在并发垃圾收集过程中获得释放未使用内存的功能。此功能对于大多数空堆特别有用。当堆几乎为空时,GC循环可能需要一段时间来获取内存并将其返回到操作系统。为了确保内存快速返回到操作系统,从Java 12开始,如果在命令行指定的时间段内没有发生垃圾收集周期,G1将尝试触发并发垃圾收集周期。G1PeriodicGCInterval参数。然后,并发垃圾收集周期将在周期结束时向操作系统释放内存。

为了确保这些常规并发垃圾收集过程不会增加不必要的CPU开销,它们仅在系统部分空闲时运行。用于触发并发周期是否正在运行的度量是平均一分钟系统负载,该值必须低于指定值G1PeriodicGCSystemLoadThreshold