0

0

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

夢幻星辰

夢幻星辰

发布时间:2025-09-05 17:15:02

|

641人浏览过

|

来源于php中文网

原创

答案是通过切换至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无法有效回收。

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

  1. 切换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
  2. 分析G1行为与调整: 切换到G1后,Full GC虽然减少了,但G1的Young GC停顿时间依然偏长,且偶尔出现
    concurrent mode failure
    。这表明G1的并发标记周期没能及时完成,老年代在并发回收前就满了。通过分析GC日志,我们注意到G1的默认
    InitiatingHeapOccupancyPercent
    (默认45%)对于我们的应用来说可能太高了。也就是说,G1直到堆内存使用率达到45%才开始并发标记,但此时老年代增长过快,导致标记来不及。
  3. 精细化参数调整:
    • 降低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以上)的应用,并且对停顿时间有较高要求。

PPT.AI
PPT.AI

AI PPT制作工具

下载

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

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

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

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

  1. 堆大小参数:

    -Xms
    -Xmx

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

    -Xmn
    -XX:NewRatio

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

    -XX:SurvivorRatio

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

    -XX:MaxTenuringThreshold

    • 作用: 设置对象在新生代中经历多少次GC后晋升到老年代(默认15)。
    • 确定值: 这个参数与SurvivorRatio协同工作。如果你的应用中有很多对象在新生代存活时间稍长,但最终还是会被回收,可以适当调高这个值,让它们在新生代多存活一段时间,避免过早进入老年代。反之,如果调低,则会加速对象进入老年代。
  5. 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调优没有银弹,它需要耐心、细致的分析和持续的实践。

相关文章

数码产品性能查询
数码产品性能查询

该软件包括了市面上所有手机CPU,手机跑分情况,电脑CPU,电脑产品信息等等,方便需要大家查阅数码产品最新情况,了解产品特性,能够进行对比选择最具性价比的商品。

下载

本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

443

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

605

2023.08.10

堆和栈的区别
堆和栈的区别

堆和栈的区别:1、内存分配方式不同;2、大小不同;3、数据访问方式不同;4、数据的生命周期。本专题为大家提供堆和栈的区别的相关的文章、下载、课程内容,供大家免费下载体验。

443

2023.07.18

堆和栈区别
堆和栈区别

堆(Heap)和栈(Stack)是计算机中两种常见的内存分配机制。它们在内存管理的方式、分配方式以及使用场景上有很大的区别。本文将详细介绍堆和栈的特点、区别以及各自的使用场景。php中文网给大家带来了相关的教程以及文章欢迎大家前来学习阅读。

605

2023.08.10

线程和进程的区别
线程和进程的区别

线程和进程的区别:线程是进程的一部分,用于实现并发和并行操作,而线程共享进程的资源,通信更方便快捷,切换开销较小。本专题为大家提供线程和进程区别相关的各种文章、以及下载和课程。

765

2023.08.10

页面置换算法
页面置换算法

页面置换算法是操作系统中用来决定在内存中哪些页面应该被换出以便为新的页面提供空间的算法。本专题为大家提供页面置换算法的相关文章,大家可以免费体验。

497

2023.08.14

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

37

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

136

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

47

2026.03.10

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Kotlin 教程
Kotlin 教程

共23课时 | 4.4万人学习

C# 教程
C# 教程

共94课时 | 11.3万人学习

Java 教程
Java 教程

共578课时 | 81.6万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号