性能优化

生产环境中 Java 21 启动参数

生产环境中 Java 21 启动参数

D瓜哥
在 OpenJDK 21 升级指南 中,给大家分享了一下升级到 OpenJDK 21 中遇到的一些问题。文末留了一个小问题:生产环境的 Java 21 启动参数怎么配置?这篇文章将给出 D瓜哥的答案。 先说明一下生产环境的机器配置:4C8G,四个内核,8G 内存。 启动参数 鉴于 JVM GC 性能测试(二):递增流量 和 JVM GC 性能测试(三):真实流量 中,G1 GC 的惊艳表现,这里分别提供 Gen ZGC 和 G1 GC 两个配置。 两个配置差距级小,为了方便复制粘贴,还是分两个来展示。 Gen ZGC 配置 追求极致低延迟,就上 GenZGC,它通过牺牲大约 10% 的吞吐量,换来无与伦比的低延时。 注意:使用时,请修改日志目录! ## 变量配置 ####################################################################### # java -XshowSettings:all --展示所有配置项(测试发现也不全) -Dfile.encoding=UTF-8 # https://zhuanlan.zhihu.com/p/455313866 # https://zhuanlan.zhihu.com/p/455746995 # https://blog.csdn.net/u014149685/article/details/83002405 # 随机数来源 -Djava.security.egd=file:/dev/./urandom -Djava.security=file:/dev/./urandom # https://docs.oracle.com/javase/8/docs/api/java/net/doc-files/net-properties.html # https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/net/doc-files/net-properties.html # DNS 过期时间 -Dnetworkaddress.cache.ttl=10 # -Dsun.net.client.defaultConnectTimeout=60000 -Dsun.
JVM GC 性能测试(三):真实流量

JVM GC 性能测试(三):真实流量

D瓜哥
JVM GC 性能测试系列: JVM GC 性能对比方法 JVM GC 性能测试(一):相同流量 JVM GC 性能测试(二):递增流量 JVM GC 性能测试(三):真实流量 书接上文,在 JVM GC 性能测试(二):递增流量 的最后,D瓜哥提到了一个问题,对于在 JVM GC 性能测试(一):相同流量 和 JVM GC 性能测试(二):递增流量 中存在的巨大 QPS 差异疑惑不解。所以,D瓜哥决定将测试机器接入到线上环境,在真实访问中,观察各个 GC 的表现。 一言以蔽之 J21-Gen-ZGC 和 J21-G1 无论在稳定性,吞吐量以及响应时效性上都非常优秀。 再极端峰值情况,J21-G1 是更好的选择,更加稳定,不容易出凸点。 日常使用,J21-Gen-ZGC 响应性更好,接口耗时更低。 鉴于 OpenJDK 21 G1 GC 一如既往的惊艳表现,D瓜哥准备整理一下 G1 GC 的主要优化,敬请关注: Java G1 垃圾收集器主要优化。 1. 服务调用监控数据 监控服务调用的相关数据,这是对于用户来说,感知最强烈的相关数据,也是直接关系到服务质量的数据。 1.1. 服务调用次数 从调用次数上来看,五个分组没有大的变化,可以说根本没有达到系统的极限峰值。当然,这才是正常现象,如果日常运行都爆峰值,那说明系统早该扩容了。 图 1. 服务调用次数(秒级) 图 2. 服务调用次数(分钟级) 1.2. 服务调用耗时 整体上讲,J21-Gen-ZGC 的耗时更短,从数据上来看,TP999 能比 J21-G1 的少 10~20ms;TP99 更加夸张,J21-Gen-ZGC 的耗时只有 J21-G1 的一半。
JVM GC 性能测试(二):递增流量

JVM GC 性能测试(二):递增流量

D瓜哥
JVM GC 性能测试系列: JVM GC 性能对比方法 JVM GC 性能测试(一):相同流量 JVM GC 性能测试(二):递增流量 JVM GC 性能测试(三):真实流量 在上一篇文章 JVM GC 性能测试(一):相同流量 中,D瓜哥使用一个总量请求对所有分组的所有机器进行性能测试。但是,经过测试发现了一个问题,同时产生了另外一个问题,有两个问题没有得到很好的解答: 由于服务响应时长直接关系到服务调用次数,当某一台机器出现问题时,整体调用次数就会急剧下降,调用次数加不上去。一个机器出问题,所有机器的访问量就上不去了。这是测试中发现的一个问题。当然,这属于测试工具的问题,别不是 GC 的问题。但是,也影响到我们的压测,也需要解决。 上次测试,这是针对某一个指定服务调用量进行性能测试,那么,无法确定每个 GC 能支撑的极限调用峰值。另外,在极限峰值和超极限峰值的情况下,各个 GC 的表现如何?这个也有待验证。 针对上述两个问题,设计了本次测试。测试方法如下: 各个分组使用一套相同的流量策略: 各个分组几乎同时开始执行测试任务; 调用量从低到高,以此同时使用相关的调用量进行测试; 除最开始预热阶段的调用量外,后续每个调用量都持续进行十分钟的测试。 针对每个 GC 分组单独设定一套调用发量程序,这个保证各个 GC 分组直接不相互影响。 最后,再分析调用量相同时段的各个 GC 表现,就可以看到各个 GC 的极限峰值。 为了保留更多细节,本文所有截图都是在 34 吋带鱼屏下,使用全屏模式展示并截图的。如果看不清楚,可以右击在新页面打开图片来查看。 具体流量及时间段: 750, 23:14:30 ~ 23:19:30 800, 23:19:30 ~ 23:29:30 850, 23:29:30 ~ 23:39:30 900, 23:39:30 ~ 23:49:30 950, 23:49:30 ~ 23:59:30 1000,23:59:30 ~ 00:09:30
JVM GC 性能测试(一):相同流量

JVM GC 性能测试(一):相同流量

D瓜哥
JVM GC 性能测试系列: JVM GC 性能对比方法 JVM GC 性能测试(一):相同流量 JVM GC 性能测试(二):递增流量 JVM GC 性能测试(三):真实流量 在上一篇文章 JVM GC 性能对比方法 介绍了性能对比的方法,这篇文章就根据该方法对上述提到的5种 JVM GC 进行性能测试。 在正式测试之前,D瓜哥进行了多次小流量试探性测试,来探索一个合适的量。找到一个比较平稳的量后,乘以机器数量,获得一个每秒总计请求量,最后使用该总量数据去做压测。 根据多次测试的数据来看,最后选择的是每台每秒 500 QPS,5 个分组,每个分组 5 台机器,所以,每秒的请求总量是: 500 * 5 * 5 = 12500 QPS;每个分组每分钟的总量是:500 * 5 * 60 = 150000 QPS。使用每台机器以此使用 100 QPS,200 QPS,300 QPS,400 QPS 各运行一分钟来对系统进行预热。最后以每台每秒 500 QPS 的访问量来对测试机器进行持续十分钟的性能测试,最后分析这十分钟的相关数据。 一言以蔽之 服务稳定性:J21-Gen-ZGC、J21-G1、J8-G1 稳定性最好;J17-ZGC 有轻微波动;J21-ZGC 有剧烈波动; 服务耗时 TP999:J21-Gen-ZGC < J17-ZGC < J21-G1 < J8-G1 < J21-ZGC;
JVM GC 性能对比方法

JVM GC 性能对比方法

D瓜哥
JVM GC 性能测试系列: JVM GC 性能对比方法 JVM GC 性能测试(一):相同流量 JVM GC 性能测试(二):递增流量 JVM GC 性能测试(三):真实流量 现在部门内部绝大部分应用都还在使用 OpenJDK 8,计划推进部门升级 JDK 到 OpenJDK21。本着实事求是,用数据说话的原则,准备对如下 GC 做性能测试: OpenJDK 8 G1 GC(以下称 J8-G1。具体版本号:1.8.0_321-b07。) OpenJDK 17 ZGC(以下称 J17-ZGC。具体版本号:17.0.9+9。) OpenJDK 21 G1(以下称 J21-G1。具体版本号:21.0.2+13-LTS。) OpenJDK 21 ZGC(以下称 J21-ZGC。具体版本号:21.0.2+13-LTS。) OpenJDK 21 Gen ZGC(以下称 J21-Gen-ZGC。具体版本号:21.0.2+13-LTS。) 所有 OpenJDK 版本都是选用相同大版本号里的最高的版本。所有的机器都是 4C8G 的配置,JVM 堆栈内存设置为 4608M 。 为了减少不必要的干扰,JVM 相关参数也尽可能做到了一致或者接近。(等测试完,D瓜哥会把相关参数也分享出来。) 测试对象 由于D瓜哥所处的部门是一个直接面向用户的线上业务部门,所以,大部分系统是直接面对用户,接受用户访问的在线业务系统。所以,为了服务线上业务系统的需求,测试对象的选择就限定在了类似的场景中。测试对象是线上接受用户访问的一个服务。结构如下: 图 1. 压测接口依赖关系图 该接口有外部依赖服务,也有数据库查询,是一个微服务架构下典型的在线服务接口。 测试方法 原本计划是想直接通过上线,将线上不同分组的机器使用不同的 GC 来做测试,但是,这样面临好几个问题: 由于正是环境上线需要审批,每次上线是针对一个构建包做上线,由于基于三个不同版本的 OpenJDK,所以,至少需要上线三次。 线上环境,不同分组的机器数量是不一样的,所以,不方便对比。比如,对比不同分组的响应请求数量。 性能测试势必会影响到线上的业务处理。如果引发客诉,鱼有没有迟到不确定,但是绝对惹一身腥。
Spring Boot 应用程序浪费的内存

Spring Boot 应用程序浪费的内存

D瓜哥
当今世界被广泛浪费的资源之一是:内存。由于编程效率低下,内存浪费量惊人(有时 "令人震惊")。我们在多个企业应用程序中都看到了这种情况。为了证明这一点,我们进行了一项小型研究。我们分析了著名的 Spring Boot Pet Clinic 应用程序,看看它浪费了多少内存。该应用程序由社区设计,旨在展示如何使用 Spring 应用程序框架构建简单但功能强大的面向数据库的应用程序。 环境 Spring Boot 2.1.4.RELEASE Java SDK 1.8 Tomcat 8.5.20 MySQL 5.7.26 with MySQL Connector/J 8.0.15 压力测试 我们使用流行的开源压力测试工具 Apache JMeter 进行压力测试。我们使用以下设置执行了 30 分钟的压力测试: 线程数(用户)- 1000(连接到目标的用户数量) 上升周期(秒) - 10。所有请求开始的时间范围。根据我们的配置,每 0.01 秒将启动 1 个新线程,即 100 个线程/秒。 循环次数 - 永久。这 1000 个线程将背靠背执行测试迭代。 持续时间(秒) - 1800。启动后,1000 个线程持续运行 1800 秒。 图 1. JMeter 设置 我们在负载测试中使用了以下场景: 在系统中添加新的宠物主人。 查看宠物主人的相关信息。 向系统中添加一只新宠物。 查看宠物相关信息。 在宠物探视历史中添加探视信息。 更新宠物相关信息。 更新宠物主人的相关信息。 通过搜索主人姓名查看主人信息。 查看所有主人的信息。 如何测量内存浪费? 业界有数百种工具可以显示内存使用量。但是,我们很少遇到能测量因低效编程而浪费的内存量的工具。 HeapHero 是一款简单的工具,它可以分析堆转储,并告诉我们由于编程效率低下而浪费了多少内存。
GC 调优成功案例:减少新生代的大小

GC 调优成功案例:减少新生代的大小

D瓜哥
当对垃圾回收性能做调优时,不仅能改善垃圾回收暂停时间,还能改善整个应用程序的响应时间并降低云计算成本。最近,我们帮助调整了一个流行应用程序的垃圾回收行为。仅仅是一个微小的改动,就带来了巨大的改善。让我们在这篇文章中讨论一下这个垃圾回收调整的成功案例。 垃圾收集关键绩效指标 有句名言叫“无法衡量的东西就无法优化”。说到垃圾回收的调整,您只需关注 3 个主要关键绩效指标 (KPI): GC 暂停时间 GC 吞吐量 CPU 消耗量 垃圾回收运行时,会暂停应用程序。“GC 停顿时间”表示应用程序在垃圾回收事件中停顿的时间。该指标以秒或毫秒为单位。 “GC 吞吐量”表示应用程序处理客户事务的总时间与处理垃圾回收活动的总时间之比。该指标以百分比为单位。例如,如果有人说他的应用程序的 GC 吞吐量是 98%,这表明该应用程序有 98% 的时间用于处理客户活动,其余 2% 的时间用于处理垃圾回收活动。 即使是处理一个简单的请求,现代应用程序也会创建成千上万个对象。因此,垃圾收集器必须在后台不断运行,以释放为每个请求创建的成千上万个对象。因此,垃圾回收往往会消耗大量的 CPU。因此,在调整垃圾回收性能时,还应研究 CPU 消耗。要了解有关这些 KPI 的更多信息,请参阅: 内存调整: 关键性能指标。 如何获取这些 KPI? 在调优垃圾回收性能时,垃圾回收日志是您最好的朋友。您可以通过 这篇文章 给出的 JVM 参数在应用程序中启用垃圾回收日志。建议始终开启垃圾回收日志,因为它能提供丰富的信息,有助于预测中断、排除生产问题并帮助进行容量规划。此外,启用垃圾收集不会给应用程序增加任何明显的开销。 启用垃圾收集日志后,您可以使用免费的垃圾收集日志分析工具,如 GCeasy、 IBM GC & Memory visualizer 和 Google Garbage cat 等,查看上述关键绩效指标。 在下面这篇文章,教你 如何进行 GC 日志分析。 垃圾回收行为基线 介绍到此为止。让我们回到本文最初的主题。我们在这个流行的应用程序上启用了垃圾回收日志。我们让应用程序运行了 24 小时。然后,我们将生成的 GC 日志文件上传到 GCeasy 工具。该工具提供了具有洞察力的图表和 GC KPI。该应用程序的 GC 吞吐量为 96.176%,平均暂停时间为 12.429 秒。
应用程序的内存是大还是小?

应用程序的内存是大还是小?

D瓜哥
应该在内存容量大的少量实例(即机器)上运行应用程序,还是在内存容量小的大量实例上运行应用程序?哪种策略是最佳的?这个问题可能会经常遇到。在开发应用程序长达 20 年,且构建了 JVM 性能工程/故障排除工具( GCeasy、 FastThread,、 HeapHero)之后,我仍然不知道这个问题的正确答案。同时,我相信这个问题也没有非黑即白的答案。在本文中,我想与大家分享一下我对这个问题的看法和经验。 两个数十亿美元企业的故事 由于我们的 JVM 性能工程/故障排除工具已广泛应用于各大企业,因此我有机会看到世界级企业应用的实际实施情况。最近,我有机会参观了两家高速成长的科技公司(如果我说出他们的名字,读这篇文章的人都会知道)。这两家公司的总部都在硅谷。它们的业务是技术,因此在工程设计方面很有一套。它们是华尔街的宠儿,享有极高的估值。它们的市值高达数十亿美元。它们是现代企业蓬勃发展的典型代表。在我们的对话中,让我们称这两家企业为公司 A 和公司 B。 在内存大小方面,两家企业都采用了两个极端,这让我感到非常惊讶。公司 A 将堆大小(即 -Xmx)设置为 250GB,而公司 B 则将堆大小设置为 2GB:公司 A 的堆大小是公司 B 的 125 倍。两家公司都对自己的内存大小设置很自信。俗话说:"事实胜于雄辩",两家企业都在扩大规模,处理数十亿的关键业务交易。 两家公司都从事相同的业务,收入/市值大致相同,位于同一地理区域,在同一时间点采用两种极端的内存大小,这真是一次绝佳的体验。鉴于这种现实生活中的真实经验,正确的答案是什么?大内存还是小内存?我的结论是:如果你有一支优秀的团队,采用这两种策略都能取得成功。 大内存容量可能很昂贵 与内存容量小、实例数量多的情况相比,内存容量大、实例(即机器)数量少的情况往往成本较高。以下是基于美国东部(弗吉尼亚州北部)地区 AWS EC2 实例成本的简单计算: m4.16xlarge - 256GB 内存 - Linux 按实例收费:3.2 美元/小时 T3a small - 2GB 内存 - Linux 按实例收费:0.0188 美元/小时 因此,要获得 256GB 内存容量,我们必须获得 128 个 “T3a small” 实例(即 128 个实例 x 2GB = 256GB)。 128 x T3a small - 2GB 内存 - Linux 按实例收费:2.
如何实现 GC 的高吞吐量?

如何实现 GC 的高吞吐量?

D瓜哥
三四十年前,开发人员负责释放在应用程序中创建的对象。业务应用程序相当复杂,有不同的工作流、用例和场景。即使开发人员在某个场景中少释放一个对象,对象也会在内存中累积,造成内存泄漏。Java 于 1995 年推出时,承诺自动进行垃圾回收。它将删除对象的责任从开发人员转移到了 Java 虚拟机(JVM),从而彻底改变了内存管理。整个行业都积极拥抱了这一创新理念,因为开发人员不再需要操心手动内存管理。从那时起,自动垃圾回收已成为所有现代编程语言的默认功能。 在本篇文章中,我们将探讨垃圾回收过程中的一个关键性能指标:"GC 吞吐量"。我们将了解它的含义、在 Java 应用程序中的重要性以及它对整体性能的影响。此外,我们还将深入探讨提高 GC 吞吐量的可行策略,为现代软件开发释放其优势。 什么是垃圾回收吞吐量? 每当运行自动垃圾回收事件时,应用程序都会停顿,以识别内存中未引用的对象并将其释放。在停顿期间,不会处理任何客户请求。垃圾回收吞吐量请求应用程序处理客户请求的时间占多大比例,垃圾回收活动的时间占多大比例。例如,如果有人说他的应用程序的 GC 吞吐量是 98%,这意味着他的应用程序有 98% 的时间用于处理客户请求,其余 2% 的时间用于处理垃圾回收活动。 高 GC 吞吐量是可取的,因为它表明应用程序有效地利用了系统资源,从而减少了停顿,提高了整体性能。相反,GC 吞吐量低会导致垃圾回收停顿时间增加,影响应用程序的响应速度,造成性能瓶颈。监控和优化 GC 吞吐量对于确保应用程序的顺利执行和响应速度至关重要。在下一节中,我们将探讨查找应用程序 GC 吞吐量的方法,并了解如何解释结果以优化 Java 应用程序性能。继续… 如何找到应用程序的 GC 吞吐量? 垃圾回收日志是研究 GC 性能的最佳来源。如果你的应用程序运行在 JVM 上,你可以通过 如何进行 GC 日志分析 文章中提到的 JVM 参数启用 GC 日志。启用 GC 日志后,让应用程序处理流量至少一天,以观察高流量和低流量时段各自的运行情况。之后,可以将生成的 GC 日志文件上传到 GC 日志分析工具,以获得有价值的分析结果。一些常用的 GC 日志分析工具包括 GCeasy、 IBM GC & Memory visualizer 和 Google Garbage cat 等。这些工具将报告 GC 吞吐量以及其他重要的 GC 指标。下面是 GCeasy 工具的摘录,展示了包括 GC 吞吐量在内的各种 GC 关键性能指标 (KPI) 报告。
Java ZGC 调优

Java ZGC 调优

D瓜哥
ZGC 是一种专门的垃圾回收器,主要用于管理大型堆和尽量减少 Java 应用程序中的停顿。它能应对在内存密集型工作负载和一致的响应时间至关重要的情况下的垃圾回收的挑战。ZGC 利用并发处理能力和先进的算法,为优化现代 Java 应用程序的性能提供了有效的解决方案。在本篇文章中,将专门探讨调整 ZGC 以提高性能的技术。不过,如果想了解更多基础知识,可以观看在 JAX 伦敦会议上发表的 垃圾回收调优 讲座。 如何启用 ZGC? 确保使用的 Java 版本支持 ZGC。OpenJDK 从 JDK11 开始支持 ZGC。在启动应用程序时添加以下 JVM 参数,这样就可以在 Java 应用程序中启用 ZGC 垃圾收集器: # D瓜哥 · https://www.digauge.com -XX:+UseZGC -XX:+ZGenerational (1) 1 D瓜哥注:分代 ZGC 从 OpenJDK 21+ 开始支持。 何时使用 ZGC? 如果应用符合其中任何一项要求,就可以考虑使用 ZGC: 堆大小较大:ZGC 特别适合堆容量较大的应用程序,堆容量通常在数十 GB 或更大。如果应用需要大量内存,ZGC 的低延迟特性将使其成为一个令人信服的选择。 低延迟要求:当应用需要一致的响应时间和低延迟性能时,ZGC 将大显身手。在需要最大限度缩短垃圾回收暂停时间的情况下,ZGC 表现出色,特别适合交互式应用和实时性应用。 具有不同工作负载的应用:ZGC 专为处理不同的工作负载而设计,因此适用于内存使用模式不可预测的应用。无论应用程序经历的是周期性的,突发性的,还是富有变化性的负载,ZGC 都能有效地适应这些调整。 ZGC 调优参数 ZGC 是 Java 中的一种垃圾收集器,它采用了一种不同的调优方法:将暴露的 JVM 参数数量降至最低。与需要细粒度调整的传统垃圾收集器不同,ZGC 专注于优化大型堆的管理,同时以最小的配置开销提供高效的垃圾收集。这种精简的方法允许开发人员主要关注一个关键的 JVM 调整参数:堆大小。