Java性能调优实战:从一次FullGC到稳住百万并发
2026/9/11 4:40:08 网站建设 项目流程

凌晨两点,告警群炸了。核心交易系统响应时间从50ms飙到3秒,CPU打到98%,订单失败率肉眼可见地往上涨。值班同事第一反应是重启,但重启后不到十分钟,同样的症状再次出现。这一次,我们没有重启,而是决定把根因挖出来。

第一步:抓现场,别急着重启

jstat -gcutil pid 1000的输出触目惊心:老年代使用率98%,Full GC每30秒一次,每次停顿2.5秒,累计Full GC次数已经超过200次。jmap -histo:live pid导出堆直方图,排在第一的是java.util.HashMap$Node,实例数超过800万。什么Map会存这么多东西?顺着代码排查,发现一个本地缓存:每次查询用户信息都往一个静态Map里塞,键是用户ID,值是用户对象,永远不过期。上线三个月,用户量从10万涨到200万,这个Map就成了内存黑洞。

第二步:治标,紧急止血

定位到泄漏点,先改代码:把本地Map换成Caffeine,设置最大容量10万条,写入后30分钟过期。同时,在运维侧临时把堆从4G扩到8G,给修复争取时间。改完上线,Full GC频率从30秒一次降到4小时一次,系统暂时稳住了。

但这不是终点。百万并发是三个月后的目标,以当前的架构,光靠加堆内存解决不了根本问题。

第三步:治本,从GC到架构全面调优

GC选型与参数。原来用的是Parallel GC,暂停时间长。切换到G1,堆16G,设-XX:MaxGCPauseMillis=200,新生代用-Xmn4g,同时开启-XX:+ParallelRefProcEnabled加速引用处理。切换后,Young GC停顿从100ms降到30ms,Full GC几乎消失。

对象分配优化。交易链路中有大量DTO在循环里创建,Young GC频率高。引入对象池复用核心对象,同时用-XX:+UseTLAB默认开启线程本地分配缓冲,减少锁竞争。Young GC频率从每秒3次降到每5秒1次。

线程池精细化。之前所有业务共用一个线程池,核心线程50,队列无界。改成按业务隔离:订单线程池核心100、队列1000、拒绝策略CallerRuns;查询线程池核心200、队列2000、拒绝策略DiscardOldest。同时加-XX:ActiveProcessorCount对齐容器CPU限制,避免线程数按宿主机CPU算。

数据库连接池。HikariCP最大连接数从50调到200,配合-XX:+UseContainerSupport让JVM感知容器内存。慢SQL加索引后,平均查询从200ms降到20ms,连接持有时间大幅缩短。

第四步:压测验证,步步为营

调优不是拍脑袋。用JMeter分阶段压测:500并发→2000→10000→50000。每轮观察TPS、P99响应时间、GC日志、CPU和内存曲线。500并发时TPS 8000,P99 80ms;到10000并发,TPS稳定在12000,P99 150ms;50000并发时出现瓶颈——网卡带宽打满。换万兆网卡,调大TCP缓冲区,最终在80000并发时TPS达到15000,P99 200ms,Full GC一天不超过两次。

百万并发不是单机扛住的,是水平扩展+无状态设计+异步化共同实现的。但单机性能调优是基础——如果单机P99是3秒,加一百台机器也只是把雪崩推迟几分钟。

写在最后

这次调优给我三个教训:第一,本地缓存必须有容量和过期策略,否则就是定时炸弹;第二,GC调优不是调参数,而是先找内存泄漏,再谈参数;第三,百万并发是架构问题,但架构问题往往先从单机性能暴露出症状。别迷信“加机器”,先把单机吃透,再谈分布式。从一次FullGC到稳住百万并发,这条路没有捷径,但有方法:监控→定位→修复→压测→迭代。循环五轮,系统自然脱胎换骨。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询