文章目录
Pre
性能优化 - 案例篇:池化对象_Commons Pool 2.0通用对象池框架
性能优化 - 案例篇: 19 条常见的 Java 代码优化法则
引言:为何提前暴露指标与分析的重要性
在正式进行性能优化之前,必须先“看得到”系统运行状况:缓存命中率、数据库连接池使用情况、响应时长分布、CPU/内存消耗、垃圾回收停顿等。只有掌握真实数据,才能有针对性地优化;盲目调整往往事倍功半,甚至适得其反。
指标暴露与监控接入
Prometheus 集成
Maven 依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-core</artifactId> </dependency> <dependency> <groupId>io.micrometer</groupId> <artifactId>micrometer-registry-prometheus</artifactId> </dependency>
application.properties / application.yml 配置
management.endpoint.metrics.enabled=true management.endpoint.prometheus.enabled=true management.endpoints.web.exposure.include=health,info,prometheus,metrics management.metrics.export.prometheus.enabled=true management.endpoint.health.show-details=always # 可根据需要开放其他端点,如 httptrace、threaddump
启动与访问
启动后,访问
http://<host>:<port>/actuator/prometheus
可看到所有默认与自定义指标。配置 Prometheus server 抓取该 endpoint,例如在 prometheus.yml:
scrape_configs: - job_name: 'springboot-app' metrics_path: '/actuator/prometheus' static_configs: - targets: ['app-host:port']
自定义业务指标示例
@RestController public class TestController { private final MeterRegistry registry; public TestController(MeterRegistry registry) { this.registry = registry; } @GetMapping("/test") public String test() { registry.counter("app_test_invocations", "from", "127.0.0.1", "method", "test").increment(); return "ok"; } }
- 在 Prometheus 中可见指标如
app_test_invocations_total{from="127.0.0.1",method="test"} 5.0
- 在 Prometheus 中可见指标如
对于缓存命中率,可在缓存拦截或 CacheManager 事件中注册 Counter/Gauge,例如:
Cache<Object, Object> cache = ...; // 若使用 Caffeine,可 attach 监听 // 当命中时 registry.counter("cache_hit", "cache", "myCache").increment(); // 当未命中时 registry.counter("cache_miss", "cache", "myCache").increment();
Grafana 可视化与 AlertManager
在 Grafana 中配置 Prometheus 数据源,创建 Dashboard 展示:
- JVM 内存、GC 停顿、线程数
- HTTP 请求速率、延迟分布(可借助 Histogram/Summary)
- 缓存命中率:hit/(hit+miss)
- 数据库连接池使用率:活跃连接数 vs 最大连接数
AlertManager 配置告警规则,例如:
- 95% 延迟超过阈值
- GC 停顿时长过长
- 连接池耗尽告警
此部分可参考 Prometheus 与 Grafana 官方文档,自行搭建实验环境。
性能剖析工具:火焰图与 async-profiler
async-profiler 下载与使用
从 GitHub 下载 async-profiler release 包,解压至服务器目录,如
/opt/async-profiler
。启动 SpringBoot 应用时,添加 javaagent 参数:
java -agentpath:/opt/async-profiler/build/libasyncProfiler.so=start,svg,file=profile.svg -jar your-app.jar
运行一段业务场景(压测或真实流量),然后停止进程或通过 async-profiler 提供的 CLI 停止采样:
# 若不想重启,可 attach 模式: ./profiler.sh -d 30 -f profile.svg <pid>
查看生成的
profile.svg
,用浏览器打开:
- 横轴表示消耗的采样比例,宽度越大表示更耗时的方法。
- 纵向为调用栈层级。
- 从最宽处逐层向下分析,找到热点方法,结合源代码定位优化点。
结合 Flame 图优化示例
- 若热点在某个慢方法,可查看方法内部逻辑:是否存在不必要的循环、I/O 阻塞,或可并行优化。
- 若热点在序列化/反序列化,可考虑更高效的库或减小返回对象结构。
- 若大量时间在 GC,可结合 GC 日志分析堆配置是否合理。
HTTP 及 Web 层优化
CDN 与静态资源加速
- 将常用静态资源(JS、CSS、图片)托管于 CDN,减轻后端压力;用户访问时由地理就近节点提供。
- 对第三方库可使用公共 CDN;自有资源可上传到 CDN 或静态文件服务器。
Cache-Control/Expires 在 Nginx 中配置示例
location ~* \.(ico|gif|jpg|jpeg|png|css|js)$ {
add_header Cache-Control "max-age=31536000, immutable";
}
- 根据资源更新策略设置版本号或文件指纹,保证缓存命中又可及时更新。
减少域名数量
- 将静态资源和 API 接口合理合并域名;避免过多不同子域导致 DNS 查询延迟。
- 可启用 HTTP/2 时,多路复用可减少域名依赖,但 HTTP/2 支持需服务器和客户端皆启用。
Gzip 及资源压缩配置
gzip on;
gzip_min_length 1k;
gzip_buffers 4 16k;
gzip_comp_level 6;
gzip_http_version 1.1;
gzip_types text/plain application/javascript text/css application/json;
- 确保对动态 JSON 接口开启 gzip,减小响应体体积。
- 对大型静态文件可在构建时先行压缩(如 Brotli),结合 Nginx 支持。
Keep-Alive 配置
客户端与 Nginx:
http { keepalive_timeout 60s 60s; keepalive_requests 10000; }
Nginx 与 SpringBoot 后端长连接:
location / { proxy_pass http://backend; proxy_http_version 1.1; proxy_set_header Connection ""; }
SpringBoot/Tomcat 默认支持 Keep-Alive;可通过自定义 Connector 调整超时。
SpringBoot 容器调优
自定义嵌入式 Tomcat
如果项目并发量比较高,想要修改最大线程数、最大连接数等配置信息,可以通过自定义Web 容器的方式,代码如下所示。
@SpringBootApplication(proxyBeanMethods = false)
public class App implements WebServerFactoryCustomizer<ConfigurableServletWebServerFactory> {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
@Override
public void customize(ConfigurableServletWebServerFactory factory) {
if (factory instanceof TomcatServletWebServerFactory) {
TomcatServletWebServerFactory f = (TomcatServletWebServerFactory) factory;
f.setProtocol("org.apache.coyote.http11.Http11Nio2Protocol");
f.addConnectorCustomizers(c -> {
Http11NioProtocol protocol = (Http11NioProtocol) c.getProtocolHandler();
protocol.setMaxConnections(200);
protocol.setMaxThreads(200);
protocol.setConnectionTimeout(30000);
// protocol.setSelectorTimeout(3000); // NIO2 可选项
});
}
}
}
- 设置协议为 NIO2 可在高并发 I/O 场景下提升性能;需基准测试验证。
setMaxThreads
和setMaxConnections
值根据服务器硬件资源与业务并发调整,可在压测环境多次尝试并观察响应延迟、CPU 利用率、线程使用情况。
替换 Undertow
在
pom.xml
中排除 Tomcat 并引入 Undertow:<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> <exclusions> <exclusion> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-tomcat</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-undertow</artifactId> </dependency>
Undertow 线程模型和资源占用一般较轻;需要在业务场景中压测对比。
注意部分 Tomcat 特有配置不适用,需调整监控指标对应项。
JVM 参数回顾
结合性能优化 - 高级进阶:JVM 常见优化参数
,可启动时传入:-XX:+UseG1GC -Xms2048m -Xmx2048m -XX:+AlwaysPreTouch -XX:MaxMetaspaceSize=256m -XX:ReservedCodeCacheSize=240m -XX:MaxDirectMemorySize=512m -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dumps -XX:ErrorFile=/path/to/hs_err_pid%p.log -Xlog:gc*,gc+age=trace,safepoint:file=logs/gc.log:utctime,pid,tags:filecount=5,filesize=100m
根据硬件和应用特点调整堆大小;若GC停顿问题突出,可结合 async-profiler 和 GC 日志深入分析。
应用性能监控与分布式追踪
SkyWalking 集成
下载与部署
- 下载 SkyWalking Agent 和后端服务(按存储类型,如 Elasticsearch 存储版)。
- 部署 SkyWalking 后端:配置 Storage、UI、Collector。
启动应用时添加 Agent
java -javaagent:/opt/skywalking-agent/skywalking-agent.jar \ -Dskywalking.agent.service_name=your-service-name \ -Dskywalking.collector.backend_service=collector-host:11800 \ -jar your-app.jar
查看调用链与指标
- 访问 SkyWalking UI,可看到每次请求的调用链图、各段耗时、数据库/HTTP 调用详情、JVM 指标、GC 指标。
- 结合 Prometheus 等指标,综合判断:若某接口响应慢,可查看是哪一段(查询、序列化、远程调用等)成为瓶颈。
报警与自动化
- 可结合 SkyWalking 的告警和 Prometheus AlertManager 设置阈值告警。
- 定期分析热点接口和突发异常请求流,制定优化计划。
各层优化思路
Controller 层
- DTO 精简与分页:避免一次性返回过大结果集;对列表数据使用分页或流式方式(如 Spring MVC StreamingResponseBody)。
- JSON 序列化优化:选择高性能序列化库(Jackson 已较快,可开启 Afterburner 模块),避免将不必要字段序列化。
- 输入校验与限流:对异常或恶意请求提前拦截,减少无效处理。
- 幂等性与缓存:对可缓存接口,如 GET 查询,结合 HTTP 缓存头或后端缓存减少重复计算。
Service 层
无状态设计:Service Bean 默认单例无状态,避免在 Bean 中维护请求级状态。
合理拆分与职责分离:将复杂逻辑拆成小模块,便于监控与优化。
异步与并行:对于可并行处理的子任务,可使用 CompletableFuture、异步消息队列等;但要注意线程池配置与上下文传递。
缓存策略:
- 本地缓存(Caffeine):低延迟、减轻远程调用压力;监控命中率,避免缓存污染。
- 分布式缓存(Redis):适用于共享场景;注意 TTL 策略、防止缓存穿透(布隆过滤、请求预热)、防止雪崩(加随机过期)、防止击穿(互斥锁或预加载)。
避免重复计算与请求合并:对重复请求可合并或去重,减少下游压力。
分布式事务讨论:
- 两阶段提交、TCC、本地消息表、MQ事务消息等方案都会增加延迟与资源占用;仅在必要场景使用。
- 优先考虑补偿事务与柔性事务,实现最终一致性;通过幂等设计与补偿逻辑将风险和性能开销控制在可接受范围内。
如上图,分布式事务要在改造成本、性能、时效等方面进行综合考虑。有一个介于分布式事务和非事务之间的名词,叫作柔性事务。柔性事务的理念是将业务逻辑和互斥操作,从资源层上移至业务层面。
传统事务 vs 柔性事务
关于传统事务和柔性事务,我们来简单比较一下。
ACID
关系数据库, 最大的特点就是事务处理, 即满足 ACID。
原子性(Atomicity):事务中的操作要么都做,要么都不做。
一致性(Consistency):系统必须始终处在强一致状态下。
隔离性(Isolation):一个事务的执行不能被其他事务所干扰。
持久性(Durability):一个已提交的事务对数据库中数据的改变是永久性的。
BASE
BASE 方法通过牺牲一致性和孤立性来提高可用性和系统性能。
BASE 为 Basically Available、Soft-state、Eventually consistent 三者的缩写,其中 BASE 分别代表:
基本可用(Basically Available):系统能够基本运行、一直提供服务。
软状态(Soft-state):系统不要求一直保持强一致状态。
最终一致性(Eventual consistency):系统需要在某一时刻后达到一致性要求。
互联网业务,推荐使用补偿事务,完成最终一致性。比如,通过一系列的定时任务,完成对数据的修复。
DAO 层
ORM 使用注意:
- 懒加载原则:避免无意触发大量关联查询,合理使用 FetchType.LAZY,并在Service层通过显式查询或 DTO 投影避免 N+1。
- 批量操作:对于大量写场景,使用批量插入/更新。
SQL 优化与索引:分析慢查询日志、使用 EXPLAIN 确认索引命中,避免全表扫描。
分库分表注意:理解中间件实现原理,避免误以为简单 SQL 实现即高效;关注路由开销与合并成本。
数据库连接池:
- HikariCP 默认已非常高效,但仍需监控活跃连接数、等待时长;根据业务并发调整最大连接数,避免连接池耗尽或空闲过多浪费资源。
- 在 Prometheus 中可通过
HikariPool
指标收集连接使用情况,适时调整。
缓存优化
Caffeine:适合本地缓存,低延迟;需监控缓存大小、命中率、加载延迟;避免过大导致内存占用过高。
Redis:
- 连接池配置:监控和调整 Lettuce/Redisson 连接数;注意阻塞或过度并发导致连接耗尽。
- 数据序列化:选择高效序列化方式(如 JSON、Kryo、FST 等),兼顾可读性与性能。
- 缓存策略:参见上文防护策略;结合业务场景设计合理 TTL。
二级缓存:对数据库读密集应用,可考虑本地+分布式二级缓存架构;注意缓存一致性。
资源与线程管理
线程池配置:
- 对于异步任务、自定义 Executor,设置合适的 core/max pool size、queue size;监控线程活跃度、队列长度,防止任务堆积。
- 对于定时任务,避免过多定时线程争抢资源。
非阻塞与异步:
- 若业务适合,可使用 WebFlux 或 Reactor,但需慎重考虑团队熟悉度与实际场景;异步场景要注意线程切换开销和上下文传递。
- 对外 HTTP 调用可使用异步 HTTP 客户端(如 WebClient),避免阻塞线程。
端到端测试与压测
- 压测工具:wrk、JMeter、Locust 等
- 测前准备:确保监控、日志、profiling 准备完毕;在测试环境部署与生产相近的架构。
- 测试脚本设计:模拟真实业务流量,包括登录、查询、写入等混合场景。
- 结果分析:结合 Prometheus/Grafana 监控数据和火焰图,找出瓶颈;反复调优并回归测试,评估改动效果。
- 负载模型:考虑渐增负载测试、稳定性测试、持久压力测试,观察资源消耗与响应曲线。
小结
- 指标采集—剖析—优化—验证是闭环流程:始终保持对系统可观测性。
- 渐进式改动:在非生产环境验证,避免一次性大改带来风险;生产环境小步部署与监控回滚准备。
- CI/CD 集成:可在持续集成/部署流程中集成简单的健康检查和性能基准测试。
- 定期回顾:定时检查关键接口的性能指标,防止代码或依赖升级带来性能回退。
- 团队协作:文档化优化经验,分享给团队成员,形成共识和规范。