Java应用性能优化全攻略
2025.04.30
Java
10 min
3.8k 字
// 目录 · contents
前言 性能优化方法论 一、JVM 调优 1.1 内存区域与GC选择 1.2 生产环境推荐JVM参数 1.3 ZGC:超低延迟垃圾收集器 二、CPU Profiling 2.1 async-profiler 实战 2.2 在代码中嵌入性能监控 三、内存优化 3.1 常见内存泄漏模式 3.2 Heap Dump 分析 3.3 对象分配优化 四、线程 Dump 分析 4.1 获取Thread Dump 4.2 常见线程问题模式 五、常见性能反模式 六、数据库连接池调优 6.1 HikariCP 配置 6.2 连接池大小计算 七、缓存优化策略 多级缓存实现 性能优化检查清单 总结
前言
性能优化是Java开发者的必修课。一个生产系统的性能问题往往不是单一原因导致的,需要从JVM层、代码层、数据库层、架构层多维度系统化排查。本文将提供一套完整的性能优化方法论,从问题发现、分析定位到优化实施,覆盖Java应用性能优化的各个关键环节。
性能优化方法论
flowchart TB
A[发现性能问题] --> B[确定优化目标]
B --> C[性能基准测试]
C --> D{瓶颈在哪里?}
D -->|CPU高| E[CPU Profiling]
D -->|内存高| F[Heap Dump分析]
D -->|响应慢| G[链路追踪]
D -->|吞吐低| H[线程Dump分析]
E --> I[定位热点方法]
F --> J[定位内存泄漏]
G --> K[定位慢查询/慢接口]
H --> L[定位线程阻塞]
I --> M[实施优化]
J --> M
K --> M
L --> M
M --> N[回归测试]
N --> O{达到目标?}
O -->|是| P[持续监控]
O -->|否| D
style A fill:#f96,stroke:#333
style M fill:#9f6,stroke:#333
style P fill:#69f,stroke:#333
第一原则 :先量化,后优化。没有数据支撑的优化就是盲目猜测。
一、JVM 调优
1.1 内存区域与GC选择
graph TB
subgraph JVM内存布局
subgraph 堆 Heap
YOUNG[年轻代 Young Gen]
OLD[老年代 Old Gen]
YOUNG --> EDEN[Eden]
YOUNG --> S0[Survivor 0]
YOUNG --> S1[Survivor 1]
end
subgraph 非堆
META[元空间 Metaspace]
CCS[压缩类空间]
CC[代码缓存 CodeCache]
end
subgraph 线程私有
STACK[虚拟机栈]
PC[程序计数器]
end
subgraph 直接内存
DIRECT[DirectByteBuffer]
end
end
style YOUNG fill:#ffa,stroke:#333
style OLD fill:#aaf,stroke:#333
style META fill:#afa,stroke:#333
1.2 生产环境推荐JVM参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 java \ -Xms4g -Xmx4g \ -XX:MetaspaceSize=256m \ -XX:MaxMetaspaceSize=512m \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ -XX:G1HeapRegionSize=8m \ -XX:InitiatingHeapOccupancyPercent=45 \ -Xlog:gc*,gc+heap=debug,gc+phases=debug:file=/var/log/app/gc.log:time,uptime ,level,tags:filecount=10,filesize=100m \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/var/log/app/heapdump.hprof \ -XX:+UseStringDeduplication \ -XX:+AlwaysPreTouch \ -jar app.jar
1.3 ZGC:超低延迟垃圾收集器
1 2 3 4 5 6 7 java \ -Xms8g -Xmx8g \ -XX:+UseZGC \ -XX:+ZGenerational \ -XX:SoftMaxHeapSize=6g \ -jar app.jar
ZGC的GC停顿时间通常在1ms以内,与堆大小无关。适合大堆(几十GB到TB级)和低延迟要求的场景。
二、CPU Profiling
2.1 async-profiler 实战
async-profiler是生产环境CPU分析的首选工具,几乎零开销。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 wget https://github.com/async-profiler/async-profiler/releases/download/v3.0/async-profiler-3.0-linux-x64.tar.gz tar xzf async-profiler-3.0-linux-x64.tar.gz ./asprof -d 30 -f /tmp/cpu-flamegraph.html <PID> ./asprof -d 30 -e alloc -f /tmp/alloc-flamegraph.html <PID> ./asprof -d 30 -e lock -f /tmp/lock-flamegraph.html <PID> ./asprof -d 30 -e wall -t -f /tmp/wall-flamegraph.html <PID>
2.2 在代码中嵌入性能监控
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 @Aspect @Component public class PerformanceMonitorAspect { private static final Logger log = LoggerFactory.getLogger(PerformanceMonitorAspect.class); @Around("@within(org.springframework.stereotype.Service)") public Object monitorPerformance (ProceedingJoinPoint joinPoint) throws Throwable { String methodName = joinPoint.getSignature().toShortString(); long startNanos = System.nanoTime(); try { Object result = joinPoint.proceed(); long elapsedMs = (System.nanoTime() - startNanos) / 1_000_000 ; if (elapsedMs > 500 ) { log.warn("Slow method: {} took {}ms" , methodName, elapsedMs); } else if (elapsedMs > 100 ) { log.info("Method: {} took {}ms" , methodName, elapsedMs); } Metrics.timer("method.execution" , "method" , methodName) .record(elapsedMs, TimeUnit.MILLISECONDS); return result; } catch (Throwable e) { long elapsedMs = (System.nanoTime() - startNanos) / 1_000_000 ; log.error("Method {} failed after {}ms" , methodName, elapsedMs, e); Metrics.counter("method.error" , "method" , methodName).increment(); throw e; } } }
三、内存优化
3.1 常见内存泄漏模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 public class MemoryLeakPatterns { private static final Map<String, Object> cache = new HashMap <>(); private static final Map<String, Object> boundedCache = new LinkedHashMap <>(1000 , 0.75f , true ) { @Override protected boolean removeEldestEntry (Map.Entry<String, Object> eldest) { return size() > 1000 ; } }; private static final Cache<String, Object> caffeineCache = Caffeine.newBuilder() .maximumSize(10_000 ) .expireAfterWrite(Duration.ofMinutes(10 )) .recordStats() .build(); public String readFileBad (String path) throws IOException { BufferedReader reader = new BufferedReader (new FileReader (path)); return reader.readLine(); } public String readFileGood (String path) throws IOException { try (BufferedReader reader = new BufferedReader (new FileReader (path))) { return reader.readLine(); } } public class InnerTask implements Runnable { @Override public void run () { } } public static class StaticInnerTask implements Runnable { @Override public void run () { } } }
3.2 Heap Dump 分析
1 2 3 4 5 6 7 8 9 10 11 jmap -dump:live,format=b,file=heapdump.hprof <PID> jcmd <PID> GC.heap_dump /tmp/heapdump.hprof ./ParseHeapDump.sh /tmp/heapdump.hprof \ org.eclipse.mat.api:suspects \ org.eclipse.mat.api:overview \ org.eclipse.mat.api:top_components
3.3 对象分配优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 public class AllocationOptimization { public String buildCsvBad (List<String[]> rows) { String result = "" ; for (String[] row : rows) { result += String.join("," , row) + "\n" ; } return result; } public String buildCsvGood (List<String[]> rows) { StringBuilder sb = new StringBuilder (rows.size() * 64 ); for (String[] row : rows) { for (int i = 0 ; i < row.length; i++) { if (i > 0 ) sb.append(',' ); sb.append(row[i]); } sb.append('\n' ); } return sb.toString(); } public long sumBad (List<Integer> numbers) { Long sum = 0L ; for (Integer n : numbers) { sum += n; } return sum; } public long sumGood (List<Integer> numbers) { long sum = 0L ; for (int n : numbers) { sum += n; } return sum; } public long sumStream (List<Integer> numbers) { return numbers.stream().mapToLong(Integer::longValue).sum(); } }
四、线程 Dump 分析
4.1 获取Thread Dump
1 2 3 4 5 6 7 8 9 10 11 12 13 14 jstack -l <PID> > thread-dump.txt jcmd <PID> Thread.print > thread-dump.txtkill -3 <PID>for i in 1 2 3; do jcmd <PID> Thread.print > thread-dump-$i .txt sleep 5done
4.2 常见线程问题模式
graph TB
TD[Thread Dump分析] --> DL[死锁 Deadlock]
TD --> BT[阻塞 BLOCKED]
TD --> WT[等待 WAITING]
TD --> RN[运行 RUNNABLE]
DL --> DL1[两个线程互相等待对方持有的锁]
BT --> BT1[大量线程等待同一把锁]
WT --> WT1[线程池耗尽等待队列]
RN --> RN1[CPU密集型计算<br/>或自旋锁]
DL1 --> FIX1[调整加锁顺序<br/>使用tryLock超时]
BT1 --> FIX2[减小锁粒度<br/>使用并发集合]
WT1 --> FIX3[调整线程池大小<br/>增加队列容量]
RN1 --> FIX4[算法优化<br/>或异步处理]
style DL fill:#f66,stroke:#333
style BT fill:#fa6,stroke:#333
style WT fill:#ff6,stroke:#333
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 public class DeadlockDetector { public static void detectAndReport () { ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean(); long [] deadlockedThreads = threadMXBean.findDeadlockedThreads(); if (deadlockedThreads != null ) { ThreadInfo[] threadInfos = threadMXBean.getThreadInfo( deadlockedThreads, true , true ); StringBuilder sb = new StringBuilder ("DEADLOCK DETECTED!\n" ); for (ThreadInfo info : threadInfos) { sb.append(String.format( "Thread '%s' (id=%d) is waiting to lock %s held by '%s'%n" , info.getThreadName(), info.getThreadId(), info.getLockInfo(), info.getLockOwnerName() )); for (StackTraceElement ste : info.getStackTrace()) { sb.append("\tat " ).append(ste).append("\n" ); } } System.err.println(sb); } } }
五、常见性能反模式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 public class PerformanceAntiPatterns { public List<UserDTO> getUsersBad (List<Long> userIds) { return userIds.stream() .map(id -> userRepository.findById(id).orElse(null )) .filter(Objects::nonNull) .map(this ::toDTO) .toList(); } public List<UserDTO> getUsersGood (List<Long> userIds) { Map<Long, User> userMap = userRepository.findAllById(userIds).stream() .collect(Collectors.toMap(User::getId, Function.identity())); return userIds.stream() .map(userMap::get) .filter(Objects::nonNull) .map(this ::toDTO) .toList(); } public synchronized Map<String, Object> getStatsBad () { Map<String, Object> stats = new HashMap <>(); stats.put("userCount" , userRepository.count()); stats.put("orderCount" , orderRepository.count()); return stats; } private final ConcurrentHashMap<String, AtomicLong> counters = new ConcurrentHashMap <>(); public Map<String, Object> getStatsGood () { return Map.of( "userCount" , counters.getOrDefault("users" , new AtomicLong ()).get(), "orderCount" , counters.getOrDefault("orders" , new AtomicLong ()).get() ); } public boolean isEmailBad (String input) { return input.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$" ); } private static final Pattern EMAIL_PATTERN = Pattern.compile("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$" ); public boolean isEmailGood (String input) { return EMAIL_PATTERN.matcher(input).matches(); } public int parseIntBad (String s, int defaultValue) { try { return Integer.parseInt(s); } catch (NumberFormatException e) { return defaultValue; } } private static final Pattern INTEGER_PATTERN = Pattern.compile("-?\\d+" ); public int parseIntGood (String s, int defaultValue) { if (s == null || !INTEGER_PATTERN.matcher(s).matches()) { return defaultValue; } return Integer.parseInt(s); } }
六、数据库连接池调优
6.1 HikariCP 配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 spring: datasource: hikari: maximum-pool-size: 20 minimum-idle: 5 connection-timeout: 30000 idle-timeout: 600000 max-lifetime: 1800000 validation-timeout: 5000 leak-detection-threshold: 60000 pool-name: HikariPool-Main
6.2 连接池大小计算
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 public class ConnectionPoolSizing { @Component public static class HikariMetrics { @Autowired private DataSource dataSource; @Scheduled(fixedRate = 60_000) public void logPoolStats () { if (dataSource instanceof HikariDataSource hikari) { HikariPoolMXBean pool = hikari.getHikariPoolMXBean(); Logger log = LoggerFactory.getLogger(HikariMetrics.class); log.info("HikariPool Stats - Active: {}, Idle: {}, " + "Waiting: {}, Total: {}" , pool.getActiveConnections(), pool.getIdleConnections(), pool.getThreadsAwaitingConnection(), pool.getTotalConnections() ); if (pool.getThreadsAwaitingConnection() > 0 ) { log.warn("Threads waiting for connection: {}" , pool.getThreadsAwaitingConnection()); } } } } }
七、缓存优化策略
graph TB
REQ[请求] --> L1{L1: 本地缓存<br/>Caffeine}
L1 -->|命中| RES[返回]
L1 -->|未命中| L2{L2: 分布式缓存<br/>Redis}
L2 -->|命中| UPDATE_L1[更新L1] --> RES
L2 -->|未命中| DB[(数据库)]
DB --> UPDATE_L2[更新L2] --> UPDATE_L1_2[更新L1] --> RES
style L1 fill:#ffa,stroke:#333
style L2 fill:#aff,stroke:#333
style DB fill:#faf,stroke:#333
多级缓存实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 @Component public class MultiLevelCacheManager { private final Cache<String, Object> localCache = Caffeine.newBuilder() .maximumSize(10_000 ) .expireAfterWrite(Duration.ofMinutes(5 )) .build(); private final RedisTemplate<String, Object> redisTemplate; public MultiLevelCacheManager (RedisTemplate<String, Object> redisTemplate) { this .redisTemplate = redisTemplate; } @SuppressWarnings("unchecked") public <T> T get (String key, Class<T> type, Supplier<T> loader) { Object value = localCache.getIfPresent(key); if (value != null ) { return type.cast(value); } value = redisTemplate.opsForValue().get(key); if (value != null ) { localCache.put(key, value); return type.cast(value); } T result = loader.get(); if (result != null ) { redisTemplate.opsForValue().set(key, result, Duration.ofMinutes(30 )); localCache.put(key, result); } return result; } public void evict (String key) { localCache.invalidate(key); redisTemplate.delete(key); } }@Service public class ProductService { private final MultiLevelCacheManager cacheManager; private final ProductRepository productRepository; public Product getProduct (Long id) { return cacheManager.get( "product:" + id, Product.class, () -> productRepository.findById(id).orElse(null ) ); } }
性能优化检查清单
JVM
GC停顿时间是否可接受
GC日志、JFR
JVM
堆内存使用率是否合理
JVisualVM、Grafana
CPU
是否存在热点方法
async-profiler
内存
是否存在内存泄漏
Heap Dump + MAT
线程
是否存在死锁或大量阻塞
Thread Dump
数据库
是否存在慢SQL
慢查询日志
数据库
连接池配置是否合理
HikariCP指标
缓存
缓存命中率是否足够
Redis INFO、Caffeine stats
网络
是否存在N+1调用
链路追踪
代码
是否存在性能反模式
Code Review
总结
Java性能优化不是一次性工作,而是持续的过程。核心原则:
量化优先 :用数据说话,避免凭感觉优化。使用JFR、async-profiler等工具进行量化分析。
二八法则 :80%的性能问题由20%的代码引起,找到真正的热点才有优化价值。
不要过早优化 :先让代码正确和清晰,在有性能问题时再进行针对性优化。
全链路思考 :性能瓶颈可能在任何环节,需要从JVM、代码、数据库、网络、架构等多维度综合分析。
持续监控 :建立性能基线,通过监控系统实时观察性能指标,在问题恶化之前发现并解决。