关于 performance-smell-detection
Performance Smell Detection 面向审查代码或排查已测得性能问题的 Java 开发者。它指出潜在的代码级瓶颈,并强调先测量、兼顾可读性,避免过早优化。
主要功能
- 01审查 Java 性能模式
- 02标记潜在代码级瓶颈
- 03涵盖字符串、流和集合
- 04分析装箱与正则开销
- 05强调优化前先分析测量
适用场景
- 01审查性能关键的 Java 路径
- 02排查已测得的 Java 性能下降
- 03了解 Java 性能取舍
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ decebals/ claude-code-java --skill performance-smell-detection安装后在 Claude Code 中输入/performance-smell-detection确认已加载。
附带文件README.md
详细介绍
性能代码异味检测技能
识别 Java 代码中潜在的代码级性能问题。
理念
“过早优化是万恶之源”——Donald Knuth
此技能旨在帮助你发现潜在的性能异味,而不是盲目地“修复”它们。现代 JVM(Java 21/25)已经高度优化。请始终:
- 先测量——使用 JMH、性能分析器或生产环境指标
- 关注热点路径——90% 的时间花在 10% 的代码上
- 考虑可读性——清晰的代码往往比微优化更重要
适用场景
- 审查对性能要求很高的代码路径
- 调查已测出的性能问题
- 学习 Java 性能模式
- 关注性能的代码审查
范围
此技能: 代码级性能(流、集合、对象)
数据库相关: 使用 jpa-patterns 技能(N+1、延迟加载、分页)
架构相关: 使用 architecture-review 技能
快速参考:潜在异味
| 异味 | 严重程度 | 上下文 |
|---|---|---|
| 在循环中编译正则表达式 | 🔴 高 | 通常值得修复 |
| 在循环中拼接字符串 | 🟡 中 | 在 Java 21/25 中仍然适用 |
| 在紧密循环中使用流 | 🟡 中 | 取决于集合大小 |
| 热点路径中的装箱 | 🟡 中 | 先测量 |
| 无界集合 | 🔴 高 | 内存风险 |
| 未设置集合容量 | 🟢 低 | 影响较小,仅在关键场景下测量 |
字符串操作(Java 9+ / 21 / 25)
变化内容
从 Java 9(JEP 280)开始,使用 + 拼接字符串时会使用 invokedynamic,而不是 StringBuilder。JVM 能很好地优化简单的字符串拼接。
Java 25 为 String 键的 Map 查找增加了 String::hashCode 常量折叠优化。
仍然适用:在循环中使用 StringBuilder
// 🔴 Still problematic - new String each iteration
String result = "";
for (String s : items) {
result += s; // O(n²) - creates n strings
}
// ✅ StringBuilder for loops
StringBuilder sb = new StringBuilder();
for (String s : items) {
sb.append(s);
}
String result = sb.toString();
// ✅ Or use String.join / Collectors.joining
String result = String.join("", items);
现在没问题:简单拼接
// ✅ Fine in Java 9+ - JVM optimizes this
String message = "User " + name + " logged in at " + timestamp;
// ✅ Also fine
return "Error: " + code + " - " + description;
在热点路径中避免:String.format
// 🟡 String.format has parsing overhead
log.debug(String.format("Processing %s with id %d", name, id));
// ✅ Parameterized logging (SLF4J)
log.debug("Processing {} with id {}", name, id);
Stream API(审慎看待)
实际情况
流有开销,但通常可以接受:
- 少于 100 个元素:流可能慢 2–5 倍(但耗时仍以微秒计)
- 1K–10K 个元素:差距会显著缩小
- 超过 10K 个元素:性能通常与循环相差不超过 50%
- GraalVM:可以将流优化到与循环性能相当
建议:优先使用流以提高可读性。只有在性能分析表明存在瓶颈时,才改用循环进行优化。
流可能造成问题的场景
// 🔴 Stream created per iteration in hot loop
for (int i = 0; i < 1_000_000; i++) {
boolean found = items.stream()
.anyMatch(item -> item.getId() == i);
}
// ✅ Pre-compute lookup structure
Set<Integer> itemIds = items.stream()
.map(Item::getId)
.collect(Collectors.toSet());
for (int i = 0; i < 1_000_000; i++) {
boolean found = itemIds.contains(i);
}
使用流没有问题的场景
// ✅ Single pass, readable, not in tight loop
List<String> names = users.stream()
.filter(User::isActive)
.map(User::getName)
.sorted()
.collect(Collectors.toList());
// ✅ Primitive streams avoid boxing
int sum = numbers.stream()
.mapToInt(Integer::intValue)
.sum();
并行流:谨慎使用
// 🔴 Parallel on small collection - overhead > benefit
smallList.parallelStream().map(...); // < 10K items
// 🔴 Parallel with shared mutable state
List<String> results = new ArrayList<>();
items.parallelStream()
.forEach(results::add); // Race condition!
// ✅ Parallel for CPU-intensive + large collections
List<Result> results = largeDataset.parallelStream() // > 10K items
.map(this::expensiveCpuComputation)
.collect(Collectors.toList());
装箱/拆箱
仍然是实际问题
装箱会在堆上创建对象,增加 GC 压力。JVM 会缓存小值(-128 到 127),但不会缓存更大的值。
未来:Project Valhalla 将大幅改善这一点。
// 🔴 Boxing in tight loop - creates millions of objects
Long sum = 0L;
for (int i = 0; i < 1_000_000; i++) {
sum += i; // Unbox, add, box
}
// ✅ Primitive
long sum = 0L;
for (int i = 0; i < 1_000_000; i++) {
sum += i;
}
使用基本类型流
// 🟡 Boxing overhead
int sum = list.stream()
.reduce(0, Integer::sum);
// ✅ Primitive stream
int sum = list.stream()
.mapToInt(Integer::intValue)
.sum();
正则表达式
始终在循环外预编译
这条建议并未过时——Pattern.compile 的开销很大。
// 🔴 Compiles pattern every iteration
for (String input : inputs) {
if (input.matches("\\d{3}-\\d{4}")) { // Compiles regex!
process(input);
}
}
// ✅ Pre-compile
private static final Pattern PHONE = Pattern.compile("\\d{3}-\\d{4}");
for (String input : inputs) {
if (PHONE.matcher(input).matches()) {
process(input);
}
}
集合
容量提示(轻微优化)
// 🟢 Low severity - but free optimization if size known
List<User> users = new ArrayList<>(expectedSize);
Map<String, User> map = new HashMap<>(expectedSize * 4 / 3 + 1);
根据用途选择合适的集合
// 🟡 O(n) lookup in loop
List<String> allowed = getAllowed();
for (Request r : requests) {
if (allowed.contains(r.getId())) { } // O(n) each time
}
// ✅ O(1) lookup
Set<String> allowed = new HashSet<>(getAllowed());
for (Request r : requests) {
if (allowed.contains(r.getId())) { } // O(1)
}
无界集合
// 🔴 Memory risk - could grow unbounded
@GetMapping("/users")
public List<User> getAllUsers() {
return userRepository.findAll(); // Millions of rows?
}
// ✅ Pagination
@GetMapping("/users")
public Page<User> getUsers(Pageable pageable) {
return userRepository.findAll(pageable);
}
现代 Java(21/25)模式
使用虚拟线程处理 I/O(Java 21+)
// 🟡 Traditional thread pool for I/O - wastes OS threads
ExecutorService executor = Executors.newFixedThreadPool(100);
for (Request request : requests) {
executor.submit(() -> callExternalApi(request)); // Blocks OS thread
}
// ✅ Virtual threads - millions of concurrent I/O operations
try (ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor()) {
for (Request request : requests) {
executor.submit(() -> callExternalApi(request));
}
}
结构化并发(Java 21+ 预览版)
// ✅ Structured concurrency for parallel I/O
try (StructuredTaskScope.ShutdownOnFailure scope = new StructuredTaskScope.ShutdownOnFailure()) {
Future<User> user = scope.fork(() -> fetchUser(id));
Future<Orders> orders = scope.fork(() -> fetchOrders(id));
scope.join();
scope.throwIfFailed();
return new UserProfile(user.resultNow(), orders.resultNow());
}
性能审查检查清单
🔴 高严重程度(通常值得修复)
- 在循环中调用 Regex Pattern.compile
- 未分页的无界查询
- 在循环中拼接字符串(StringBuilder 仍然适用)
- 使用共享可变状态的并行流
🟡 中严重程度(先测量)
- 在紧密循环中使用流(迭代次数 >100K)
- 热点路径中的装箱
- 在循环中调用 List.contains()(改用 Set)
- 使用传统线程处理 I/O(考虑使用虚拟线程)
🟢 低严重程度(有益但非必需)
- 设置集合初始容量
- 轻微优化流
- toArray(new T[0]) 与 toArray(new T[size]) 的选择
何时不应优化
- 不是热点路径 - 初始化代码、配置、管理端点
- 没有经过测量的问题 - “看起来很慢”并不算测量结果
- 可读性变差 - 清晰的代码优于微优化
- 集合较小 - 即使处理 100 个项目,也只需几微秒
分析命令
# Find regex in loops (potential compile overhead)
grep -rn "\.matches(\|\.split(" --include="*.java"
# Find potential boxing (Long/Integer as variables)
grep -rn "Long\s\|Integer\s\|Double\s" --include="*.java" | grep "= 0\|+="
# Find ArrayList without capacity
grep -rn "new ArrayList<>()" --include="*.java"
# Find findAll without pagination
grep -rn "findAll()" --include="*.java"
评论
支持 Markdown