一个深夜,监控屏幕上的CPU曲线突然像被激怒的刺猬一样竖起尖刺。你的Java服务开始响应迟缓,用户投诉如潮水般涌来。你翻开代码库,开始寻找罪魁祸首——而真相往往藏在最平凡的几行代码里。性能优化没有那么神秘,它是一门从细节中榨取效率的艺术。以下10个技巧,能帮你从“勉强能用”迈向“游刃有余”。

字符串拼接:最温柔的慢性毒药

很多新手喜欢用 + 号连接字符串,尤其是在循环里。如果循环次数不多,差异微乎其微;一旦循环达到数千次甚至更高,每次 + 都会在堆里产生新的String对象,引发频繁的GC。你写的不是代码,是给垃圾回收器派发的加班通知单。

正确的做法是用 StringBuilder(非线程安全但更快)或 StringBuffer(线程安全)。即便在单线程中,StringBuilder也比直接拼接快出一个数量级。更可怕的是,很多框架内部也在大量拼接SQL、JSON、日志,如果不统一使用 StringBuilder,整个链路的性能都会被拖垮。一个简单的习惯:凡是循环内的字符串组装,一律掏出StringBuilder。

缓存:用空间换时间的交易永远值得

业务代码中最常见的性能黑洞,就是反复计算或查询那些几乎不变的数据。拿用户权限来说,每次请求都从数据库拉取,等于让工程车去运送一枚别针。

本地缓存是性价比最高的优化武器——CaffeineGuava Cache或简单的 ConcurrentHashMap 加过期策略,就能把响应时间从几十毫秒降到微秒级。不过要注意缓存的穿透、雪崩和击穿。别做缓存搬运工,而是给每个缓存项设置合理的TTL,并使用锁或异步加载来避免同时请求数据库。分布式场景则要考虑Redis或Memcached,但本地缓存仍然是最快的那一层。

对象创建:别让JVM替你数钞票

在Java中,对象的创建和销毁都是有代价的。尤其是在高并发路径上,频繁 new 对象会导致Young GC频繁触发,进而影响整个应用的吞吐量。一个对象的出生,背后是内存分配、构造函数执行、可能的逃逸分析失败,最后还得被回收。 每一个环节都在消耗你的CPU周期。

常见的优化手段有:使用对象池(比如Netty的ByteBuf池)、复用变量(如 SimpleDateFormat 的线程局部变量)、以及避免在循环内创建不必要的临时对象。比如 for(int i=0; i<list.size(); i++) 每次调用 list.size() 会生成一次方法调用,虽然很小,但积少成多。更极端的案例是日志框架的字符串模板拼接——请始终使用 logger.debug("value: {}", obj) 的占位符方式,而非 logger.debug("value: " + obj),这样在日志级别未开启时能避免字符串对象的创建。

并行流不是银弹,小心你的线程池

Java 8引入了并行流,让开发者可以轻松利用多核。但很多人把它当成了万能加速器。list.parallelStream().forEach(...) 写起来爽,跑起来却可能让系统崩溃——因为并行流默认使用 ForkJoinPool.commonPool,这个池子的大小等于 Runtime.getRuntime().availableProcessors() - 1

如果多个并行流任务同时执行,它们会共享同一个线程池,导致CPU争抢和上下文切换暴增。更危险的是,在并行流内部使用阻塞操作(如远程调用、写文件),会直接锁死整个公共线程池。 优化的原则是:明确执行任务的负载类型——CPU密集型的并行流才有明显提升;IO密集型的必须自定义线程池,并为每个任务隔离资源。不要因为写着顺手,就让系统为你买单。

数据库访问:每一轮查询都是一次昂贵的谈判

数据库通常是整个系统的瓶颈。很多人只关注SQL索引,却忽略了代码层面的交互方式。打开一个连接,执行一条SQL,关闭连接——这个三角恋式的操作,每一次都在付出网络、认证、事务开销。 优化方向可以从三个层面切入:

第一,使用连接池(如HikariCP)复用连接,把连接创建开销降到最低。第二,批量操作优于单条循环,用 addBatch 代替 for 循环里的 executeUpdate,一次网络往返搞定1000条数据。第三,利用缓存层减少数据库命中。更进阶的是:延迟加载与预加载的平衡艺术。如果业务确定会用到关联数据,就用JOIN一次性查出;如果大多数情况不需要,就用懒加载避免浪费。没有银弹,只有对业务特征的深刻理解。

JVM参数:别让默认配置拖后腿

很多开发者在部署Java应用时,直接用了JVM的出厂设置。这就像买来一辆赛车却挂着二档上路。堆大小、GC算法、元空间限制,这些参数是性能的阀门。 例如,默认的 -Xms-Xmx 往往偏小,导致应用刚启动时频繁触发Full GC。你可以设置 -Xms 等于 -Xmx,避免动态调整带来的额外计算。

另一个常见误区是GC选择。对于响应时间敏感的服务,G1 GC虽好,但并非万能,当堆内存小于4GB时,Parallel GC可能更高效。记住:没有最好的GC,只有最适合当前场景的GC。 通过 jstatGCeasy 等工具观察GC日志,调整新生代与老年代的比例,往往能获得10%以上的吞吐量提升。不调JVM参数就谈性能优化,无异于纸上谈兵。

集合类选型:HashMap的容量玄学

见过无数人在构造函数里这么写:new HashMap<>()。当你知道要放几千个元素时,默认初始容量16会导致多次rehash。每一次rehash都会重新计算所有元素的桶位置,并移动对象,这比新建一个集合还要慢。 你需要做的是预估初始容量:initialCapacity = (需要存储的数量 / 负载因子) + 1,负载因子默认0.75。比如要放1000个,就设为 1000 / 0.75 + 1 = 1334,直接 new HashMap<>(1334)

ArrayList和LinkedList的选择同样关键:随机访问多就用ArrayList,首尾插入删除多才考虑LinkedList。LinkedList的get(index)是O(n),你用for循环遍历它,等于亲手制造性能事故。 另外,线程不安全的集合如果被多线程使用,除了加锁,还可以考虑 ConcurrentHashMapCopyOnWriteArrayList 等专用容器,它们通过分段锁或写时复制,将并发度提升了几个级别。

减少锁竞争:从悲观到乐观的进化

锁是并发编程中性能的杀手。当一个线程持有锁时,其他线程只能等待,这就是“阻塞”。你的代码里每一个synchronized块,都是一个潜在的交通岗亭。 优化的核心是:缩小锁的范围、降低锁的粒度、甚至使用无锁结构。

例如,使用 ReentrantReadWriteLock:读操作不互斥,写操作才独占。如果读多写少,读到性能几乎等同于无锁。更高阶的玩法是使用CAS(比较并交换),比如 AtomicInteger,它在硬件层面进行无锁更新,比synchronized快几个数量级。如果数据量小且冲突极少,甚至可以考虑 ThreadLocal,每个线程保留自己的副本,彻底消除竞争。无锁不是魔法,但它能让你从“排队等厕所”变成“每人一个马桶”。

I/O优化:让数据传输飞起来

传统的Java I/O是阻塞模式,一个线程对应一个连接,连接多了线程数暴涨,CPU全花在线程切换上。这就是BIO(阻塞IO)的短板——成千上万的线程在等待,却没有几个真正在干活。 解决方案是引入NIO(非阻塞IO)或AIO(异步IO),结合Selector模型,用一个线程监控多个通道的事件。

实际项目中,更常用的是Netty、Undertow这类框架,它们封装了NIO的复杂性。但即使不升级框架,你也能做出简单优化:使用缓冲流(BufferedInputStream/OutputStream)代替原始流,减少系统调用次数。 比如读取文件时,每次read()默认只读一个字节,系统调用开销巨大。加上8KB的缓冲区,吞吐量能提升百倍。同样,写日志时记得开启自动flush?不不不——异步日志才是王道。 使用Log4j2的AsyncAppender,把I/O操作交给后台线程,业务线程瞬间返回。

懒加载与预加载:时机就是一切

有些对象创建很昂贵,但又不是每次请求都需要。这时可以选择懒加载:只有在第一次使用时才初始化,Double-Check Locking(双重检查锁定)模式需要搭配 volatile 关键字,防止指令重排序。懒加载的思路是:把账记在将来,但务必保证第一次使用的速度也能接受。

与之相反,预加载则是在系统启动时或低峰期把数据加载到内存,运行时直接使用。常见于配置中心、字典表、模板文件等。一张表如果只有几百行数据,而每次请求都要查,不如在应用启动时一次性加载到本地HashMap里。 关键是平衡“启动时间”与“运行速度”。如果你的应用启动要6秒,预加载2秒,总时间8秒,平均每次请求快10毫秒,那么上线1000次请求后,预加载就赚回来了。性能优化本质上是在做时间投资,算清楚ROI,才能知行合一。

读完这10个技巧,你会发现性能优化并没有高深的理论,全是工程实践中积累的肌肉记忆。一句“没用过StringBuilder”可能就能解释为什么你的应用在双十一崩溃。 不要等到监控告警再翻代码,而是在写每一行代码时多问一句:“这个写法有没有更快的替代品?” 高效的Java应用从来不是写出来的,而是改出来的——从第一个循环的拼接,到最后一个缓冲区的释放,每一个细节都值得被认真对待。

下一次当你的服务再次告警时,希望你能胸有成竹地打开日志,而不是红着眼眶祈祷。性能优化的终点,是让你的代码跑出接近硬件的极限。 记住,你不是在写Java,是在跟计算机底层做交易。交易越公平,运行就越快。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐