项目仓库java-concurrency-security:用于学习使用的的项目。基于 Spring Boot 2.7 的高并发性能调优 安全监控完整脚手架涵盖 JVM 调优、线程池优化、Redis 缓存防护、分库分表、OAuth2.0 认证、API 网关、ELK 日志收集、SkyWalking APM 监控等核心能力。 - AtomGit | GitCode前言为什么要调 JVM打个比方JVM 就像一家餐厅的厨房。堆内存就是厨房的操作台面积GC垃圾回收就是厨师收拾桌面的频率。 操作台太小堆内存不够→ 厨师不停收拾频繁 GC→ 做菜变慢响应变慢 操作台太大但厨师不行堆大但 GC 算法差→ 收拾一次要很久长时间停顿调优的目标让厨房大小合适厨师收拾又快又少。1. 核心参数说明1.1 堆内存设置类比把 JVM 堆想象成你家的储物间。-Xms是入住时就划好的面积-Xmx是最大可扩展面积。 如果一开始不划够-Xms太小后面扩容要装修触发扩容 GC影响性能。参数说明推荐值类比-Xms初始堆大小与-Xmx相同入住就划好固定面积不临时扩建-Xmx最大堆大小物理内存的 60~70%储物间最大面积别占满整个房子-Xmn新生代大小堆的 25%~40%储物间里常用物品区的大小-XX:MetaspaceSize元空间初始大小256m存放说明书的柜子类元数据-XX:MaxMetaspaceSize元空间最大512m说明书柜子上限-XX:MaxDirectMemorySize直接内存上限512m厨房外面的临时操作台NIO用1.2 配置原则堆大小 物理内存 × 60~70% 留 30% 给操作系统和其他进程 新生代 堆 × 25~40% 短命对象多就大一点 元空间 256m~512m 类多就大一点示例8G 内存机器# 8G 机器给 JVM 分 4G一半留 4G 给系统 -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m示例16G 内存机器# 16G 机器给 JVM 分 10G -Xms10g -Xmx10g -Xmn4g -XX:MetaspaceSize256m -XX:MaxMetaspaceSize512m为什么-Xms和-Xmx要设一样就像餐厅开业前就把厨房面积固定好避免营业中扩建导致暂停接客Stop The World。2. GC 算法选择2.1 什么是 GC类比GC 就是餐厅的收桌员。客人走了对象不再被引用收桌员把桌子清理干净回收内存 好让新客人坐下创建新对象。收桌员干活时有些桌子暂时不能用 → 这就是停顿。2.2 对比表GC停顿时间吞吐量适用堆大小JDK 版本类比G1可控目标 200ms高≥ 4GJDK 9 默认分区打扫可控节奏ZGC 10ms较高可达 TBJDK 15边营业边打扫几乎不停Parallel较长最高不限JDK 8 默认关门大扫除扫得最干净CMS低较高 8GJDK 14 已移除老方案已被淘汰怎么选堆 ≥ 4G → 用G1绝大多数场景的默认选择要求极致低延迟金融、实时系统→ 用ZGC后台批处理任务不在乎延迟只要快→ 用Parallel2.3 G1 调优要点G1 的工作原理把堆分成很多小块Region像棋盘一样。GC 时优先清理垃圾最多的区域Garbage First 的由来。-XX:UseG1GC # 启用 G1 -XX:MaxGCPauseMillis200 # 告诉 G1每次停顿别超过 200ms -XX:G1HeapRegionSize4m # 每块区域 4MB像棋盘每格的大小 -XX:InitiatingHeapOccupancyPercent45 # 堆用到 45% 时开始后台清理 -XX:G1ReservePercent10 # 预留 10% 空间应急防止清理来不及MaxGCPauseMillis不要设太小设成 50ms 看似美好但 G1 为了达到这个目标会频繁清理 反而导致吞吐量下降。200ms 是大多数场景的甜点值。2.4 ZGC 调优超低延迟场景-XX:UseZGC -XX:ZGenerational # JDK 21 分代 ZGC性能更好 -Xms8g -Xmx8g # ZGC 不需要设新生代它自己管理ZGC vs G1 的区别G1 像分批打扫每次停一小会儿ZGC 像边营业边打扫几乎不停。 但 ZGC 需要更多 CPU 资源来维持不停顿。3. 新生代/老年代比例类比新生代 快递收发区大部分快递来了就走老年代 仓库长期存放的东西。 如果你的业务大部分请求都是来了就处理完走人如 Web 请求快递区要大一点。场景新生代占比说明举例Web 请求处理40~50%大量短生命周期对象电商下单请求对象用完即弃缓存服务25~30%长生命周期对象多Redis 缓存对象长期驻留内存批处理20~25%对象存活时间长夜间跑批数据对象要活到处理完怎么判断你的场景# 看 GC 日志中 Young GC 的回收率 # 如果每次 Young GC 回收 80% → 说明对象短命 → 新生代可以大一些 # 如果每次只回收 30~50% → 说明对象活得久 → 新生代别太大留给老年代4. GC 日志分析4.1 开启 GC 日志类比GC 日志就像厨房的打扫记录本记录每次打扫的时间、耗时、清理了多少。# JDK 11推荐 -Xlog:gc*:filegc.log:time,uptime,level,tags:filecount5,filesize50m # JDK 8 -XX:PrintGCDetails -XX:PrintGCDateStamps -Xloggc:/data/logs/gc.log4.2 关键指标健康检查清单指标健康值异常时怎么办Young GC 频率每分钟 5 次频率太高 → 新生代太小加大-Xmn单次停顿时间 200msG1太长 → 检查是否有大对象或内存泄漏Full GC 次数趋近于 0出现 → 说明内存不够或有泄漏紧急排查堆回收率每次 30%太低 → 说明对象都在用考虑加大堆真实案例某电商系统上线后频繁 Full GC每小时 2~3 次排查发现是一个本地缓存 没有设上限把老年代塞满了。加上maximumSize限制后 Full GC 消失。5. OOM 排查OOMOut Of Memory厨房完全堆满了再也放不下任何东西。 这时候需要拍照留证Heap Dump然后分析是什么东西占了这么多空间。5.1 配置自动 Dump# OOM 时自动生成堆快照犯罪现场照片 -XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/data/logs/heapdump.hprof5.2 分析工具工具特点适用场景MATMemory Analyzer ToolEclipse 插件自动分析泄漏嫌疑对象离线分析 Dump 文件JVisualVMJDK 自带可视化监控开发环境实时监控Arthas阿里开源在线诊断生产环境不停机排查5.3 常见 OOM 原因及解决原因现象解决方案内存泄漏Dump 中某个对象占 80% 内存检查集合是否只增不减缓存无上限缓存对象持续增长加maximumSize限制大查询一次查出百万条记录加分页限制查询量线程过多每个线程占 1MB 栈空间减少线程池大小6. 生产环境启动脚本完整的 JVM 生产启动脚本参见项目中config/start.sh包含堆内存配置-Xms、-Xmx、-XmnG1 GC 参数GC 日志滚动OOM 自动 DumpSkyWalking Agent 接入一键启动bash config/start.sh7. 快速调优决策树系统卡顿了 ├── GC 停顿时间长 │ ├── 是 Full GC → 检查内存泄漏加大堆 │ └── 是 Young GC → 减小新生代或换 GC 算法 ├── GC 频率太高 │ └── 加大堆 或 加大新生代 ├── 响应慢但 GC 正常 │ └── 不是 JVM 问题查数据库/网络/锁 └── 频繁 OOM └── 开 HeapDump → 用 MAT 分析 → 找到泄漏点