Foreword
背景
去过 Java 商店。花了整整几个月的时间专门在分布式系统上运行性能测试,主要应用程序是用 Java 编写的。其中一些暗示产品由 Sun 自己(当时的 Oracle)开发和销售。
我将回顾我学到的经验教训、一些关于 JVM 的历史、一些关于内部结构的讨论、解释的几个参数以及最后的一些调整。尽量保持要点,以便您可以将其应用到实践中。
Java 世界中的事物正在快速变化,因此自去年我完成所有这些工作以来,其中的一部分可能已经过时了。 (Java 10 已经出来了吗?)
良好实践
你应该做什么:基准,基准,基准!
当您确实需要了解性能时,您需要针对您的工作负载执行真正的基准测试。没有其他选择。
Also, 你应该监控 JVM。启用监控。好的应用程序通常会提供监控网页和/或 API。除此之外还有常见的 Java 工具(JVisualVM、JMX、hprof 和一些 JVM 标志)。
请注意,通过调整 JVM 通常无法获得性能提升。它更多的是一个“崩溃还是不崩溃,找到过渡点”。这是关于知道当你给予时that您的应用程序的资源量,您可以始终期望that作为回报的表演量。知识就是力量。
性能主要取决于您的应用程序。如果你想要更快,你就必须编写更好的代码。
大多数时候你会做什么:接受可靠的敏感默认设置
我们没有时间优化和调整每个应用程序。大多数时候,我们只会接受合理的默认设置。
配置新应用程序时要做的第一件事是阅读文档。大多数重要的应用程序都附带性能调整指南,包括有关 JVM 设置的建议。
然后您可以配置应用程序:JAVA_OPTS: -server -Xms???g -Xmx???g
-
-server
:启用全面优化(该标志在当今大多数 JVM 上是自动的)
-
-Xms
-Xmx
:设置最小和最大堆(两者始终具有相同的值,这是唯一要做的优化)。
干得好,您已经了解了有关 JVM 的所有优化参数,恭喜!这很简单:D
你永远不应该做的事:
请不要复制您在互联网上找到的随机字符串,尤其是当它们采用多行时,如下所示:
-server -Xms1g -Xmx1g -XX:PermSize=1g -XX:MaxPermSize=256m -Xmn256m -Xss64k -XX:SurvivorRatio=30 -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=10 -XX:+ScavengeBeforeFullGC -XX:+CMSScavengeBeforeRemark -XX:+PrintGCDateStamps -verbose:gc -XX:+PrintGCDetails -Dsun.net.inetaddr.ttl=5 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=`date`.hprof -Dcom.sun.management.jmxremote.port=5616 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -server -Xms2g -Xmx2g -XX:MaxPermSize=256m -XX:NewRatio=1 -XX:+UseConcMarkSweepGC
例如,在谷歌首页上发现的这个东西简直太糟糕了。多次指定的参数具有冲突的值。有些只是强制使用 JVM 默认值(最终是 2 个 JVM 版本之前的默认值)。有一些已经过时并且被简单地忽略了。最后,至少有一个参数是如此无效,以至于它的存在就会在启动时持续导致 JVM 崩溃。
实际调音
如何选择内存大小:
阅读您的应用程序中的指南,它应该给出一些指示。监控生产并随后进行调整。如果需要准确性,请执行一些基准测试。
重要的提示:java进程将花费最大堆加 10%。 X% 的开销是堆管理的开销,不包含在堆本身中。
所有内存通常由进程在启动时预先分配。您可能会看到该进程一直使用最大堆。这根本不是真的。您需要使用 Java 监控工具来查看真正正在使用的内容。
寻找合适的尺寸:
- 如果它因 OutOfMemoryException 崩溃,则内存不足
- 如果没有因 OutOfMemoryException 崩溃,则内存太多
- 如果内存太多,但硬件已获得它和/或已经付费,那么它就是perfect数,工作完成!
JVM6 是青铜级,JVM7 是黄金级,JVM8 是白金级......
JVM 一直在不断改进。垃圾收集是一件非常复杂的事情,有很多非常聪明的人在研究它。它在过去十年中取得了巨大的进步,并将继续如此。
仅供参考。它们是 Oracle Java 7-8 (HotSpot) 和 OpenJDK 7-8 中至少 4 个可用的垃圾收集器。 (其他 JVM 可能完全不同,例如 Android、IBM、嵌入式):
- SerialGC
- 并行GC
- 并发标记清除GC
- G1GC
- (加上变体和设置)
[从 Java 7 及更高版本开始。 Oracle 和 OpenJDK 代码部分共享。两个平台上的 GC 应该(大部分)相同。]
JVM >= 7 有许多优化并选择合适的默认值。它因平台而略有变化。它平衡了多种事物。例如,决定是否启用多核优化,无论 CPU 是否有多个核心。你应该让它去做。请勿更改或强制 GC 设置。
让计算机为你做决定是可以的(这就是计算机的用途)。最好让 JVM 设置始终保持 95% 的最佳状态,而不是在所有机器上强制执行“始终积极收集 8 个核心以缩短暂停时间”(其中一半最终为 t2.small)。
例外:当应用程序附带性能指南和特定调整时。保留提供的设置不变是完全可以的。
Tip:迁移到较新的 JVM 以从最新改进中受益有时可以无需付出太多努力即可提供良好的提升。
特殊情况:-XX:+UseCompressedOops
JVM 有一个特殊的设置,强制在内部使用 32 位索引(读作:类似指针)。这允许寻址 4 294 967 295 个对象 * 8 字节地址 => 32 GB 内存。 (不要与 REAL 指针的 4GB 地址空间混淆)。
它减少了总体内存消耗,并对所有缓存级别产生潜在的积极影响。
现实生活中的例子:ElasticSearch 文档指出,就内存中保存的实际数据而言,正在运行的 32GB 32 位节点可能相当于 40GB 64 位节点。
关于历史的注释:已知该标志在 java-7 之前的时代(甚至可能是 java-6 之前)不稳定。它在较新的 JVM 中已经完美运行了一段时间。
Java HotSpot™ 虚拟机性能增强 https://docs.oracle.com/javase/7/docs/technotes/guides/vm/performance-enhancements-7.html
[...] 在 Java SE 7 中,当未指定 -Xmx 且 -Xmx 值小于 32 GB 时,64 位 JVM 进程默认使用压缩 oop。对于 6u23 版本之前的 JDK 6,请在 java 命令中使用 -XX:+UseCompressedOops 标志来启用该功能。
See:JVM 再次比手动调优领先数年。不过,了解它还是很有趣的 =)
特殊情况:-XX:+UseNUMA
非均匀内存访问(NUMA)是一种用于多处理的计算机内存设计,内存访问时间取决于相对于处理器的内存位置。来源:维基百科 https://en.wikipedia.org/wiki/Non-uniform_memory_access
现代系统具有极其复杂的内存架构,具有跨核心和 CPU 的多层内存和缓存(私有和共享)。
很明显,访问当前处理器中二级缓存中的数据比必须从另一个插槽一直访问记忆棒要快得多。
我相信所有的多socket今天销售的系统在设计上是 NUMA,而所有消费者系统都不是。使用命令检查您的服务器是否支持NUMAnumactl --show
在Linux上。
NUMA 感知标志告诉 JVM 优化底层硬件拓扑的内存分配。
性能提升可能是巨大的(即两位数:+XX%)。事实上,如果有人从“NOT-NUMA 10CPU 100GB”切换到“NUMA 40CPU 400GB”,如果他不了解该标志,他可能会经历性能的[戏剧性]损失。
Note:有讨论检测 NUMA 并在 JVM 中自动设置标志http://openjdk.java.net/jeps/163 http://openjdk.java.net/jeps/163
Bonus:所有打算在大型硬件(即 NUMA)上运行的应用程序都需要针对它进行优化。它并不特定于 Java 应用程序。
走向未来:-XX:+UseG1GC
垃圾收集的最新改进是G1 收集器(阅读:垃圾优先) http://www.oracle.com/technetwork/java/javase/tech/g1-intro-jsp-135488.html.
它适用于高核心、高内存系统。绝对至少 4 核 + 6 GB 内存。它针对的是使用 10 倍及以上的数据库和内存密集型应用程序。
简而言之,在这些规模下,传统 GC 面临着太多数据无法立即处理,并且暂停变得失控。 G1 将堆分成许多小部分,这些小部分可以在应用程序运行时独立并行管理。
第一个版本于 2013 年推出。现在已经足够成熟,可以用于生产,但不会很快成为默认版本。对于大型应用程序来说,这值得一试。
不要碰:世代大小(NewGen、PermGen...)
GC 将内存分成多个部分。 (不赘述,可以google一下“Java GC Generations”。)
上次我花了一周时间在每秒点击 10000 次的应用程序上尝试 20 种不同的代标记组合。我得到了从 -1% 到 +1% 的巨大提升。
Java GC 生成是一个值得阅读或撰写相关论文的有趣主题。它们不是需要调整的东西,除非您是那 1% 的人中的一员,他们可以在真正需要优化的 1% 人中投入大量时间来获得微不足道的收益。
结论
希望这可以帮到你。享受 JVM 带来的乐趣。
Java是世界上最好的语言和最好的平台!去传播爱吧:D