一个接口耗时从50毫秒飙到3秒排查半天最后看到循环里那句result item。字符串拼接是Java性能陷阱里最擅长隐形的一个——它藏在语法糖底下每次循环都悄悄新建一个StringBuilder再做一次无用功。Java中String是不可变对象实际会创建新的String并复制旧字符串内容。循环10万次相当于做了10万次对象分配和数组复制。避坑指南很简单循环内永远使用StringBuilder尤其当拼接量超过几个时。如果必须用编译器会将其优化成StringBuilder来拼接常量但一旦进入循环这种优化就失效了。所以看到循环内的加号第一反应应该是重构成StringBuilder.append()。这只是一个开始真正的麻烦在于这样的陷阱往往藏在更深的地方。自动装箱隐形的对象分配器Java的泛型是陷阱的温床。ListInteger里每个元素都是包装类而list.add(1)在编译时会变成Integer.valueOf(1)int n list.get(0)会变成intValue()。自动装箱/拆箱是隐式进行的但每次转换都可能触发一次对象分配而对象分配意味着内存分配、构造方法和后续的GC。一个包含10万个整数的列表如果你用Integer循环累加额外产生的临时对象可能数以百万计。避坑指南能用基本类型就不要用包装类能定义专门的对象就避免泛型擦除带来的间接成本。在性能敏感的数值计算中使用int[]代替Integer[]即使代码略啰嗦但收益立竿见影。锁粒度越粗越慢多线程并发下过度同步是不折不扣的性能杀手。很多开发者在方法上直接加synchronized比如public synchronized void process()。这会让所有线程排队进入方法即使方法内部只有一小段需要保护的代码。锁的粒度决定了并发度锁越粗等待越久吞吐量越低。避坑指南优先使用ReentrantReadWriteLock或StampedLock处理读多写少的场景或者用ConcurrentHashMap、AtomicLong等并发容器和原子类取代全局锁。再退一步如果方法内部没有共享资源根本不需要加锁。synchronized不是邪恶的但无差别的synchronized一定是低效的。集合初始容量被低估的扩容成本很多人在使用ArrayList时从不在乎初始容量。当插入的元素超过默认容量10时ArrayList会进行扩容新建一个更大的数组把旧数组元素全部复制过去。如果你循环添加100万条数据会触发约17次扩容每次扩容都涉及数组复制和GC压力。频繁的扩容是将O(n)的插入操作放大成O(n^2)的元凶。避坑指南如果可以预估数据量创建集合时显式指定初始容量。比如new HashMap(1024)、new ArrayList(1000)不是多此一举而是给JVM一个明确的内存规划减少不必要的数组复制和哈希表重哈希。对HashMap尤其如此初始容量过小会导致频繁resize而resize会重新计算所有键的哈希代价高昂。IO流不关闭就是资源泄漏不关IO流的问题已经被说了一万遍但性能层面还有另一层含义。很多人用FileInputStream逐字节读取或者用BufferedReader但忘记关流。不关闭流等于把资源泄漏和性能灾难同时打开。文件读写是高频操作系统调用开销巨大如果没有缓冲每次read/write都要陷入内核态。避坑指南永远使用带缓冲的流如BufferedInputStream、BufferedReader并且用try-with-resources确保流在离开作用域时自动关闭。JDK7引入的try-with-resources不仅简洁还能避免finally块中重新抛出异常的错误。另外连接池、线程池也同样适用——不是关闭了就行而是要确保及时释放否则线程耗尽比内存泄漏更致命。正则模式回溯是CPU杀手正则表达式是文本处理中的利器但也是性能陷阱的常客。一个看起来没问题的模式(.)在遇到长字符串时可能引发灾难性的回溯CPU占用飙到100%接口直接假死。正则引擎的回溯算法在极端输入下会呈指数级增长。很多开发者每个循环里都调用Pattern.compile()这等于每次都要重新编译模式白白浪费CPU。避坑指南把Pattern静态编译成常量不要在循环里反复创建对复杂模式可以改用String.indexOf、contains等简单字符串方法先做粗筛。如果必须使用正则建议设置超时或将问题转化为有限自动机。别忘了正则的匹配复杂度与输入长度和模式嵌套深度都相关越简单的模式越安全。流式API优雅不等于高效Java 8的Stream API让代码优雅了但也隐藏着性能陷阱。很多人一听到并行流就兴奋直接在list.parallelStream().forEach(...)上操作。并行流不是银弹它的线程管理、任务切分和合并开销可能超过它带来的加速。对于小数据量或顺序敏感的操作并行流反而慢。避坑指南并行流只适合耗时长、数据量大且可以无状态处理的场景。对于普通过滤、映射优先使用传统for循环或顺序流因为JIT对循环的优化远好于对Lambda的优化。另外Stream的对象创建和迭代器开销也不可忽视在低延迟路径上简单for循环往往是最佳选择。编程美学不应该以吞吐量作为代价尤其在性能敏感的服务中。N1查询数据库访问的隐形炸弹数据库访问中N1查询是性能问题的常青树。当你查一个订单列表然后循环为每个订单去查用户信息看起来逻辑清晰但N次数据库往返让接口响应时间呈线性增长。N1查询的本质是把批量操作降级成了逐条操作每一次查询都消耗一次网络往返。避坑指南使用JOIN或批量IN查询一次获取所有关联数据或者在ORM框架中使用BatchSize、fetch join等。更关键的是在性能剖析时看一眼SQL日志中慢查询次数如果明显大于预期数据条数很可能就是N1在作祟。记住数据库连接和网络延迟是宝贵资源循环查询等于用最昂贵的方式完成一件批量任务。日志拼接在不知不觉中烧CPU日志系统是生产的眼睛但失控的日志会成为性能黑洞。很多人写日志时直接拼接字符串log.debug(user: user , action: action)。即使当前日志级别是INFOdebug方法没有被执行但字符串拼接依然在调用前完成了。日志级别判断是懒惰的而字符串拼接是贪婪的。避坑指南使用占位符log.debug(user: {}, action: {}, user, action)并确保日志框架的占位符在拼接前会先判断级别。更彻底的做法是在进入密集日志区前先检查logger.isDebugEnabled()。对于高吞吐系统把日志级别调至INFO或WARN并采用异步日志能显著减少锁竞争和I/O等待。日志不是越多越好每条日志都在消耗你的余寿。异常流控用最贵的机制干最细的活异常处理也是性能陷阱的重灾区。很多人喜欢用异常控制流程比如在解析数字时调用Integer.parseInt然后catchNumberFormatException来判断合法性。异常的创建会填充栈信息代价远高于普通的if判断在高频调用中这种开销会被放大百倍。在循环中反复抛出和捕获异常性能可能下降几个数量级。避坑指南用预判断代替异常比如先用正则或自定义校验函数过滤非法输入再调用parseInt。如果异常确实无法避免也要考虑通过Boolean返回值或null语义来替代。记住异常只应该在真正的异常场景中使用而不是作为业务逻辑的分支工具。JVM对异常的捕捉有优化但栈的填充和构造成本永远存在。看到底牌才能避开陷阱这些陷阱彼此独立又经常同时出现。一个慢接口背后往往是字符串拼接、自动装箱、无容量初始化和N1查询的组合拳。真正的性能高手是在写代码的每一行都知道这行代码在底层会变成什么。性能优化不是靠某种神秘技巧而是把这些常见陷阱刻进肌肉记忆。当你的代码出现循环、泛型、锁、IO、正则、日志、异常时停下来问自己这里是否存在无谓的对象创建、不必要的扩容、过大的锁粒度、不可控的回溯用JProfiler或Arthas定位热点用JMH验证微基准然后把优化沉淀为团队规范。下一次当你看到循环里的、方法上的synchronized、没有容量的ArrayList时你会想起这篇文章——并且知道真正的坑永远藏在最不起眼的代码里。