本文目录导读:

- 字符串处理与集合误用(基础入门)
- 循环内频繁 IO/数据库查询(中层实战)
- 高并发下的锁竞争(JVM 并发)
- 频繁创建对象与 GC 压力(内存管理)
- 过多反射与动态代理(框架层优化)
- 数据库查询:N+1 问题(ORM 经典)
- 算法与数据结构选择不当(复杂计算)
- 序列化与网络通信(分布式系统)
- 如何设计“可优化的案例”用于实践?
- 总结建议
适合做性能优化的Java案例通常具备可复现、有优化空间、能直观对比效果的特点,以下从不同技术维度整理了几类经典案例,并附上核心思路,你可以根据自身技术栈选择实践。
字符串处理与集合误用(基础入门)
场景:处理大量文本日志,使用 拼接字符串,或使用 ArrayList 频繁插入头部。
代码示例:
// 反模式:循环内使用字符串拼接
String result = "";
for (int i = 0; i < 10000; i++) {
result += "data" + i; // 每次循环创建多个StringBuilder对象
}
// 优化后:显式使用 StringBuilder
StringBuilder sb = new StringBuilder(10000 * 10);
for (int i = 0; i < 10000; i++) {
sb.append("data").append(i);
}
优化点:避免频繁创建临时字符串;集合初始容量设置(如 HashMap 避免扩容rehash);使用 LinkedList 替代 ArrayList 做头部插入。
循环内频繁 IO/数据库查询(中层实战)
场景:Web 应用中,循环调用数据库查询或远程 API。 反模式:
for (Long orderId : orderIdList) {
Order order = orderDao.findById(orderId); // N次数据库IO
// 处理...
}
优化方案:
- 批处理:
SELECT * FROM orders WHERE id IN (:ids) - 缓存:使用
LoadingCache(Caffeine/Guava)缓存热点数据,避免重复查询 - 异步化:对非关键路径使用
CompletableFuture(如发送通知)
高并发下的锁竞争(JVM 并发)
场景:秒杀系统中使用 synchronized 锁住整个方法。
反模式:
public synchronized boolean reduceStock(Long productId) { // 锁粒度过粗,所有商品共用一个锁
// 检查库存、扣减...
}
优化方案:
- 分段锁:
ConcurrentHashMap实现(按商品ID分桶),或使用ReentrantLock细化锁粒度 - 乐观锁:数据库
UPDATE ... SET stock = stock - 1 WHERE stock > 0 - 无锁化:
AtomicLong/CAS或LongAdder(更适合高并发计数)
频繁创建对象与 GC 压力(内存管理)
场景:实时风控系统,每秒处理数万笔交易,每笔交易创建大量临时对象。
反模式:每次处理都 new HashMap<>()、new StringBuilder()。
优化方案:
- 对象池:使用
ThreadLocal复用对象(注意内存泄漏),或 Apache Commons Pool - 逃逸分析:确保小对象在栈上分配(JVM默认开启,但需避免对象“逃逸”到方法外)
- 零拷贝:处理文件时使用
FileChannel.transferTo(),避免堆内外内存拷贝
过多反射与动态代理(框架层优化)
场景:JSON 序列化/反序列化框架(如 Jackson)或 ORM 框架中大量使用反射获取字段值。 反模式:
// 每次调用都 getDeclaredField() 并 setAccessible(true)
优化方案:
- 缓存元数据:使用
ClassValue或ConcurrentHashMap缓存Field/Method实例 - 方法句柄:
MethodHandles.Lookup比反射快数倍 - 字节码增强:运行时生成访问类字段的直接字节码(如 ASM、ByteBuddy),Spring 的 Bean 属性复制也采用此思路
数据库查询:N+1 问题(ORM 经典)
场景:JPA/Hibernate 查询一篇文章及其作者。 反模式:
// 查询100篇文章 -> 触发100次查询作者
List<Post> posts = postRepository.findAll();
for (Post p : posts) {
System.out.println(p.getAuthor().getName());
}
优化方案:
- JOIN FETCH:
SELECT p FROM Post p JOIN FETCH p.author - @BatchSize:批量加载关联实体
- DTO 投影:只查询需要的字段(
SELECT p.title, a.name FROM Post p JOIN Author a ...)
算法与数据结构选择不当(复杂计算)
场景:实时排名系统需要高频插入和查询前100名。
反模式:使用 Collections.sort(list) 每次全排序(O(n log n))。
优化方案:
- 堆/优先队列:
PriorityQueue(固定大小,只保留前100名) - TreeMap/TreeSet:实现自定义比较器,获取前N个元素为 O(log n)
- ForkJoinPool:并行化处理大数据集(如大型数据去重)
序列化与网络通信(分布式系统)
场景:微服务间 RPC 传输大对象。 优化点:
- 协议选择:Protobuf/FlatBuffers 比 Java 原生序列化快10-100倍
- 紧凑字段:使用
int替代long,short替代int等 - 压缩:
Snappy或GZIPOutputStream压缩传输数据
如何设计“可优化的案例”用于实践?
- 明确基准数据:准备 10 万条数据,记录优化前后耗时/CPU/内存(使用 JMH 或简单
System.nanoTime())。 - 制造瓶颈:主动添加
Thread.sleep(1ms)模拟 IO,或设置 -Xmx10m 模拟内存不足。 - 工具配合:先用 VisualVM 或 Arthas 定位问题,再动手优化,最后用 JMeter 压力测试验证。
总结建议
- 入门:从字符串拼接、集合选择、循环内 DB 查询入手(立竿见影)。
- 进阶:深入 GC 日志分析、锁优化、字节码增强。
- 高阶:设计一个“故意写差”的秒杀系统(木马式漏洞代码),然后逐步用方案优化,对比效果。
这些案例的核心价值不仅在于“快速”,更在于理解JVM底层机制(类加载、内存模型、编译优化),从而写出更可靠的代码。