高效应用程序必须配置的7个JVM参数​

b87c6397fd0cc4e1000141c8e4990478.png围绕垃圾收集和内存,您可以将 600 多个参数传递给 JVM。如果包括其他方面 JVM 参数计数将轻松超过 1000+。争论点太多,任何人都无法消化和理解。在本文中,我们将重点介绍七个重要的 JVM 参数,您可能会发现它们很有用。

1. -Xmx 和 -XX:MaxMetaspaceSize

-Xmx 可能是最重要的 JVM 参数。-Xmx 定义您分配给应用程序的最大堆大小。您可以像这样定义应用程序的堆大小:

-Xmx2g

这带来了一个问题,我的应用程序的正确堆大小是多少?我应该为我的应用程序分配大堆大小还是小堆大小?答案是:这取决于你的服务在承载预期流量时需要多少内存,你可以通过压测或者实际线上流量获得。

元空间是 JVM 的元数据定义(例如类定义、方法定义)将被存储的区域。默认情况下,可用于存储此元数据信息的内存量是无限的(即受容器或机器的 RAM 大小限制)。您需要使用 -XX:MaxMetaspaceSize 参数来指定可用于存储元数据信息的内存量的上限。

-XX:MaxMetaspaceSize=256m

2. GC算法

截至目前 OpenJDK 中有 7 种不同的 GC 算法

a. Serial GC

b. Parallel GC

c. Concurrent Mark & Sweep GC

d. G1 GC

e. Shenandoah GC

f. ZGC

g. Epsilon GC

如果您没有明确指定 GC 算法,那么 JVM 将选择默认算法。在 Java 8 之前,Parallel GC 是默认的 GC 算法。从 Java 9 开始,G1 GC 是默认的 GC 算法。

GC 算法的选择在确定应用程序的性能方面起着至关重要的作用。根据我们的研究,我们正在观察 ZGC 算法的出色性能结果。如果您使用 JVM 11+ 运行,那么您可以考虑使用 ZGC 算法(即 -XX:+UseZGC)。关于 Z GC 算法的更多细节可以 在这里找到。https://wiki.openjdk.java.net/display/zgc/Main

7f2a216fe8068eb34c908f5074a96be0.png

3.启用GC日志

垃圾收集日志包含有关垃圾收集事件、内存回收、暂停时间持续时间的信息……您可以通过传递以下 JVM 参数来启用垃圾收集日志:

从 JDK 1 到 JDK 8:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:{file-path}

从 JDK 9 及更高版本:

-Xlog:gc*:file={file-path}

例子:

-XX:+PrintGCDetails-XX:+PrintGCDateStamps-Xloggc:/opt/workspace/myAppgc.log
-Xlog:gc*:file=/opt/workspace/myAppgc.log

通常 GC 日志用于调整垃圾收集性能。但是,GC 日志包含重要的微观指标。这些指标可用于预测应用程序的可用性和性能特征。GC 吞吐量是您的应用程序花在处理客户事务上的时间与花在处理 GC 活动上的时间的比值。假设您的应用程序的 GC 吞吐量为 98%,那么这意味着应用程序将 98% 的时间用于处理客户活动,其余 2% 用于 GC 活动。

现在让我们看一下健康 JVM 的堆使用图:

9e001b80b5e95356d6ba3d9bfa63690a.png

您可以看到完美的锯齿图案。您可以注意到,当 Full GC(红色三角形)运行时,内存利用率一直下降到底部。

现在让我们看一下有问题 JVM 的堆使用图:

e4c59cb814c855613a505e7c1bb73324.png

您可以注意到图表的右端,即使 GC 反复运行,内存利用率也没有下降。这是应用程序遇到某种内存问题的典型迹象。

如果您仔细查看图表,您会注意到重复的完整 GC 开始发生在上午 8 点左右。但是,应用程序仅在上午 8:45 左右才开始出现 OutOfMemoryError。到上午 8 点,应用程序的 GC 吞吐量约为 99%。但就在早上 8 点之后,GC 吞吐量开始下降到 60%。因为当重复 GC 运行时,应用程序不会处理任何客户事务,它只会执行 GC 活动。作为一种主动措施,如果您发现 GC 吞吐量开始下降,您可以从负载均衡服务器中剔除该 JVM。这样不健康的 JVM 就不会处理任何新的流量。它将最大限度地减少对客户的影响。

cd8bdda62c6cb0a18091b5264ccc75c5.png
重复的 Full GC 发生在 OutOfMemoryError 之前

4.-XX:+HeapDumpOnOutOfMemoryError, -XX:HeapDumpPath

OutOfMemoryError 是一个严重的问题,会影响应用程序的可用性/性能 SLA。要诊断 OutOfMemoryError 或任何与内存相关的问题,必须在应用程序开始遇到 OutOfMemoryError 之前的那一刻或几分钟捕获堆转储。由于我们不知道什么时候会抛出 OutOfMemoryError,因此很难在抛出的时候手动捕获堆转储。但是,可以通过传递以下 JVM 参数来自动捕获堆转储:

-XX:+HeapDumpOnOutOfMemoryError-XX:HeapDumpPath={HEAP-DUMP-FILE-PATH}

在“-XX:HeapDumpPath”中,您需要指定应该存储堆转储的文件路径。当您传递这两个 JVM 参数时,当抛出 OutOfMemoryError 时,堆转储将被自动捕获并写入定义的文件路径。

例子:

-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/crashes/my-heap-dump.hprof

捕获堆转储后,您可以使用 HeapHero、EclipseMAT等工具来分析堆转储

5.-Xss

每个应用程序将有数十、数百、数千个线程。每个线程都有自己的堆栈。在每个线程的堆栈中存储以下信息:

a. 当前执行的方法/函数

b. 原始数据类型

c. 变量

d. 对象指针

e. 返回值。

它们中的每一个都消耗内存。如果它们的消耗超过一定的限制,则抛出 StackOverflowError。但您可以通过传递 -Xss 参数来增加线程的堆栈大小限制。

例子:

-Xss256k

如果将此 -Xss 值设置为一个巨大的数字,那么内存将被阻塞和浪费。假设您将 -Xss 值分配为 2mb,而它只需要 256kb,那么您最终会浪费大量内存,而不仅仅是 1792kb(即 2mb – 256kb)。你想知道为什么吗?

假设您的应用程序有 500 个线程,然后 -Xss 值为 2mb,您的线程将消耗 1000mb 内存(即 500 个线程 x 2mb/线程)。另一方面,如果您仅将 -Xss 分配为 256kb,那么您的线程将仅消耗 125mb 的内存(即 500 个线程 x 256kb/线程)。您将为每个 JVM 节省 875mb(即 1000mb – 125mb)的内存。是的,它会产生如此巨大的变化。

我们的建议是从较低的值(比如 256kb)开始。使用此设置运行彻底的回归、性能和 AB 测试。仅当您遇到 StackOverflowError 时才增加该值,否则请考虑坚持较低的值。

6. -Dsun.net.client.defaultConnectTimeout 和 -Dsun.net.client.defaultReadTimeout

现代应用程序使用多种协议(即 SOAP、REST、HTTP、HTTPS、JDBC、RMI...)来连接远程应用程序。有时远程应用程序可能需要很长时间才能响应。有时它可能根本没有反应。

如果您没有适当的超时设置,并且远程应用程序响应速度不够快,那么您的应用程序线程/资源将被卡住。远程应用程序无响应会影响应用程序的可用性。它可以使您的应用程序陷入停顿。为了保护您的应用程序的高可用性,应配置适当的超时设置。

您可以在 JVM 级别传递这两个强大的超时网络属性,这些属性可以全局适用于所有使用 java.net.URLConnection 的协议处理程序:

sun.net.client.defaultConnectTimeout 指定与主机建立连接的超时时间(以毫秒为单位)。例如,对于 HTTP 连接,它是与 HTTP 服务器建立连接时的超时。sun.net.client.defaultReadTimeout 指定与资源建立连接时从输入流中读取的超时时间(以毫秒为单位)。例如,如果您想将这些属性设置为 2 秒:

-Dsun.net.client.defaultConnectTimeout=2000
-Dsun.net.client.defaultReadTimeout=2000

请注意,这两个属性的默认值为 -1,这意味着没有设置超时。

7.-Duser.timeZone

您的应用程序可能对时间/日期有敏感的业务需求。例如,如果您正在构建一个交易应用程序,您不能在上午 9:30 之前进行交易。要实现那些与时间/日期相关的业务需求,您可能会使用 java.util.Date、java.util.Calendar 对象。默认情况下,这些对象从底层操作系统获取时区信息。这将成为一个问题;如果您的应用程序在分布式环境中运行。请看以下场景:

a. 如果您的应用程序跨多个数据中心运行,例如旧金山、芝加哥、新加坡,那么每个数据中心中的 JVM 最终将具有不同的时区。因此,每个数据中心的 JVM 会表现出不同的行为。这将导致不一致的结果。

b. 如果您在云环境中部署应用程序,应用程序可能会在您不知情的情况下移动到不同的数据中心。同样在这种情况下,您的应用程序最终会产生不同的结果。

c. 您自己的运营团队也可以在不告知开发团队知识的情况下更改时区。它还会扭曲结果。

为了避免这些混乱,强烈建议使用 -Duser.timezone 系统属性在 JVM 上设置时区。例如,如果您想为您的应用程序设置 EDT 时区,您将执行以下操作:

-Duser.timezone=US/Eastern

结论

在本文中,总结了一些重要的 JVM 参数配置。希望对您有所帮助。

5b2bef051f464cf0384b72a034196ed2.png

推荐

使用 FlatBuffers 提高反序列化性能

添加 K8S CPU limit 会降低服务性能?


原创不易,随手关注或者”在看“,诚挚感谢!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值