本项目是一个Java最佳实践学习项目,包含性能测试、数据库性能分析、JVM优化、Redis优化等多个模块,旨在提供可操作的性能优化指南和实践案例。
- src/main/java/com/bage/study/best/practice/
- cache/: 缓存相关实现
- config/: 配置类
- controller/: 控制器
- metrics/: 指标监控
- model/: 数据模型
- mq/: 消息队列
- repo/: 数据访问
- service/: 业务逻辑
- trial/: 性能测试相关控制器
- utils/: 工具类
安装
brew install wrk使用示例
# 10线程,50连接,压测90秒
wrk -t10 -c50 -d90s http://localhost:8000/user/insert
# 带延迟统计
wrk -t10 -c100 -d60s -T3s --latency http://localhost:8000/log/insert压测结果分析
- Latency: 响应时间分布
- Req/Sec: 每秒请求数
- Requests/sec: 总体QPS
- Transfer/sec: 每秒传输数据量
启动命令
cd /Users/bage/bage/software/apache-jmeter-5.5/bin
java -jar jmeter.jar慢SQL检测
-- 查看慢查询配置
show variables like '%slow_query%';
-- 开启慢查询日志
set global slow_query_log = 'ON';
set global slow_query_log_file = '/var/log/mysql/slow-query.log';
set global long_query_time = 1;慢SQL示例
http://localhost:8000/mysql/sql/slow?key=64415%20Hudson%20Drives
慢SQL场景测试API
# 全表扫描
http://localhost:8000/mysql/full/table/scan
# 复杂JOIN查询
http://localhost:8000/mysql/complex/join
# 无索引排序
http://localhost:8000/mysql/order/by/no/index
# LIKE前缀模糊匹配
http://localhost:8000/mysql/like/prefix?prefix=xxx
# LIKE中缀模糊匹配(索引失效)
http://localhost:8000/mysql/like/infix?keyword=xxx
# 函数操作(索引失效)
http://localhost:8000/mysql/function/operation慢SQL分析工具
- MySQL慢查询日志
- EXPLAIN分析
- Performance Schema
- MySQL Enterprise Monitor
- Percona Toolkit
JDBC vs 连接池性能对比
- JDBC直连:
http://localhost:8000/data/source/jdbc/get?phone=18119069047 - 连接池:
http://localhost:8000/data/source/pool/get?phone=18119069047
Druid监控
http://localhost:8000/druid/index.html
http://localhost:8000/druid/sql.html1. Druid内置监控
- Web控制台:
http://localhost:8000/druid/index.html - 登录信息: 用户名: sa, 密码: bage
- 功能: 连接池状态、SQL执行统计、慢SQL分析、并发线程监控
2. 自定义监控API
- 连接池基本信息:
http://localhost:8000/data/source/info - 连接池状态:
http://localhost:8000/data/source/status - 连接获取测试:
http://localhost:8000/data/source/get/connection - 并发测试:
http://localhost:8000/data/source/test/concurrency?threadCount=10&iterations=100 - 连接池极限测试:
http://localhost:8000/data/source/test/limit?connectionCount=100 - 连接池性能监控:
http://localhost:8000/data/source/monitor?testCount=50 - 批量测试:
http://localhost:8000/data/source/batch/test?batchSize=100
3. 第三方监控工具
- Prometheus + Grafana: 通过Spring Boot Actuator暴露指标
- JMX: 监控连接池运行时状态
- APM工具: 如SkyWalking、Pinpoint等
连接池状态指标
| 指标 | 说明 | 常规值范围 | 异常值 | 优化建议 |
|---|---|---|---|---|
| activeCount | 当前活跃连接数 | < maxActive | >= maxActive | 增加maxActive或检查连接泄漏 |
| poolingCount | 当前空闲连接数 | 10-30% maxActive | < minIdle 或 > 80% maxActive | 调整minIdle或maxActive |
| waitThreadCount | 等待连接的线程数 | 0-5 | > 10 | 增加maxActive或检查连接释放 |
| usageRate | 连接池使用率 | 20-70% | > 80% 或 < 10% | 调整maxActive或检查连接泄漏 |
| acquisitionTime | 连接获取时间 | < 10ms | > 100ms | 检查连接池配置或数据库响应 |
性能指标
| 指标 | 说明 | 常规值范围 | 异常值 | 优化建议 |
|---|---|---|---|---|
| connectionSuccessRate | 连接获取成功率 | > 99% | < 95% | 检查数据库连接或网络 |
| avgAcquisitionTime | 平均连接获取时间 | < 5ms | > 50ms | 优化连接池配置 |
| errorCount | 错误计数 | 0 | > 0 | 检查错误原因并修复 |
| removeAbandonedCount | 移除的废弃连接数 | 0 | > 0 | 检查连接释放代码 |
SQL执行指标
| 指标 | 说明 | 常规值范围 | 异常值 | 优化建议 |
|---|---|---|---|---|
| slowSqlCount | 慢SQL数量 | 0 | > 0 | 优化SQL语句和索引 |
| avgSqlExecuteTime | 平均SQL执行时间 | < 100ms | > 500ms | 优化SQL语句和索引 |
| sqlExecuteCount | SQL执行次数 | 正常业务量 | 异常增长 | 检查是否存在SQL注入或死循环 |
连接池常见错误
| 错误 | 症状 | 原因 | 解决方案 |
|---|---|---|---|
| 连接泄漏 | activeCount持续增长,最终达到maxActive | 连接未正确关闭 | 使用try-with-resources或确保finally中关闭连接 |
| 连接超时 | 获取连接时抛出timeout异常 | 连接池无可用连接 | 增加maxActive,检查连接释放,设置合理的maxWait |
| 连接失效 | 执行SQL时抛出connection reset异常 | 连接长时间空闲被数据库关闭 | 配置validationQuery和testWhileIdle |
| 内存泄漏 | 应用内存持续增长 | 连接池配置过大,连接对象未释放 | 调整连接池大小,检查连接释放 |
| 线程阻塞 | 应用线程阻塞在获取连接 | 连接池耗尽,连接未及时释放 | 增加maxActive,检查连接释放,使用异步操作 |
数据库常见错误
| 错误 | 症状 | 原因 | 解决方案 |
|---|---|---|---|
| Too many connections | 数据库拒绝连接 | 连接池maxActive超过数据库max_connections | 调整maxActive,增加数据库max_connections |
| Connection reset by peer | 连接突然关闭 | 网络问题或数据库重启 | 检查网络连接,配置合理的连接超时 |
| Communications link failure | 通信链路失败 | 网络问题或数据库服务异常 | 检查网络连接,配置连接重试机制 |
1. 连接池配置优化
// 推荐配置示例
@Bean
dataSource {
DruidDataSource dataSource = new DruidDataSource()
dataSource.url = "jdbc:mysql://localhost:3306/mydb"
dataSource.username = "root"
dataSource.password = "password"
// 基本配置
dataSource.initialSize = 10 // 初始连接数
dataSource.maxActive = 100 // 最大连接数
dataSource.minIdle = 5 // 最小空闲连接数
dataSource.maxWait = 60000 // 获取连接的最大等待时间
// 连接验证
dataSource.validationQuery = "SELECT 1"
dataSource.testWhileIdle = true // 空闲时验证连接
dataSource.testOnBorrow = false // 获取连接时不验证
dataSource.testOnReturn = false // 返回连接时不验证
// 连接回收
dataSource.timeBetweenEvictionRunsMillis = 60000 // 连接回收间隔
dataSource.minEvictableIdleTimeMillis = 300000 // 连接最小空闲时间
// 监控配置
dataSource.filters = "stat,wall,log4j" // 启用监控和防火墙
dataSource
}2. 分库分表场景优化
- 多数据源管理: 使用动态数据源路由
- 连接池隔离: 为不同数据库配置独立的连接池
- 读写分离: 主库用于写操作,从库用于读操作
3. 高并发场景优化
- 连接池大小: 根据并发数和数据库性能调整
- 异步操作: 使用CompletableFuture处理并发请求
- 批量操作: 使用JDBC批处理减少连接次数
- 事务管理: 合理控制事务范围,避免长事务
4. 监控与告警
- 实时监控: 集成Prometheus + Grafana
- 告警配置: 当连接池使用率超过80%时告警
- 慢SQL监控: 配置slowSqlMillis,监控执行时间超过阈值的SQL
案例1: 连接泄漏导致系统崩溃
场景描述:
- 应用在高峰期出现响应缓慢,最终系统崩溃
- 监控发现activeCount持续增长,达到maxActive后不再下降
根因分析:
- 代码中使用了try-catch但未在finally中关闭连接
- 异常情况下连接未被释放,导致连接池耗尽
解决方案:
- 使用try-with-resources自动关闭连接
- 添加removeAbandoned配置,自动回收长时间未使用的连接
- 增加连接池监控和告警
优化效果:
- 系统稳定性显著提高
- 连接池使用率保持在合理范围
- 高峰期不再出现连接耗尽的情况
案例2: 慢SQL导致连接池阻塞
场景描述:
- 应用响应时间突然变长
- 监控发现大量线程等待获取连接
根因分析:
- 某个复杂查询执行时间过长,占用连接时间太久
- 导致其他请求无法获取连接,形成阻塞链
解决方案:
- 优化SQL语句,添加合适的索引
- 调整连接池配置,设置合理的maxWait
- 实现SQL超时机制,避免长时间占用连接
优化效果:
- SQL执行时间从5秒降至100ms以内
- 连接池阻塞问题解决
- 系统响应时间恢复正常
验证场景1: 连接池基本功能
# 获取连接池信息
http://localhost:8000/data/source/info
# 测试连接获取
http://localhost:8000/data/source/get/connection验证场景2: 并发性能测试
# 10个线程,每个线程执行100次连接获取
http://localhost:8000/data/source/test/concurrency?threadCount=10&iterations=100
# 预期结果: 成功率>99%,平均获取时间<10ms验证场景3: 连接池极限测试
# 尝试获取100个连接
http://localhost:8000/data/source/test/limit?connectionCount=100
# 预期结果: 当connectionCount>maxActive时,会出现获取失败验证场景4: 性能监控
# 执行50次连接获取测试并监控性能
http://localhost:8000/data/source/monitor?testCount=50
# 预期结果: 生成详细的性能报告,包括成功率、平均获取时间等验证场景5: 批量测试
# 批量执行100次连接获取测试
http://localhost:8000/data/source/batch/test?batchSize=100
# 预期结果: 生成批量测试报告,分析连接池性能趋势官方文档:
技术博客:
监控工具:
性能调优:
基础参数配置
# JDK 8
java -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation \
-XX:+PrintHeapAtGC -XX:NumberOfGCLogFiles=100 -XX:GCLogFileSize=10M \
-Xloggc:my-gc-%t.gc.log -jar -Xms64m -Xmx256m target/study-best-practice-0.0.1-SNAPSHOT.jar
# JDK 17+
java -jar -Xlog:gc:my-gc.log:time,level -Xms64m -Xmx256m target/study-best-practice-0.0.1-SNAPSHOT.jar推荐生产环境参数
java -Xms4g -Xmx4g -Xmn2g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=512m \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=8m \
-XX:G1ReservePercent=15 -XX:InitiatingHeapOccupancyPercent=45 \
-XX:ParallelGCThreads=8 -XX:ConcGCThreads=2 \
-XX:+DisableExplicitGC -XX:+AlwaysPreTouch \
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump \
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/path/to/gc.log \
-jar application.jar垃圾回收器选择
| 回收器 | 参数 | 适用场景 |
|---|---|---|
| 串行回收器 | -XX:+UseSerialGC |
单线程、小型应用 |
| 并行回收器 | -XX:+UseParallelGC |
吞吐量优先 |
| CMS回收器 | -XX:+UseConcMarkSweepGC |
低延迟优先 |
| G1回收器 | -XX:+UseG1GC |
平衡吞吐量和延迟 |
| ZGC | -XX:+UseZGC |
超大堆、低延迟 |
GC日志配置
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+UseGCLogFileRotation \
-XX:+PrintHeapAtGC -XX:NumberOfGCLogFiles=100 -XX:GCLogFileSize=10M \
-Xloggc:/opt/app/gc-%t.logGC日志分析工具
生成堆转储文件
jps
jmap -dump:file=heap.hprof,format=b {pid}分析堆转储文件
- 使用MAT (Memory Analyzer Tool)
- 使用VisualVM
大Key检测
redis-cli -a password --bigkeys大Key示例
- 大集合:
http://localhost:8000/redis/big/key/set?count=10000 - 大值:
http://localhost:8000/redis/big/value/set?count=10000
大Key影响
- 内存占用高
- 操作阻塞
- 网络传输慢
基本操作
# 初始化数据
http://localhost:8000/redis/random/init?max=10000
# 随机写入
http://localhost:8000/redis/random/set
# 随机读取
http://localhost:8000/redis/random/get性能测试API
# 单线程写入测试
http://localhost:8000/redis/performance/single/write?count=1000
# 多线程写入测试
http://localhost:8000/redis/performance/multi/write?count=10000&threads=10
# 单线程读取测试
http://localhost:8000/redis/performance/single/read?count=1000
# 多线程读取测试
http://localhost:8000/redis/performance/multi/read?count=10000&threads=10
# 混合读写测试
http://localhost:8000/redis/performance/mixed/read/write?count=10000&threads=10&readRatio=0.7Redis场景测试API
# 生成大key(大字符串)
http://localhost:8000/redis/performance/big/key/generate?size=1024
# 生成大List
http://localhost:8000/redis/performance/big/list/generate?size=10000
# 模拟热点key访问
http://localhost:8000/redis/performance/hot/key?count=10000&threads=10
# 测试大key性能
http://localhost:8000/redis/performance/big/key/test?size=512
# 清理测试数据
http://localhost:8000/redis/performance/cleanup监控工具
- Redis Insight: 可视化管理和监控Redis
- Prometheus + Grafana: 监控Redis指标
- Redis Exporter: 提供Redis监控指标
性能分析指南 详细的Redis性能分析过程请参考:Redis-MQ性能分析指南.md
// 随机发送MQ
http://localhost:8000/mq/send/random
// MQ 异步写入
http://localhost:8000/user/insert/async/mq
// 接受MQ消息
MQMessageReceiver性能测试API
# 单线程发送测试
http://localhost:8000/mq/performance/single/send?count=1000
# 多线程发送测试
http://localhost:8000/mq/performance/multi/send?count=10000&threads=10
# 发送并接收测试
http://localhost:8000/mq/performance/send/receive?count=100监控工具
- RabbitMQ Management UI:
http://localhost:15672 - Prometheus + Grafana: 监控RabbitMQ指标
- RabbitMQ Prometheus插件: 提供RabbitMQ监控指标
性能分析指南 详细的RabbitMQ性能分析过程请参考:Redis-MQ性能分析指南.md
MyBatis缓存分为两级:
| 缓存级别 | 作用域 | 默认状态 | 特点 |
|---|---|---|---|
| 一级缓存 | SqlSession级别 | 默认开启 | 同一个SqlSession内有效,执行更新操作会清空 |
| 二级缓存 | Mapper级别 | 默认关闭 | 多个SqlSession共享,需要手动开启 |
测试API
# 测试同一请求内的相同查询(使用缓存)
http://localhost:8000/mybatis/cache/level1/same?phone=13800138000
# 测试执行更新操作后(缓存清空)
http://localhost:8000/mybatis/cache/level1/after/update?phone=13800138000预期结果
- 第一次查询会发送SQL到数据库
- 第二次相同查询会使用一级缓存,不会发送SQL
- 执行更新操作后,缓存会被清空,后续查询会重新发送SQL
配置步骤
-
开启全局缓存(默认已开启)
<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在Mapper接口上开启二级缓存
@CacheNamespace(size = 512, flushInterval = 60000) public interface UserMapper extends BaseMapper<UserEntity> { }
-
实体类实现Serializable接口
public class UserEntity implements Serializable { private static final long serialVersionUID = 1L; // 字段... }
测试API
# 测试二级缓存
http://localhost:8000/mybatis/cache/level2?phone=13800138000预期结果
- 第一次请求会发送SQL到数据库
- 第二次请求(不同SqlSession)会使用二级缓存,不会发送SQL
- 执行更新操作后,对应Mapper的二级缓存会被清空
一级缓存注意事项
- 作用域限制:只在同一个SqlSession内有效,不同SqlSession之间不共享
- 自动清空:执行
insert、update、delete操作时会自动清空一级缓存 - 查询条件:缓存基于SQL语句和参数,即使是同一个方法,不同参数也会有不同缓存
- 性能影响:对于频繁重复查询的场景,一级缓存可以显著提升性能
二级缓存注意事项
- 配置复杂:需要手动开启,且实体类需要实现Serializable接口
- 内存占用:缓存会占用内存,需要合理设置缓存大小
- 一致性问题:多表关联查询时,可能出现缓存不一致的问题
- 序列化开销:对象需要序列化和反序列化,会有一定性能开销
- 适用场景:适合查询频率高、数据变更频率低的场景
常见问题与解决方案
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 缓存不一致 | 多表关联查询时,更新了一张表但另一张表的缓存未更新 | 谨慎使用二级缓存,或在相关Mapper上都开启缓存 |
| 内存溢出 | 缓存大小设置过大,或缓存了大对象 | 合理设置缓存大小,避免缓存大对象 |
| 性能下降 | 序列化开销过大,或缓存命中率低 | 只在合适的场景使用二级缓存,监控缓存命中率 |
| 数据过期 | 缓存未及时更新,导致读取到旧数据 | 设置合理的缓存刷新间隔,或使用缓存淘汰策略 |
-
优先使用一级缓存:一级缓存默认开启,无需额外配置,适用于大多数场景
-
二级缓存使用场景:
- 数据变更频率低的查询
- 对响应时间要求高的场景
- 系统内存充足
-
缓存配置建议:
// 合理的缓存配置 @CacheNamespace( size = 512, // 缓存大小 flushInterval = 60000, // 60秒自动刷新 readWrite = true, // 读写缓存 blocking = false // 非阻塞 )
-
监控与调优:
- 监控缓存命中率
- 根据业务场景调整缓存大小
- 定期清理过期缓存
application.properties配置
server.tomcat.threads.max=200
server.tomcat.threads.min-spare=20
server.tomcat.max-connections=10000
server.tomcat.accept-count=100查看线程状态
jstack {pid} > threads.txt分析线程阻塞
- 使用Arthas
- 使用jstack分析线程堆栈
排查步骤
- 使用
top命令查看CPU使用情况 - 使用
jstack查看线程堆栈 - 使用Arthas生成火焰图
- 定位耗时方法并优化
常见原因
- 死循环
- 复杂计算
- 线程阻塞
- GC频繁
排查步骤
- 使用
jmap -heap查看内存使用情况 - 生成堆转储文件
- 使用MAT分析堆转储
- 定位大对象和内存泄漏点
常见原因
- 静态集合持有对象
- 监听器未移除
- 线程局部变量未清理
- 连接未关闭
排查步骤
- 查看GC日志
- 分析GC原因和频率
- 调整JVM参数
- 优化代码减少对象创建
常见原因
- 内存分配不合理
- 对象创建过于频繁
- 大对象直接进入老年代
- 内存泄漏
排查步骤
- 开启慢查询日志
- 分析慢SQL
- 检查索引使用情况
- 优化SQL语句
常见原因
- 缺少索引
- SQL语句复杂
- 全表扫描
- 连接池配置不合理
场景
- 系统在高峰期出现Redis响应延迟
- 监控显示Redis CPU使用率高
排查过程
- 使用
redis-cli --bigkeys检测大Key - 发现多个包含10万+元素的集合
- 分析代码发现使用了
HGETALL操作大Hash
解决方案
- 拆分大Key为多个小Key
- 使用
HSCAN分批获取数据 - 增加Redis集群节点
优化效果
- Redis响应时间从50ms降至5ms
- CPU使用率从80%降至20%
场景
- 服务每几分钟出现一次短暂卡顿
- GC日志显示Young GC频繁
排查过程
- 分析GC日志
- 发现新生代空间过小
- 检查代码发现大量临时对象创建
解决方案
- 调整JVM参数,增大新生代空间
- 优化代码,减少临时对象创建
- 使用对象池复用对象
优化效果
- Young GC频率从每分钟10次降至每分钟2次
- 服务卡顿现象消失
场景
- 接口响应时间超过2秒
- 数据库CPU使用率高
排查过程
- 查看慢查询日志
- 发现一条SQL执行时间超过1秒
- 分析执行计划,发现全表扫描
解决方案
- 添加合适的索引
- 优化SQL语句,减少关联查询
- 增加数据库缓存
优化效果
- 接口响应时间从2秒降至200ms
- 数据库CPU使用率从70%降至30%
部署步骤
- 启动Prometheus
docker run --network bage-net -d --name bage-prometheus -p 9090:9090 \
-v /path/to/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus- 启动Grafana
docker run -d --name=bage-grafana -p 3000:3000 grafana/grafana- 配置数据源
- 访问:
http://localhost:3000 - 用户名/密码: admin/admin
- 添加Prometheus数据源:
http://bage-prometheus:9090
推荐仪表盘
- JVM监控: 4701-JVM Micrometer
- MySQL监控: 7362-MySQL
- Redis监控: Redis Dashboard
访问端点
- 健康检查:
http://localhost:8000/actuator/health - 指标:
http://localhost:8000/actuator/prometheus - 环境:
http://localhost:8000/actuator/env
测试API
# 完整验证类初始化顺序(父类+子类)
http://localhost:8000/class/init/test
# 单独验证父类初始化顺序
http://localhost:8000/class/init/parent预期执行顺序
- 父类静态变量初始化
- 父类静态代码块执行
- 子类静态变量初始化
- 子类静态代码块执行
- 父类实例变量初始化
- 父类实例代码块执行
- 父类构造函数执行
- 子类实例变量初始化
- 子类实例代码块执行
- 子类构造函数执行
验证说明
- 类的初始化过程只会执行一次,首次访问类时触发
- 静态代码块和静态变量的执行顺序与它们在代码中的定义顺序一致
- 实例代码块和实例变量的执行顺序与它们在代码中的定义顺序一致
- 构造函数会在实例代码块执行完成后执行
JVM参数根据来源和特性分为三类:
| 分类 | 前缀 | 示例 | 说明 |
|---|---|---|---|
| 标准参数 | 无 | -version、-cp |
所有JVM实现都必须支持的参数,向后兼容 |
| 非标准参数 | -X |
-Xms、-Xmx |
特定JVM实现支持的参数,可能在不同版本间变化 |
| 高级参数 | -XX: |
-XX:MaxGCPauseMillis、-XX:+UseG1GC |
用于JVM调优和调试的参数,变化频繁 |
常见内存配置参数:
| 参数 | 类型 | 含义 | 示例 |
|---|---|---|---|
-Xms |
非标准 | 初始堆内存大小 | -Xms512m(初始堆内存512MB) |
-Xmx |
非标准 | 最大堆内存大小 | -Xmx1g(最大堆内存1GB) |
-Xmn |
非标准 | 年轻代大小 | -Xmn256m(年轻代256MB) |
-Xss |
非标准 | 线程栈大小 | -Xss1m(每个线程栈1MB) |
常见高级参数:
| 参数 | 类型 | 含义 | 示例 |
|---|---|---|---|
-XX:SurvivorRatio |
数值型 | 伊甸区与幸存者区比例 | -XX:SurvivorRatio=8(伊甸区:幸存者区=8:1) |
-XX:MaxMetaspaceSize |
数值型 | 元空间最大大小 | -XX:MaxMetaspaceSize=256m |
-XX:MaxGCPauseMillis |
数值型 | 最大GC停顿时间目标 | -XX:MaxGCPauseMillis=100(目标100ms) |
-XX:ParallelGCThreads |
数值型 | 并行GC线程数 | -XX:ParallelGCThreads=4 |
-XX:+UseG1GC |
布尔型 | 启用G1垃圾收集器 | -XX:+UseG1GC |
-XX:+UseZGC |
布尔型 | 启用Z垃圾收集器 | -XX:+UseZGC |
-XX:CMSInitiatingOccupancyFraction |
数值型 | CMS触发阈值 | -XX:CMSInitiatingOccupancyFraction=75 |
-XX:InitiatingHeapOccupancyPercent |
数值型 | G1触发阈值 | -XX:InitiatingHeapOccupancyPercent=45 |
Serial收集器配置
-XX:+UseSerialGC -Xms512m -Xmx512m -Xmn256m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:serial-gc.logParallel收集器配置
-XX:+UseParallelGC -XX:+UseParallelOldGC -Xms1g -Xmx1g -Xmn512m -XX:SurvivorRatio=8 -XX:ParallelGCThreads=4 -XX:MaxGCPauseMillis=100 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:parallel-gc.logCMS收集器配置
-XX:+UseConcMarkSweepGC -XX:+UseParNewGC -Xms1g -Xmx1g -Xmn512m -XX:SurvivorRatio=8 -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+CMSParallelRemarkEnabled -XX:+CMSScavengeBeforeRemark -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:cms-gc.logG1收集器配置
-XX:+UseG1GC -Xms1g -Xmx1g -XX:MaxGCPauseMillis=200 -XX:ParallelGCThreads=4 -XX:ConcGCThreads=2 -XX:InitiatingHeapOccupancyPercent=45 -XX:G1HeapRegionSize=16m -XX:G1MaxNewSizePercent=60 -XX:G1ReservePercent=15 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -XX:+PrintAdaptiveSizePolicy -Xloggc:g1-gc.logZGC收集器配置 (JDK 11+)
-XX:+UseZGC -Xms1g -Xmx1g -XX:ZGCHeapSizeMax=1g -XX:ZGCHeapSizeMin=512m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:zgc-gc.log测试API
# 触发年轻代GC
http://localhost:8000/gc/young/gc
# 触发老年代GC
http://localhost:8000/gc/old/gc
# 测试内存分配策略
http://localhost:8000/gc/memory/allocation?size=100
# 测试G1 GC行为
http://localhost:8000/gc/g1/test?count=1000内存配置验证
| 参数 | 说明 | 验证方法 |
|---|---|---|
| Xms/Xmx | 初始/最大堆内存 | 启动时指定不同值,观察内存分配 |
| Xmn | 年轻代大小 | 调整大小,观察GC频率变化 |
| SurvivorRatio | 伊甸区与幸存者区比例 | 调整比例,观察对象晋升行为 |
| MaxMetaspaceSize | 元空间最大大小 | 调整大小,观察类加载行为 |
GC行为验证
| 参数 | 说明 | 验证方法 |
|---|---|---|
| MaxGCPauseMillis | 最大GC停顿时间 | 调整值,观察GC停顿时间变化 |
| ParallelGCThreads | 并行GC线程数 | 调整线程数,观察GC速度变化 |
| CMSInitiatingOccupancyFraction | CMS触发阈值 | 调整阈值,观察CMS启动时机 |
| InitiatingHeapOccupancyPercent | G1触发阈值 | 调整阈值,观察G1启动时机 |
| G1MaxNewSizePercent | G1年轻代最大比例 | 调整比例,观察年轻代大小变化 |
| G1HeapRegionSize | G1区域大小 | 调整大小,观察内存分配行为 |
-
GC日志分析
- 启动时添加
-XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log参数 - 使用GCViewer等工具分析GC日志
- 关注GC类型、GC时间、内存使用情况等指标
- 启动时添加
-
内存使用监控
- 使用JConsole、VisualVM等工具监控内存使用
- 观察年轻代、老年代内存变化
- 关注对象晋升情况和内存碎片
-
性能对比
- 在相同硬件环境下测试不同垃圾回收器
- 对比GC频率、GC停顿时间、吞吐量等指标
- 根据应用特点选择合适的垃圾回收器
-
调优建议
- 对于低延迟应用,优先考虑G1或ZGC
- 对于高吞吐量应用,优先考虑Parallel收集器
- 根据堆大小和硬件配置调整GC线程数
- 定期分析GC日志,持续优化GC配置
核心性能指标
| 指标 | 说明 | 理想值 |
|---|---|---|
| 吞吐量 | 非GC时间占总时间的比例 | >95% |
| GC停顿时间 | 单次GC的暂停时间 | <100ms(低延迟应用) |
| GC频率 | 单位时间内GC的次数 | 越少越好 |
| 内存使用率 | 堆内存的使用比例 | <70% |
| GC CPU占用 | GC过程中CPU的使用率 | <20% |
监控工具
-
JDK自带工具
- jstat:实时监控GC统计信息
- jconsole:图形化监控工具
- jvisualvm:可视化性能分析工具
-
第三方工具
- GCViewer:GC日志分析工具
- Prometheus + Grafana:监控告警系统
- Arthas:Java诊断工具
监控命令示例
# 使用jstat监控GC情况(每1秒输出一次,共10次)
jstat -gcutil <pid> 1000 10
# 使用jstat监控内存使用情况
jstat -gccapacity <pid>
# 查看GC详细信息
jstat -gc <pid>测试API
# 测试垃圾回收性能指标
http://localhost:8000/gc/performance/test?count=1000&interval=10
# 比较不同垃圾回收器的性能
http://localhost:8000/gc/performance/compare?count=500性能指标验证示例
// 初始化GC监控器
GCMonitor gcMonitor = new GCMonitor();
gcMonitor.start();
// 创建对象,触发GC
List<byte[]> objects = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
byte[] obj = new byte[(i % 10 + 1) * 1024 * 1024];
objects.add(obj);
if (i % 50 == 0) {
objects.clear();
System.gc();
}
Thread.sleep(10);
}
// 停止监控并获取结果
GCMonitor.GCMonitorResult result = gcMonitor.stop();
System.out.println("GC性能测试结果: " + result);场景1:大量小对象
- 测试方法:创建大量1MB大小的对象,每5个清空一次
- 预期结果:年轻代GC频繁,老年代GC较少
- 适合回收器:Parallel GC(吞吐量优先)
场景2:少量大对象
- 测试方法:创建少量10MB大小的对象,每2个清空一次
- 预期结果:可能直接进入老年代,触发老年代GC
- 适合回收器:G1 GC(大对象处理较好)
场景3:混合大小对象
- 测试方法:创建混合大小的对象,模拟真实应用场景
- 预期结果:年轻代和老年代GC都有发生
- 适合回收器:根据应用特点选择
注意事项
-
GC日志配置
- 生产环境中建议开启GC日志,但要注意日志文件大小
- 可使用
-XX:+UseGCLogFileRotation参数进行日志轮转
-
内存分配策略
- 避免频繁创建大对象,可能导致直接进入老年代
- 合理设置年轻代大小,减少对象晋升
-
并发处理
- 注意GC线程与应用线程的资源竞争
- 合理设置GC线程数,避免CPU过度使用
易错点
-
过度调优
- 不要盲目追求GC停顿时间,可能影响吞吐量
- 根据应用特点选择合适的垃圾回收器
-
参数冲突
- 注意不同GC参数之间的兼容性
- 例如,G1 GC的一些参数不适用于其他回收器
-
监控盲区
- 不要只关注GC指标,还要关注应用的整体性能
- 结合业务指标进行调优
-
版本差异
- 不同JDK版本的GC行为可能有所不同
- 升级JDK时要重新评估GC配置
调优建议
-
循序渐进
- 先使用默认配置,观察性能表现
- 再根据监控结果进行针对性调优
-
对比测试
- 在相同条件下测试不同配置
- 选择最适合应用的GC配置
-
持续监控
- 建立长期的GC监控机制
- 定期分析GC日志,及时发现问题
-
容量规划
- 根据应用的内存使用情况,合理规划堆大小
- 预留足够的内存空间,避免频繁GC
通过以上实践,可以全面了解垃圾回收的性能指标,选择合适的垃圾回收器和配置参数,从而提高应用的性能和稳定性。
测试API
# 完整验证类加载过程
http://localhost:8000/class/load/test
# 获取类加载信息
http://localhost:8000/class/load/info?className=com.bage.study.best.practice.trial.classload.TestClass
# 测试不同类加载器
http://localhost:8000/class/load/loader/test类加载的三个阶段
- 加载:将类的字节码加载到内存,生成Class对象
- 链接:验证、准备、解析
- 初始化:执行静态代码块和静态变量初始化
触发类初始化的场景
- 访问类的静态变量(非final常量)
- 调用类的静态方法
- 创建类的实例
- 初始化子类时,父类会先初始化
- 使用反射API访问类
类加载器层次
| 类加载器 | 加载范围 | 父加载器 |
|---|---|---|
| Bootstrap ClassLoader | JDK核心类库 | 无 |
| Extension ClassLoader | JDK扩展类库 | Bootstrap |
| App ClassLoader | 应用类路径 | Extension |
| Custom ClassLoader | 自定义路径 | App |
场景1:访问静态常量
- 访问
TestClass.STATIC_CONSTANT - 结果:不会触发类初始化,只会触发加载
场景2:访问静态变量
- 访问
TestClass.staticVar - 结果:触发类初始化,执行静态代码块和静态变量初始化
场景3:调用静态方法
- 调用
TestClass.staticMethod() - 结果:如果类未初始化,会先初始化
场景4:创建实例
- 创建
new TestClass() - 结果:如果类未初始化,会先初始化,然后执行实例代码块和构造函数
场景5:创建多个实例
- 创建多个
TestClass实例 - 结果:类初始化只执行一次,实例化会执行多次
-
查看日志输出
- 启动应用后,访问测试API
- 观察控制台日志,查看类加载、初始化、实例化的顺序
- 关注不同阶段的执行时间点
-
分析类加载信息
- 访问
/class/load/info接口 - 查看类的加载状态、初始化状态、加载器信息
- 查看实例化数量
- 访问
-
使用工具监控
- 使用JConsole、VisualVM等工具监控类加载
- 观察类加载数量和内存使用变化
- 分析类加载的性能影响
TestClass.java
public class TestClass {
// 静态常量
public static final String STATIC_CONSTANT = "static constant";
// 静态变量
public static String staticVar = initStaticVar();
// 静态代码块
static {
log.info("TestClass static block executed");
ClassLoadMonitor.recordClassInit(TestClass.class.getName());
}
// 实例变量
private String instanceVar = initInstanceVar();
// 实例代码块
{
log.info("TestClass instance block executed");
}
// 构造函数
public TestClass() {
log.info("TestClass constructor executed");
ClassLoadMonitor.recordInstanceCreation(TestClass.class.getName());
}
// 静态方法
public static void staticMethod() {
log.info("TestClass staticMethod executed");
}
// 实例方法
public void instanceMethod() {
log.info("TestClass instanceMethod executed");
}
private static String initStaticVar() {
log.info("TestClass initStaticVar executed");
return "initialized static var";
}
private String initInstanceVar() {
log.info("TestClass initInstanceVar executed");
return "initialized instance var";
}
}- Arthas: 阿里巴巴开源的Java诊断工具
- JConsole: JDK自带的监控工具
- VisualVM: 可视化JVM监控工具
- MAT: 内存分析工具
- GCViewer: GC日志分析工具
- WRK: 轻量级HTTP压测工具
- JMeter: 功能强大的压测工具
- Gatling: 高性能压测工具
打包
mvn clean package启动
java -jar target/study-best-practice-0.0.1-SNAPSHOT.jar拷贝文件
scp -r ./target/study-best-practice-0.0.1-SNAPSHOT.jar user@server:/path/to/deploy启动
java -jar study-best-practice-0.0.1-SNAPSHOT.jar \
--spring.config.location=file:///path/to/application.properties