在深入研究 JVM 调优之前,您应该首先考虑以下几点:
考虑到您的环境的成本,您可以通过添加硬件而不是花时间调优JVM来获得更多的收益。
从长远来看,优化稳定性比优化性能更有效,但两者确实存在重叠
在开始调整之前,您应该彻底检查您的系统,看看某处是否存在潜在问题,因为调整可能会延迟或暂时隐藏它。如果不监控 JVM,则无法进行 JVM 调优,也无法进行调试。查看需要监控的关键 JVM 性能指标有哪些,哪些是当今可用的最佳监控工具。
不管调优如何,内存泄漏总是会导致垃圾收集(GC)问题。
应用优化考虑所有性能层
虽然很关键,但调优JVM还不足以确保最佳性能。例如,如果一个应用程序有一个糟糕的设计架构或编写糟糕的代码,你不能指望仅仅通过调优JVM就能实现性能的飙升。
更不用说,一旦您优化了前两个,您就需要回到JVM并重新开始。好的调优着眼于整个系统和所有可能影响性能的层,包括数据库和操作系统。
在规划JVM性能调优之前需要知道什么
JVM Main Parameters
JVM参数或JVM参数是特定于Java的值,它改变Java虚拟机的行为,当谈到JVM调优时,您应该知道一些重要的参数,因为在配置、调优和改进JVM的总体性能时会遇到这些参数。
Heap Memory
无论您做什么,当涉及到JVM性能时,您很可能会达到必须初始化堆内存的地步。指定最小和最大堆大小的参数分别是-Xms<堆大小>[unit]和-Xmx<堆大小>[unit]。单元是你想要初始化内存的单元。可以是g (GB), m (MB), k (KB)。
在设置JVM内存的最小和最大堆大小时,您可能需要考虑将它们设置为相同的值。这样你的堆就不必调整大小,节省宝贵的CPU周期。如果你正在使用更大的堆,你也可以通过设置-XX:+ AlwaysPreTouch。
从Java 8开始,Metaspace已经取代了旧的PermGen内存空间。不再有java.lang.OutOfMemoryError: PermGen错误,现在我们可以开始监控java.lang.OutOfMemoryError: Metadata空间的应用程序日志。默认情况下,类元数据分配受可用本机内存数量的限制,我们有一个新的选项来限制Java 8引入的新内存空间的最大大小。但是,仅仅因为空间可以增长到本机内存的极限,并不意味着它总是尽可能多地占据空间。JVM内存的这个区域可以根据需要动态调整大小。
JVM公开的以下属性允许我们控制metspace
XX:MaxMetaspaceSize默认无限制,设置可以分配给类元数据的最大本机内存数量。
XX:MetaspaceSize设置已分配的类元数据的大小,超过该类元数据将触发第一次垃圾收集。默认值和平台相关。
XX:MinMetaspaceFreeRatio在垃圾收集后需要可用的metspace内存区域的最小百分比。如果剩余的内存量低于阈值,则将调整metspace区域的大小。
请记住,监视内存的metspace区域可能是一个非常好的主意,因为该内存区域中的高垃圾收集工作可能表明类或类加载器中的内存泄漏。
更深入地研究,重要的是要注意内存分配参数,以及在调优期间需要它们。它们如下
性能目标
延迟是运行垃圾收集事件所需的时间量。
吞吐量是 JVM 执行应用程序所花费的时间与执行垃圾收集所花费的时间的百分比。
占用空间 是垃圾收集器平稳运行所需的内存量
但是,您不能同时关注所有三个目标,因为从其中任何一个目标获得的性能收益都会导致其他一个或两个目标的性能损失。例如
高吞吐量、低延迟会导致内存占用率增高。
高吞吐量和低内存占用导致更高的延迟
低延迟和低内存占用导致吞吐量降低。
在考虑业务需求时,您必须决定哪两个与您的应用最相关。无论哪种方式,JVM 调优的目标都是优化垃圾收集器,以便您以更少的内存消耗和低延迟获得高吞吐量。但是,更少的内存/低延迟并不自动意味着内存或延迟越少或越低,性能就越好。这取决于您选择关注哪些指标.
JVM调优的原则
Minor GC收集意味着Minor GC应该收集尽可能多的死对象,以降低Full GC的频率
GC 内存最大化 - 它表示 GC 在一个周期内可以访问的内存越多,清理效率越高,收集频率越低
三分之二 - 如上所述,您需要从三个绩效目标中选择两个
首先,您需要记住的是,Java VM调优并不能解决所有性能问题。因此,只有在必要时才应该这样做。也就是说,调优是一个漫长的过程,您很可能会根据压力和基准测试结果执行持续的配置优化和多次迭代。在满足所需指标之前,您可能还需要多次调优参数,从而再次运行测试。
作为一般规则,调优应该首先满足内存使用需求、延迟,最后是吞吐量,您将在下面的部分中看到。
成本——考虑到您的环境,您可以通过添加硬件而不是花时间来调整 JVM 来获得更多收益。
请记住,监视内存的metspace区域可能是一个非常好的主意,因为该内存区域中的高垃圾收集工作可能表明类或类加载器中的内存泄漏。
如何进行JVM性能调优
检测内存占用
要确定内存使用情况,首先需要知道活动数据的大小。活动数据的大小是自应用程序进入稳定阶段以来活动的数据所占用的Java堆的大小;活动数据必须在稳定状态下测量,而不是在启动阶段。在启动阶段,JVM加载并启动应用程序的主要模块和数据;因此,JVM参数还不稳定。
另一方面,稳定阶段意味着应用已经运行了一段时间,并进行了压力测试。更具体地说,当应用程序的工作负载达到生产环境中的业务峰值时满足需求,并在达到峰值后保持稳定时,应用程序就处于稳定阶段。只有这样,每个JVM性能参数才处于稳定状态。
如何确定内存占用
确保使用默认JVM参数执行测试,因为它允许您查看应用程序在稳定阶段需要多少内存。Full GC.你可以看到在稳定状态期间的Full GC日志。您还可以使用最长的Full GC进行估计。
💡
Full GC定义是相对明确的,就是针对整个新生代、老生代、元空间(metaspace,java8以上版本取代perm gen)的全局范围的GC;Major GC 是清理老年代。
GC日志是收集有意义且丰富的数据以帮助进行调优的最佳方法之一。启用GC日志不会影响性能。因此,您甚至可以在生产环境中使用它们来检测问题。
使用以下命令行打开Full GC日志,可用System.gc()方法触发Full GC
💡
-XX:+PrintGC -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:<filename>
但是,如果没有可用的Full GC日志,您可以使用监视工具调用它们,或者使用以下命令启用它们
💡
jmap -histo:live pid
调整延迟
一旦建立了内存占用,下一步就是延迟调优。在这个阶段,堆内存大小和延迟不能满足应用程序的要求。因此,需要根据应用程序的实际需求进行新的调试。您可能必须再次调整堆大小,确定GC持续时间和频率,并决定是否需要切换到另一个垃圾收集器。
确定系统延迟需求
可接受的平均 Minor GC 频率,您将与 Minor GC 的数量进行比较。
可接受的最大Full GC暂停,您将与最长的Full GC周期进行比较。
最大Full GC暂停的可接受频率,您将与Full GC的最高频率进行比较。
可接受的平均小GC暂停时间,您将与小GC持续时间进行比较
您可以通过分别优化年轻代和老代的大小来获得这些数据。您可以在我们的垃圾收集调优指南中了解更多。
调优的吞吐量
在 JVM 性能调优的最后一步,我们对目前得到的结果运行吞吐量测试,然后根据需要进行微调。
基于测试和应用程序的总体需求,应用程序应该有一个固定的吞吐量度量。当达到或超过这个目标时,可以停止调优。
对于垃圾收集,吞吐量调优有两个目的:最小化传递到old area的对象数量,减少Full GC执行时间或Stop-the-World事件。这可能导致低吞吐量。
JAVA 8 VM 重要选项
XX:MaxMetaspaceSize=<metaspace size>[g|m|k]
Java 8不再有永久生成(PermGen),而是需要额外的元空间内存。默认大小将是无限的,我倾向于用一个稍微高的值来限制MaxMetaspaceSize。为了防止应用程序出现问题,JVM不会占用服务器的所有内存。
我建议:让您的应用程序运行几天,以了解它通常使用多少metspace Size。下次重新启动应用程序时,将限制设置为该值的两倍。
XX:+CMSClassUnloadingEnabled
您可能希望允许JVM卸载保存在内存中的类,当不再有代码指向它们。如果您的应用程序生成许多动态类,这就是您想要的。
XX:+UseConcMarkSweepGC
这个选项使JVM使用ConcurrentMarkSweepGC。它可以并行执行很多程序,但在某些情况下,仍然可能发生带STW暂停的完整GC。对于服务器工作负载来说,这种GC仍然是最好的