应优先使用StringBuilder而非String+=拼接,因其复用内部char[]、仅扩容时分配新数组;循环中构造一次、多次append、最后toString(),并预估容量避免频繁扩容。

OpenJDK 本身不提供“自动降低临时对象开销”的魔法开关,关键在代码层控制对象生成节奏 + JVM 层配合逃逸分析与GC策略。盲目池化或强行复用反而破坏 JIT 优化,得不偿失。
用对 StringBuilder 而不是 String +=
字符串拼接是最隐蔽的临时对象爆炸源。每次 String += 都隐式 new 一个新 String(底层是 new char[]),而 StringBuilder 复用内部 char[],只在扩容时才分配新数组。
- 循环内拼接必须用
StringBuilder:构造一次,.append()多次,最后.toString()仅一次生成最终字符串 - 别把
StringBuilder塞进对象池——它本就是设计来复用的,.setLength(0)比取还快 - 注意
StringBuilder不是线程安全的;若跨线程共享,改用ThreadLocal或直接用String.join()
慎用自动装箱和 Stream.boxed()
看似无害的 int 和 Integer 混用,会在字节码里悄悄插入 Integer.valueOf() 调用,超出 [-128, 127] 缓存范围就 new 对象。
- 避免
list.contains(i)(i是int)——会触发装箱;改用IntList(如 Eclipse Collections)或原始类型流 - 别写
stream.map(...).boxed().collect(...);能用IntStream/LongStream就别转成Stream - 日志占位符
log.info("id={}", id)比"id=" + id安全:未开启日志级别时,id根本不会被装箱
预估集合容量,避开扩容抖动
ArrayList、HashMap 默认初始容量小(10 / 16),扩容要复制旧数组、丢弃原对象,瞬间产生大量待回收对象。
- 明确知道大小时,显式传参:
new ArrayList(expectedSize)、new HashMap(expectedSize, 0.75f) - 批量添加前调用
ensureCapacity(),比边加边扩更省内存 - 方法返回只读集合?优先用
Collections.unmodifiableList()或ImmutableList.copyOf()(Guava),避免调用方意外修改导致防御性拷贝
对象池不是银弹,用错比不用更糟
绝大多数场景下,new SomeObject() 并不慢——G1/ZGC 对短生命周期对象回收极高效。对象池只在三个条件同时满足时才值得引入:构造耗时 >10μs、每秒创建 >1k 次、状态可安全重置。
- 别池化
String、Integer、StringBuilder:它们已被 JVM 特殊优化,池化干扰逃逸分析和 JIT 内联 - 用
Apache Commons Pool2,别手写;必须实现validateObject()(比如检查 Socket 是否断连)和destroyObject()(显式close()) - 监控不可少:
getNumActive()长期打满 = 池太小或没归还;getCreateCount()持续涨但getNumIdle()为 0 =returnObject()被漏调或异常吞掉
真正容易被忽略的是:JVM 的逃逸分析(Escape Analysis)能否生效,取决于你是否把对象限制在栈上作用域内。变量声明越窄越好,方法一结束就不可达的对象,连进老年代的机会都没有——这才是 OpenJDK 最擅长“免费”优化的部分。











