Java · #performance#optimization#java#profiling

Java应用性能优化全攻略

2025.04.30 Java 10 min 3.8k
// 目录 · contents

前言

性能优化是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
# JDK 17+ 推荐使用G1GC(默认)或ZGC
java \
# 基础内存设置
-Xms4g -Xmx4g \ # 堆初始和最大值设为相同,避免动态扩容
-XX:MetaspaceSize=256m \ # 元空间初始大小
-XX:MaxMetaspaceSize=512m \ # 元空间最大值

# G1 GC 调优
-XX:+UseG1GC \ # 使用G1垃圾收集器
-XX:MaxGCPauseMillis=200 \ # 目标最大GC停顿时间
-XX:G1HeapRegionSize=8m \ # Region大小,建议设为堆的1/2048~1/1
-XX:InitiatingHeapOccupancyPercent=45 \ # 触发并发标记的堆占用率

# GC日志(JDK 9+格式)
-Xlog:gc*,gc+heap=debug,gc+phases=debug:file=/var/log/app/gc.log:time,uptime,level,tags:filecount=10,filesize=100m \

# OOM时自动生成堆转储
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/app/heapdump.hprof \

# 其他优化
-XX:+UseStringDeduplication \ # G1下开启字符串去重
-XX:+AlwaysPreTouch \ # 启动时预分配内存页
-jar app.jar

1.3 ZGC:超低延迟垃圾收集器

1
2
3
4
5
6
7
# ZGC配置(JDK 17+,适用于对延迟敏感的应用)
java \
-Xms8g -Xmx8g \
-XX:+UseZGC \
-XX:+ZGenerational \ # JDK 21+ 分代ZGC,推荐开启
-XX:SoftMaxHeapSize=6g \ # 软上限,ZGC尽量不超过此值
-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

# CPU热点分析,采样30秒,生成火焰图
./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>

# Wall-clock分析(包含I/O等待时间)
./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);

// 监控所有Service层方法
@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 {

// 案例1:静态集合无限增长
// BAD: 缓存没有淘汰策略
private static final Map<String, Object> cache = new HashMap<>();

// GOOD: 使用带容量限制的缓存
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;
}
};

// 更好的选择:使用Caffeine
private static final Cache<String, Object> caffeineCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(10))
.recordStats()
.build();

// 案例2:未关闭的资源
// BAD:
public String readFileBad(String path) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(path));
return reader.readLine(); // reader永远不会关闭
}

// GOOD: try-with-resources
public String readFileGood(String path) throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(path))) {
return reader.readLine();
}
}

// 案例3:内部类持有外部类引用
// BAD: 非静态内部类持有外部类引用
public class InnerTask implements Runnable {
@Override
public void run() { /* ... */ }
}

// GOOD: 静态内部类不持有外部类引用
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
# 手动生成Heap Dump
jmap -dump:live,format=b,file=heapdump.hprof <PID>

# 或使用jcmd(推荐)
jcmd <PID> GC.heap_dump /tmp/heapdump.hprof

# 使用Eclipse MAT命令行分析
./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 {

// BAD: 循环内创建大量临时对象
public String buildCsvBad(List<String[]> rows) {
String result = "";
for (String[] row : rows) {
result += String.join(",", row) + "\n"; // 每次拼接创建新String
}
return result;
}

// GOOD: 使用StringBuilder预分配容量
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();
}

// BAD: 自动装箱导致大量Integer对象
public long sumBad(List<Integer> numbers) {
Long sum = 0L;
for (Integer n : numbers) {
sum += n; // 每次相加都触发拆箱和装箱
}
return sum;
}

// GOOD: 使用基本类型
public long sumGood(List<Integer> numbers) {
long sum = 0L;
for (int n : numbers) { // 自动拆箱一次
sum += n;
}
return sum;
}

// GOOD: 使用Stream的基本类型特化
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
# 方式1:jstack
jstack -l <PID> > thread-dump.txt

# 方式2:jcmd(推荐)
jcmd <PID> Thread.print > thread-dump.txt

# 方式3:kill信号(不会终止进程)
kill -3 <PID>

# 建议连续抓取3次,间隔5秒,对比分析
for i in 1 2 3; do
jcmd <PID> Thread.print > thread-dump-$i.txt
sleep 5
done

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 {

// 反模式1:N+1查询
// BAD: 循环中逐个查询
public List<UserDTO> getUsersBad(List<Long> userIds) {
return userIds.stream()
.map(id -> userRepository.findById(id).orElse(null))
.filter(Objects::nonNull)
.map(this::toDTO)
.toList();
}

// GOOD: 批量查询
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();
}

// 反模式2:过度同步
// BAD: 整个方法同步
public synchronized Map<String, Object> getStatsBad() {
Map<String, Object> stats = new HashMap<>();
stats.put("userCount", userRepository.count()); // 数据库查询
stats.put("orderCount", orderRepository.count()); // 数据库查询
return stats;
}

// GOOD: 缩小同步范围或使用并发集合
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()
);
}

// 反模式3:正则表达式编译开销
// BAD: 每次调用都编译正则
public boolean isEmailBad(String input) {
return input.matches("^[\\w.-]+@[\\w.-]+\\.[a-zA-Z]{2,}$");
}

// GOOD: 预编译正则
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();
}

// 反模式4:异常控制流程
// BAD: 用异常代替条件判断
public int parseIntBad(String s, int defaultValue) {
try {
return Integer.parseInt(s);
} catch (NumberFormatException e) {
return defaultValue; // 异常创建堆栈开销大
}
}

// GOOD: 先校验再解析
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 # 获取连接超时(ms)
idle-timeout: 600000 # 空闲连接超时(ms)
max-lifetime: 1800000 # 连接最大生命周期(ms)
validation-timeout: 5000 # 连接验证超时(ms)

# 推荐设置
leak-detection-threshold: 60000 # 连接泄漏检测阈值(ms)
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
/**
* HikariCP官方推荐公式:
* connections = ((core_count * 2) + effective_spindle_count)
*
* 对于SSD: effective_spindle_count = 1
* 4核CPU: connections = (4 * 2) + 1 = 9
*
* 实际上,对于大多数OLTP应用,10-20个连接就足够了
* 更大的连接池反而会导致数据库竞争加剧
*/
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()
);

// 告警:如果等待连接的线程数持续大于0
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 {

// L1: 本地缓存
private final Cache<String, Object> localCache = Caffeine.newBuilder()
.maximumSize(10_000)
.expireAfterWrite(Duration.ofMinutes(5))
.build();

// L2: Redis
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) {
// L1: 本地缓存查询
Object value = localCache.getIfPresent(key);
if (value != null) {
return type.cast(value);
}

// L2: Redis查询
value = redisTemplate.opsForValue().get(key);
if (value != null) {
localCache.put(key, value); // 回填L1
return type.cast(value);
}

// L3: 数据源查询
T result = loader.get();
if (result != null) {
redisTemplate.opsForValue().set(key, result,
Duration.ofMinutes(30)); // 写入L2
localCache.put(key, result); // 写入L1
}

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性能优化不是一次性工作,而是持续的过程。核心原则:

  1. 量化优先:用数据说话,避免凭感觉优化。使用JFR、async-profiler等工具进行量化分析。
  2. 二八法则:80%的性能问题由20%的代码引起,找到真正的热点才有优化价值。
  3. 不要过早优化:先让代码正确和清晰,在有性能问题时再进行针对性优化。
  4. 全链路思考:性能瓶颈可能在任何环节,需要从JVM、代码、数据库、网络、架构等多维度综合分析。
  5. 持续监控:建立性能基线,通过监控系统实时观察性能指标,在问题恶化之前发现并解决。
作者 · authorzt
发布 · date2025-04-30
篇幅 · length3.8k 字 · 10 min
许可 · licenseCC BY-SA 4.0
$ echo "comments" · 评论