突破JVM性能瓶颈:通过GC调优实现TPS提升200%的实战案例

答案是通过切换至G1 GC、降低IHOP、优化新生代大小、调整并发线程数并结合代码优化,成功将TPS提升超200%。核心在于分析GC日志,识别对象晋升过快与Full GC主因,针对性调整JVM参数并优化内存分配密集的业务代码,最终实现GC停顿大幅降低和吞吐量显著提升。

突破jvm性能瓶颈:通过gc调优实现tps提升200%的实战案例

突破JVM性能瓶颈,通过GC调优实现TPS提升200%并非神话,它背后是对系统深层次的理解和精准的干预。核心在于识别应用程序的内存使用模式和垃圾回收行为,然后针对性地调整JVM参数,优化GC策略,从而减少停顿时间,提高吞吐量。这通常是一个诊断、实验、验证的迭代过程,但回报往往是显著的性能飞跃。

解决方案

我记得有一次,我们负责的一个核心交易系统,在业务高峰期TPS总是上不去,响应时间也经常毛刺。监控数据显示,CPU使用率并不高,但GC活动异常频繁,特别是老年代的Full GC,每次都能让系统“卡”上几秒钟,这在生产环境简直是灾难。

我们当时的系统配置是默认的JDK8,使用的是Parallel GC。问题出现后,第一步当然是收集GC日志。通过

jstat -gcutil

jmap -histo:live

,我们发现新生代对象晋升老年代的速度非常快,而且老年代在短时间内就被填满,触发Full GC。更深一步分析,GC日志里充斥着

promotion failure

concurrent mode failure

(在尝试切换到G1后)。这说明我们的应用存在大量的瞬时对象,并且有部分大对象直接进入了老年代,导致GC无法有效回收。

我们的解决路径大致是这样的:

切换GC算法并初步调优: 考虑到系统是大内存(32GB堆),并且对停顿时间有一定要求,我们决定从Parallel GC切换到G1 GC。初期配置了

-XX:+UseG1GC -Xms30g -Xmx30g -XX:MaxGCPauseMillis=200

,并开启详细GC日志:

-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+PrintGCApplicationStoppedTime -Xloggc:gc.log

分析G1行为与调整: 切换到G1后,Full GC虽然减少了,但G1的Young GC停顿时间依然偏长,且偶尔出现

concurrent mode failure

。这表明G1的并发标记周期没能及时完成,老年代在并发回收前就满了。通过分析GC日志,我们注意到G1的默认

InitiatingHeapOccupancyPercent

(默认45%)对于我们的应用来说可能太高了。也就是说,G1直到堆内存使用率达到45%才开始并发标记,但此时老年代增长过快,导致标记来不及。精细化参数调整:降低IHOP: 我们尝试将

-XX:InitiatingHeapOccupancyPercent

从45%降到30%,甚至20%。这让G1能更早地启动并发标记,为回收争取更多时间。调整G1 Young Gen大小: 发现Young GC耗时主要因为新生代过大,导致每次GC需要处理大量存活对象。虽然G1是自适应的,但我们可以通过

-XX:G1NewSizePercent

-XX:G1MaxNewSizePercent

给它一个合理的范围。我们观察到新生代对象大部分是短生命周期的,适度减小新生代比例,让它们更快被回收,减少晋升到老年代的机会。优化业务代码: 这是最关键的一环。通过

JProfiler

Arthas

工具,我们定位到几处高频创建大对象的地方,比如一些集合类没有预设大小,导致频繁扩容;或者在循环中重复创建临时对象。对这些代码进行了优化,减少了不必要的内存分配。增大

-XX:ConcGCThreads

适当增加并发GC线程数,让G1的并发标记和清除阶段能更快完成,尤其是在多核CPU环境下。

经过这一系列诊断和迭代调优,特别是结合了业务代码的优化,我们成功将系统的Full GC几乎消除,Young GC的平均停顿时间也控制在了50ms以内。最终,在相同的硬件资源下,系统的TPS从原来的不足1000提升到了3000+,超过了200%的提升,响应时间也变得非常稳定。这不仅仅是参数调整,更是对整个系统运行机制的深入理解。

为什么我的应用TPS上不去,GC日志里总是Full GC?

应用TPS低,GC日志里频繁出现Full GC,这通常是JVM性能瓶颈最直接的信号。但原因往往不单一,它可能指向几个核心问题。首先,最常见的是内存泄漏或对象生命周期管理不当。如果你的应用持续创建对象但无法及时释放,或者持有对不再使用对象的引用,那么老年代就会迅速膨胀,最终触发Full GC。这些Full GC不仅耗时,还会暂停整个应用线程,直接导致TPS下降。

其次,默认的JVM参数不适合当前应用的负载。很多时候,我们直接使用JDK的默认GC配置,但这些默认值是为了通用性而非特定高性能场景设计的。比如,新生代太小可能导致对象过早晋升老年代;老年代过小则更容易被填满。当应用程序的内存分配速率远超GC回收速率时,Full GC就成了必然。

再者,存在大量短生命周期的大对象。如果你的业务逻辑频繁创建占用内存较大的临时对象,这些对象在新生代存活时间很短,但因为体积大,可能直接被分配到老年代,或者很快就占据了新生代的大部分空间,导致频繁的Young GC和快速的老年代增长。

要诊断这类问题,你不能只看“Full GC”这个表象。你需要深入分析GC日志,关注每次GC的耗时、回收了多少内存、以及各个代(新生代、老年代)的内存使用情况和对象晋升情况。

jstat -gc

可以帮你实时监控GC统计信息,而

jmap -histo:live

则能帮你找出当前堆中存活对象的数量和大小,配合

jstack

查看线程栈,往往能定位到是哪段代码在“吃”内存。

如何选择合适的GC算法,G1真的比CMS好吗?

选择GC算法从来都不是一个“哪个最好”的简单问题,它更像是一个“哪个最适合我的应用场景”的权衡。G1(Garbage-First)和CMS(Concurrent Mark Sweep)是Java 8及以前版本中,应对大堆和低延迟需求最常用的两种算法,但它们的设计哲学和适用场景有所不同。

CMS的设计目标是最大程度地减少应用停顿时间,它通过并发标记和并发清除来完成大部分工作,但仍然会有STW(Stop-The-World)阶段。CMS的优点是停顿时间短,适合对响应时间敏感的应用。然而,它也有一些明显的缺点:它不进行内存压缩,长时间运行后可能导致内存碎片化,最终可能触发Full GC(

concurrent mode failure

promotion failure

)。另外,CMS在并发标记阶段会占用一部分CPU资源,并且它对浮动垃圾(在并发标记和清除期间产生的垃圾)处理不够完美,可能需要预留更多堆空间。

G1旨在取代CMS,它将堆内存划分为多个大小相等的Region,每个Region可以是Eden、Survivor或Old区。G1的优势在于它能够预测GC停顿时间,通过

MaxGCPauseMillis

参数来设置目标停顿时间,G1会尽量在每次GC中回收最多垃圾的Region,从而实现这个目标。G1在并发标记阶段会识别出哪些Region包含的垃圾最多,优先回收这些Region。G1还具有内存压缩功能,可以有效避免碎片化问题。它更适合大堆内存(通常建议8GB以上)的应用,并且对停顿时间有较高要求。

那么,G1真的比CMS好吗?不一定。对于一些内存较小(比如几GB)且对延迟要求不那么极致的应用,CMS可能表现得很好,甚至比G1更稳定。G1的算法相对复杂,其内部的并发标记和回收过程会消耗更多的CPU资源。如果你的应用CPU资源本身就紧张,G1的额外开销可能反而会带来负面影响。

我的经验是,如果你的应用堆内存较大(8GB+),且对GC停顿有严格要求,那么G1通常是更好的选择。它能提供更可控的停顿时间,并且自带内存压缩,能有效解决碎片化问题。但如果你还在使用Java 8,并且应用堆内存不大,CMS可能仍然是一个不错的选择。对于Java 11及更高版本,ZGC和Shenandoah等更先进的低延迟GC算法则提供了更极致的性能,它们几乎可以做到与堆大小无关的停顿时间,但它们也需要更多的CPU和内存资源。选择哪个,最终还是要看你的应用特点、资源预算和性能目标。

GC调优的常用参数有哪些,怎么确定它们的值?

GC调优的参数种类繁多,但有一些是我们在实战中经常会调整的核心参数。理解这些参数的作用以及如何根据应用行为来确定它们的值,是GC调优的关键。

堆大小参数:

-Xms

-Xmx

作用: 分别设置JVM的初始堆内存和最大堆内存。确定值: 通常建议将两者设置为相同的值,以避免JVM在运行时动态调整堆大小带来的额外开销和不稳定性。这个值应该根据你的应用实际内存需求、并发量以及可用的物理内存来决定。一个经验法则是,让JVM堆大小占据物理内存的60%-80%,留下部分内存给操作系统和其他进程。如果堆过小,会频繁GC;如果过大,可能导致单次GC耗时过长,或者占用过多系统资源。

新生代大小参数:

-Xmn

-XX:NewRatio

作用:

Xmn

直接设置新生代大小,而

NewRatio

(默认2)设置老年代与新生代的比例(即老年代是新生代的2倍)。确定值: 新生代的大小直接影响Young GC的频率和耗时。如果新生代太小,对象会过早晋升老年代,导致老年代GC频繁;如果新生代太大,Young GC的单次停顿时间会变长。通常,对于大部分短生命周期对象应用,新生代可以设置为堆总大小的1/4到1/3。对于G1,可以通过

-XX:G1NewSizePercent

-XX:G1MaxNewSizePercent

来控制新生代占总堆的百分比范围。

Survivor空间比例:

-XX:SurvivorRatio

作用: 设置Eden区与单个Survivor区的比例(默认8,即Eden:S0:S1 = 8:1:1)。确定值: 适当的SurvivorRatio可以确保对象在晋升老年代前有足够的机会在新生代被回收。如果Survivor区太小,对象可能在新生代存活不了几次就晋升老年代;如果太大,则浪费了内存。通常默认值在大多数情况下是合理的,但如果GC日志显示大量对象在经历很少次GC后就晋升,可以考虑适当调整。

对象晋升老年代的年龄阈值:

-XX:MaxTenuringThreshold

作用: 设置对象在新生代中经历多少次GC后晋升到老年代(默认15)。确定值: 这个参数与SurvivorRatio协同工作。如果你的应用中有很多对象在新生代存活时间稍长,但最终还是会被回收,可以适当调高这个值,让它们在新生代多存活一段时间,避免过早进入老年代。反之,如果调低,则会加速对象进入老年代。

G1相关参数:

-XX:MaxGCPauseMillis

-XX:InitiatingHeapOccupancyPercent

作用:

MaxGCPauseMillis

是G1的停顿时间目标,G1会尽量满足这个目标;

InitiatingHeapOccupancyPercent

(IHOP)是G1启动并发标记周期的堆内存使用率阈值(默认45%)。确定值:

MaxGCPauseMillis

应根据你的应用对延迟的实际要求来设定,比如100ms或200ms。IHOP的调整是G1调优的关键,如果并发标记总来不及,导致

concurrent mode failure

,就需要降低IHOP,让G1更早启动标记。这会增加G1的CPU开销,但能有效避免Full GC。

如何确定这些值?

确定这些参数的值,绝不是一次性的配置,而是一个迭代和基于监控的过程:

从默认值开始: 不要一开始就过度调优,先用默认配置运行,收集基线数据。开启详细GC日志:

-Xloggc:gc.log -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps

这些参数是必备的,它们提供了分析GC行为的所有信息。监控与分析: 使用GCViewer、GCEasy等工具分析GC日志,结合

jstat

jmap

VisualVM

等工具实时监控JVM状态。关注GC频率、每次GC的停顿时间、GC后的内存使用情况、对象晋升模式等。负载测试: 在接近生产环境的负载下进行测试,观察GC行为,根据测试结果调整参数。小步快跑: 每次只调整一个或少量参数,然后重新测试和分析,观察效果。这样可以更容易地定位是哪个参数的调整带来了正面或负面影响。理解业务: 最重要的是理解你的应用内存分配模式。是大量短生命周期对象?还是少数几个大对象?是内存泄漏?这些都决定了你的调优方向。

GC调优没有银弹,它需要耐心、细致的分析和持续的实践。

以上就是突破JVM性能瓶颈:通过GC调优实现TPS提升200%的实战案例的详细内容,更多请关注创想鸟其它相关文章!

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 chuangxiangniao@163.com 举报,一经查实,本站将立刻删除。
发布者:程序猿,转转请注明出处:https://www.chuangxiangniao.com/p/85980.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
客户管理子账号怎么用
上一篇 2025年11月17日 08:27:54
资产怎么管理好客户
下一篇 2025年11月17日 08:28:06

相关推荐

  • mysql如何进入编辑模式 mysql输入sql语句创建数据库

    mysql如何进入编辑模式 mysql输入sql语句创建数据库mysql如何进入编辑模式 mysql输入sql语句创建数据库mysql如何进入编辑模式 mysql输入sql语句创建数据库mysql如何进入编辑模式 mysql输入sql语句创建数据库

    创建mysql数据库需登录后执行sql语句;避免sql注入用参数化查询、输入验证、最小权限原则、waf;解决乱码需统一客户端、数据库、表编码为utf8mb4;优化查询性能可通过索引、explain分析、避免select *、使用join、分页优化、定期维护、硬件升级、缓存。 想要用MySQL创建数据…

    2026年9月23日 用户投稿
    1400
  • Asianux 7.3安装Oracle 11.2.0.4单实例体验

    在asianux 7.3环境中安装#%#$#%@%@%$#%$#%#%#$%@_a189c++633d9995e11bf8607170ec9a4b8 11.2.0.4单实例的具体步骤和注意事项如下: 环境:Asianux 7.3 需求:安装Oracle 11.2.0.4 单实例 背景:系统使用默认的…

    2026年9月23日
    200
  • VSCode管理FPGA约束文件(高效编辑方法,时序约束指南)

    使用vscode高效编辑fpga约束文件的方法包括:1. 安装“better comments”和“bracket pair colorizer”等插件以提升可读性和编辑效率;2. 利用代码片段功能创建常用约束模板,如时钟和i/o约束,通过关键词快速插入以减少重复输入和错误;3. 使用支持正则表达式…

    2026年9月23日
    000
  • 如何在Krita中使用AI裁剪图片?快速掌握高效图像裁剪技巧

    如何在Krita中使用AI裁剪图片?快速掌握高效图像裁剪技巧如何在Krita中使用AI裁剪图片?快速掌握高效图像裁剪技巧如何在Krita中使用AI裁剪图片?快速掌握高效图像裁剪技巧如何在Krita中使用AI裁剪图片?快速掌握高效图像裁剪技巧

    Krita虽无内置AI裁剪功能,但可通过其构图辅助线、选区与变换工具实现“智能”裁剪,并结合外部AI工具完成内容扩展与智能构图,形成高效工作流。 ☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜ Krita本身,作为一款强大的开源数字绘画与图像…

    2026年9月23日 用户投稿
    200
  • 在Loom中利用虚拟线程实现递归任务:告别ForkJoinPool的限制

    本文探讨了Java Loom中RecursiveAction和RecursiveTask与虚拟线程的兼容性。由于它们设计上依赖于ForkJoinPool及其特定的工作线程,无法直接与虚拟线程配合使用。文章提供了两种替代方案:一是利用CompletableFuture结合虚拟线程工厂实现自定义递归任务…

    2026年9月23日
    500
  • 《蝎之尾》攻略——游戏配置要求介绍

    《蝎之尾》(tail of scorpios)是由jabberworks打造的一款设定在架空历史背景下的悬疑推理类视觉小说游戏。该游戏不仅剧情引人入胜,画面表现也相当出色,同时对设备的硬件要求较为亲民,最低仅需1.6ghz单核的intel或amd处理器即可运行。 《蝎之尾》最低配置要求如下: 操作系…

    2026年9月23日
    200
  • 微信小店铺首页怎么设置?微信店铺怎么开通

    随着线上商业的发展,越来越多的商家开始借助微信小店铺拓展销售渠道。一个结构清晰、视觉吸引人的首页不仅能提升用户购物体验,还能有效提高成交转化率。本文将详细介绍如何优化设置微信小店铺首页,帮助您打造高效的线上销售入口。 一、首页设计与风格布局 1. 简洁直观 在进行首页设计时,应坚持简洁直观的原则,避…

    2026年9月23日
    100
  • CodeIgniter 动态多数据库连接与数据导入实践指南

    本文详细介绍了在 CodeIgniter 框架中,如何根据用户输入的动态数据库凭证建立并管理第二个数据库连接。通过构建自定义连接配置数组,并利用 CodeIgniter 的数据库加载机制,开发者可以灵活地切换数据库实例,从而实现从外部数据库导入数据到主数据库的功能,提升应用的灵活性和数据处理能力。 …

    2026年9月23日
    000
  • Android自定义开关UI实现教程:打造独特交互体验

    本教程旨在指导开发者如何在Android应用中实现高度定制化的开关UI,摆脱原生组件的限制。我们将探讨两种主要方法:一是利用功能丰富的第三方库快速构建复杂动画效果的开关;二是通过XML Drawable Selector自定义原生ToggleButton的外观,实现简洁高效的视觉定制。 在andro…

    2026年9月23日
    200
  • MICCAI 2020 | 基于3D监督预训练的全身病灶检测SOTA(预训练代码和模型已公开)

    MICCAI 2020 | 基于3D监督预训练的全身病灶检测SOTA(预训练代码和模型已公开)MICCAI 2020 | 基于3D监督预训练的全身病灶检测SOTA(预训练代码和模型已公开)MICCAI 2020 | 基于3D监督预训练的全身病灶检测SOTA(预训练代码和模型已公开)MICCAI 2020 | 基于3D监督预训练的全身病灶检测SOTA(预训练代码和模型已公开)

    ▊ 研究背景介绍 由于深度学习任务通常依赖大量标注数据,医疗图像的标注需要专业知识,标注人员需精确判断病灶的大小、形状、边缘等信息,甚至需要经验丰富的专家进行多次评估,这增加了深度学习在医疗领域应用的难度。 目前,尽管有一些公开数据集(如LIDC-IDRI、LUNA等)可供使用,但这些数据集的图像数…

    2026年9月23日 用户投稿
    200
  • PHP三元运算符为什么有时难读_PHP三元运算符可读性挑战

    三元运算符适用于简单赋值,如设置默认值或二选一,但嵌套使用会降低可读性,增加理解成本,应优先用if-else处理复杂逻辑。 PHP三元运算符(?:)是一种简洁的条件表达式写法,能在一行内完成简单的判断与赋值。虽然它能减少代码行数,但在实际开发中,过度或嵌套使用三元运算符常常导致代码难以阅读和维护。 …

    2026年9月23日
    200
  • 2025内存条最新榜单 内存条品牌排行榜前十名盘点

    为您的电脑挑选合适的内存条是提升整体性能的关键一步。面对市场上琳琅满目的品牌,选择可能变得困难。本文为您整理了2025年最值得关注的内存条品牌排行榜,帮助您清晰地了解各大品牌的特点,为您的设备升级或新机配置提供有力参考。 一、2025内存条品牌排行榜前十名 1、海盗船 (Corsair):作为高端硬…

    2026年9月23日
    100
  • win10提示“由你的组织管理”怎么办_win10组织管理提示解除教程

    首先断开工作或学校账户,再将网络ID改为家庭模式,接着用命令行清除组策略和注册表项,最后手动删除特定注册表路径中的管理策略项并重启电脑。 如果您在使用Windows 10时发现系统设置中出现“由你的组织管理”的提示,这通常意味着某些策略正在控制您的设备设置,导致部分功能受限。以下是解除该管理状态的多…

    2026年9月23日
    000
  • 如何使用TensorFlowLite训练AI大模型?移动端模型优化的教程

    如何使用TensorFlowLite训练AI大模型?移动端模型优化的教程如何使用TensorFlowLite训练AI大模型?移动端模型优化的教程如何使用TensorFlowLite训练AI大模型?移动端模型优化的教程如何使用TensorFlowLite训练AI大模型?移动端模型优化的教程

    TensorFlow Lite通过模型转换、量化、剪枝等优化手段,将训练好的大模型压缩并加速,使其能在移动端高效推理。首先在服务器端训练模型,随后用TFLiteConverter转为.tflite格式,结合量化(如Float16或全整数量化)、量化感知训练、剪枝和聚类等技术减小模型体积、提升运行速度…

    2026年9月23日 用户投稿
    000
  • ​​VSCode的超级生产力指南!这些快捷键让你的编码速度起飞​​

    VSCode的快捷键能显著提升编码效率,掌握核心快捷键如Ctrl/Cmd + P快速打开文件、Ctrl/Cmd + Shift + P调出命令面板、Ctrl/Cmd + D选择下一个匹配项、Alt/Option + Click多光标编辑、Ctrl/Cmd + Shift + L选择所有匹配项、F2重…

    2026年9月23日
    100
  • 如何在mysql中调试触发器逻辑错误

    答案是使用日志表、手动验证逻辑、SIGNAL报错和检查触发器顺序可调试MySQL触发器。通过创建trigger_log表记录执行信息,将触发器逻辑在客户端分步测试,利用SIGNAL主动抛出异常,并用SHOW TRIGGERS检查多触发器冲突,系统化暴露问题。 在 MySQL 中调试触发器逻辑错误没有…

    2026年9月23日
    000
  • 抖音ai分身怎么关闭?抖音AI怎么关闭

    作为广受欢迎的短视频社交平台,抖音通过其AI分身功能为用户带来了更具个性化的推荐体验。但如何停用这一功能也逐渐成为用户关心的问题。本文将为您详细介绍如何关闭抖音的AI分身,并探讨在享受个性化推荐的同时如何保障个人隐私。 一、抖音AI分身功能概述 抖音的AI分身是基于人工智能技术,通过对用户的兴趣偏好…

    2026年9月23日
    000
  • mysql怎么修改索引 mysql索引创建与更新操作教程

    mysql怎么修改索引 mysql索引创建与更新操作教程mysql怎么修改索引 mysql索引创建与更新操作教程mysql怎么修改索引 mysql索引创建与更新操作教程mysql怎么修改索引 mysql索引创建与更新操作教程

    mysql中修改索引的正确方法是删除旧索引并创建新索引,因为mysql不支持直接修改索引结构;1. 创建索引可通过create index或alter table add index实现,用于加速数据检索;2. 删除索引使用drop index或alter table drop index,操作前需…

    2026年9月23日 用户投稿
    200
  • Hibernate 3.6 Criteria API 根别名设置行为解析

    在Hibernate 3.6版本中,使用getSession().createCriteria(Entity.class, “myAlias”)尝试为根实体设置自定义表别名时,生成的SQL语句中的根别名仍可能默认为this_,而非用户指定的别名。这源于Hibernate内部C…

    2026年9月23日
    100
  • VSCode如何管理技术债务 VSCode代码质量跟踪的实用方法

    eslint、pylint等linter类扩展可实时识别代码问题,从源头减少技术债务;2. sonarlint能集成sonarqube规则,深度检测代码异味并提供修复建议;3. code metrics可量化函数圈复杂度等指标,帮助定位高风险代码;4. todo tree将todo、fixme等注释…

    2026年9月23日
    000

发表回复

登录后才能评论
关注微信