跳到主要内容 Java 性能优化的 50 个细节 | 极客日志
Java java 算法
Java 性能优化的 50 个细节 总结了 50 条 Java 性能优化建议,涵盖代码编写、JVM 调优、并发编程、IO 网络及数据库交互等方面。内容涉及局部变量使用、对象池、字符串处理、正则预编译、集合选择、线程池配置、内存映射文件、日志级别控制、序列化方式、缓存策略、异步编程、SQL 优化及索引设计等具体实践。旨在通过减少 GC 压力、降低锁竞争、优化资源利用及避免常见陷阱,提升系统整体运行效率与稳定性。
SparkGeek 发布于 2026/3/25 更新于 2026/4/16 3 浏览引言
Java 性能优化是一个永恒的话题。无论是构建高并发的 Web 服务,还是处理海量数据的离线任务,性能始终是衡量系统质量的核心指标之一。然而,优化并非盲目地堆砌技巧,而是需要深入理解 JVM 原理、操作系统特性以及编程语言的底层实现。
本文整理了 50 个 Java 性能优化的细节,涵盖代码编写、JVM 调优、并发编程、IO 网络等多个维度。每个细节都力求讲清原理、提供可操作的实践建议,并辅以代码示例。
一、代码层面的优化细节
1. 使用局部变量避免堆分配
问题 :在方法内部频繁创建的对象如果逃逸出方法作用域,会被分配到堆上,增加 GC 压力。
优化 :尽可能使用局部变量,让对象在栈上分配(如果 JIT 支持逃逸分析)。即使没有逃逸分析,局部变量的生命周期短,也能减轻年轻代 GC 的负担。
public void process () {
Date d = new Date ();
}
public void process (Date d) {
}
2. 重用对象避免频繁创建(对象池) 问题 :创建和销毁对象的成本较高,尤其是重量级对象(如数据库连接、线程、Socket)。
优化 :使用对象池技术,如数据库连接池、线程池。对于轻量级对象,如果创建开销小,则不必池化,以免增加管理成本。
3. 使用基本类型而不是包装类型 问题 :包装类型(如 Integer、Double)是对象,占用更多内存,且涉及自动装箱/拆箱会有性能开销。
优化 :在局部变量、成员变量等场景,尽量使用基本类型(int、double),除非必须使用泛型或集合。
Integer sum = 0 ;
for (int i = 0 ; i < 100000 ; i++) {
sum += i;
}
int sum = 0 ;
for (int i = 0 ; i < 100000 ; i++) {
sum += i;
}
4. 谨慎使用正则表达式,预编译 Pattern 问题 :正则表达式的编译非常耗时,如果重复使用相同的正则,每次调用 String.matches() 都会重新编译。
优化 :将正则表达式预编译为 Pattern 对象,并复用。
for (String line : lines) {
if (line.matches("\\d+" )) {
...
}
}
private static final Pattern DIGIT_PATTERN = Pattern.compile("\\d+" );
for (String line : lines) {
if (DIGIT_PATTERN.matcher(line).matches()) {
...
}
}
5. 字符串拼接使用 StringBuilder/StringBuffer 问题 :使用 + 拼接字符串,在循环中会产生大量临时 String 对象,导致 GC 压力。
优化 :在循环或复杂拼接中使用 StringBuilder(非线程安全,性能更高)或 StringBuffer(线程安全)。
String s = "" ;
for (int i = 0 ; i < 1000 ; i++) {
s += i;
}
StringBuilder sb = new StringBuilder (1000 * 5 );
for (int i = 0 ; i < 1000 ; i++) {
sb.append(i);
}
String s = sb.toString();
6. 使用 String.intern 节省内存(但需谨慎) 原理 :String.intern() 可以将字符串放入常量池,重复的字符串可以共享同一个字符数组,从而节省内存。
适用场景 :有大量重复字符串且生命周期较长,如缓存的业务数据中的枚举值。但注意常量池的垃圾回收在 JDK 7+ 已支持,但 intern 本身有代价(查找和可能扩容)。
String city = rs.getString("city" ).intern();
7. 避免在循环中创建临时变量 问题 :在循环体内创建对象,如果对象作用域仅在循环内,可以考虑将对象移到循环外部复用。
优化 :但要注意,如果对象需要每次重置,复用的代价可能高于创建。需要权衡。
for (int i = 0 ; i < 100000 ; i++) {
List<String> list = new ArrayList <>();
list.add("item" + i);
}
List<String> list = new ArrayList <>();
for (int i = 0 ; i < 100000 ; i++) {
list.clear();
list.add("item" + i);
}
8. 使用延迟初始化 问题 :有些对象的创建成本较高,且不一定每次都会被使用,提前初始化浪费资源。
优化 :使用延迟加载(Lazy Initialization),直到第一次使用时才创建。注意并发环境下的线程安全。
public class LazyHolder {
private static final HeavyObject INSTANCE = new HeavyObject ();
public static HeavyObject getInstance () {
return INSTANCE;
}
}
private volatile HeavyObject instance;
public HeavyObject getInstance () {
if (instance == null ) {
synchronized (this ) {
if (instance == null ) {
instance = new HeavyObject ();
}
}
}
return instance;
}
9. 尽量减少同步范围,使用并发集合 问题 :过度使用 synchronized 会导致线程阻塞,降低并发性能。
优化 :缩小同步块的范围,优先使用 java.util.concurrent 包中的并发集合(如 ConcurrentHashMap、CopyOnWriteArrayList)替代手动同步。
Map<String, String> map = new HashMap <>();
synchronized (map) {
map.put(key, value);
}
ConcurrentHashMap<String, String> map = new ConcurrentHashMap <>();
map.put(key, value);
10. 使用 ThreadLocal 避免同步 场景 :如果某个变量每个线程都需要有自己的独立副本,可以使用 ThreadLocal,避免加锁。
private static final ThreadLocal<SimpleDateFormat> DATE_FORMAT = ThreadLocal.withInitial(() -> new SimpleDateFormat ("yyyy-MM-dd" ));
public String formatDate (Date date) {
return DATE_FORMAT.get().format(date);
}
11. 使用原子类代替加锁 原理 :java.util.concurrent.atomic 包下的类(如 AtomicInteger、AtomicReference)基于 CAS 操作,无锁但能保证线程安全,性能优于加锁。
private int count;
public synchronized void increment () {
count++;
}
private AtomicInteger count = new AtomicInteger ();
public void increment () {
count.incrementAndGet();
}
12. 合理使用线程池,避免创建过多线程 问题 :每次任务都新建线程,线程创建销毁开销大,且过多线程会导致上下文切换频繁,甚至耗尽系统资源。
优化 :使用 ThreadPoolExecutor 创建线程池,合理设置核心线程数、最大线程数、队列等参数。
ExecutorService executor = new ThreadPoolExecutor (
10 ,
50 ,
60L , TimeUnit.SECONDS,
new LinkedBlockingQueue <>(1000 )
);
executor.submit(() -> { ... });
13. 使用 NIO 而非 BIO 原理 :传统 BIO 是阻塞 IO,每个连接需要一个线程处理;NIO 基于 Selector 多路复用,一个线程可管理多个连接,适合高并发。
优化 :对于网络通信,优先使用 NIO 框架(如 Netty)而非自己编写 Socket 循环。
14. 使用缓冲流进行 IO 操作 问题 :不带缓冲的读写(如 FileInputStream.read())每次调用都会触发系统调用,性能极低。
优化 :使用 BufferedInputStream、BufferedReader 等包装流,减少实际 IO 次数。
try (FileInputStream fis = new FileInputStream ("file.txt" )) {
int b;
while ((b = fis.read()) != -1 ) {
...
}
}
try (BufferedInputStream bis = new BufferedInputStream (new FileInputStream ("file.txt" ))) {
byte [] buffer = new byte [8192 ];
int len;
while ((len = bis.read(buffer)) != -1 ) {
...
}
}
15. 使用内存映射文件提高大文件读写 原理 :MappedByteBuffer 将文件直接映射到内存,读写操作相当于内存操作,速度极快,适合大文件。
try (RandomAccessFile file = new RandomAccessFile ("large.dat" , "rw" ); FileChannel channel = file.getChannel()) {
MappedByteBuffer buffer = channel.map(FileChannel.MapMode.READ_WRITE, 0 , file.length());
}
16. 避免过度日志,合理设置日志级别 问题 :生产环境打印过多日志(特别是 DEBUG 级别)会严重影响性能,磁盘 IO 和字符串拼接开销不容忽视。
优化 :设置合适的日志级别(如 INFO),并使用占位符避免字符串拼接的浪费。
logger.debug("User " + userId + " login at " + System.currentTimeMillis());
if (logger.isDebugEnabled()) {
logger.debug("User {} login at {}" , userId, System.currentTimeMillis());
}
17. 使用合适的序列化方式 问题 :Java 原生序列化(Serializable)性能差、体积大,且存在安全漏洞。
优化 :对于内部通信,可选用 Protobuf、Kryo、Avro 等高性能序列化框架;对于跨语言场景,Protobuf 或 JSON 是更好的选择。
18. 使用软引用/弱引用实现缓存 场景 :实现缓存时,如果缓存对象过多可能导致内存溢出,可以使用 SoftReference(内存不足时回收)或 WeakReference(下次 GC 即回收)。
Map<String, SoftReference<BigImage>> cache = new HashMap <>();
SoftReference<BigImage> ref = cache.get(key);
BigImage image = (ref == null ) ? null : ref.get();
if (image == null ) {
image = loadBigImage(key);
cache.put(key, new SoftReference <>(image));
}
19. 合理设置 JVM 堆大小和垃圾回收器 问题 :堆过小会导致频繁 GC,过大则单次 GC 时间长。GC 器的选择也至关重要。
优化 :根据应用特点选择 GC 器(如 G1、ZGC),并设置初始堆大小(-Xms)和最大堆大小(-Xmx)一致,避免动态调整。监控 GC 日志以调优。
20. 避免使用 finalize() 方法 原理 :finalize() 由 Finalizer 线程执行,执行时间不确定,且会导致对象复活等问题,严重影响 GC 性能。
优化 :使用 Cleaner(Java 9+)或显式释放资源(如 try-with-resources)。
21. 使用局部变量表尽量复用 原理 :JVM 局部变量表在方法调用时分配,方法内局部变量复用可以减少栈帧大小,但更重要的是避免不必要的对象引用。
实践 :合理声明变量,不要在循环内重复声明引用变量(但现代 JIT 会优化,可读性更重要)。
22. 使用 lambda 表达式注意不要捕获过多变量 问题 :Lambda 表达式如果捕获外部变量,会生成匿名内部类,且捕获的变量必须实际 final。捕获大量变量可能增加内存占用。
优化 :尽量将外部变量作为参数传递,或使用静态方法。
23. 使用 Stream API 注意性能影响 原理 :Stream API 提供了声明式编程,但自动装箱、中间操作(如 filter、map)可能产生额外开销。parallelStream 未必总是更快,需数据量大且操作可并行。
int sum = list.parallelStream().filter(x -> x > 0 ).mapToInt(i -> i).sum();
24. 避免在热点代码中使用反射 问题 :反射涉及动态解析,性能远低于直接调用。尤其是在高频调用的方法中。
优化 :如果必须使用,可缓存 Method 对象,或者使用 MethodHandle(比反射略快)。更优方案是使用编译时代码生成(如 LambdaMetafactory)。
private static final Method method = getMethod();
method.invoke(obj, args);
25. 使用 MethodHandle 替代反射(如果必须) 原理 :MethodHandle 是 Java 7 引入的,类似于反射,但具有更好的性能,因为它由 JIT 直接支持,可以进行内联优化。
26. 使用缓存减少重复计算 场景 :计算密集型或 IO 密集型操作,如果输入相同输出相同,可以考虑缓存结果(如 Guava Cache、Caffeine)。
LoadingCache<Key, Graph> graphs = Caffeine.newBuilder()
.maximumSize(10000 )
.expireAfterWrite(10 , TimeUnit.MINUTES)
.build(key -> createExpensiveGraph(key));
27. 使用异步编程提升响应性 原理 :将耗时操作异步化,可以释放当前线程处理其他请求。Java 中可以使用 CompletableFuture 或响应式框架(如 Reactor)。
CompletableFuture.supplyAsync(() -> fetchData())
.thenApply(data -> process(data))
.thenAccept(result -> send(result));
28. 减少上下文切换 原理 :CPU 上下文切换开销较大。多线程竞争锁、系统调用过多都会导致上下文切换。
优化 :减少锁竞争、使用无锁数据结构、减少线程数量(合理配置线程池)、避免频繁的线程阻塞。
29. 使用数据库连接池 问题 :每次数据库操作都创建连接、关闭连接,开销巨大。
优化 :使用 HikariCP、Druid 等连接池,复用连接。
30. 使用批量操作减少数据库交互 原理 :网络往返(RTT)是性能杀手。批量提交 SQL(如 JDBC batch)可大幅提升性能。
Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement("INSERT INTO user (name) VALUES (?)" );
for (User user : userList) {
ps.setString(1 , user.getName());
ps.addBatch();
}
ps.executeBatch();
31. 优化 SQL 语句和索引 原理 :慢 SQL 往往是性能瓶颈。通过 EXPLAIN 分析执行计划,建立合适的索引,避免全表扫描。
优化 :避免 SELECT *,只查需要的列;避免在索引列上使用函数;合理使用覆盖索引等。
32. 使用 PreparedStatement 预编译 SQL 原理 :PreparedStatement 会预编译 SQL,减少数据库解析开销,同时防止 SQL 注入。
PreparedStatement ps = conn.prepareStatement("SELECT * FROM user WHERE id = ?" );
ps.setInt(1 , id);
ResultSet rs = ps.executeQuery();
33. 使用读写分离 场景 :对于读多写少的应用,可以将读操作分发到从库,写操作在主库,通过主从复制减轻主库压力。
34. 使用 CDN 和缓存静态资源 原理 :静态资源(图片、CSS、JS)放在 CDN 上,减少服务器带宽和响应时间。
35. 使用 GZIP 压缩减少网络传输 原理 :对于文本类响应(JSON、HTML),启用 GZIP 压缩可减少 70% 以上传输量,但会增加 CPU 开销。需权衡。
36. 避免过度设计,保持简单 原理 :简单的代码更容易被 JIT 优化,也更容易维护。复杂的模式可能会引入不必要的间接层和性能损耗。
37. 使用性能分析工具定位瓶颈 工具 :JProfiler、VisualVM、Arthas、JMH 等。不要猜测性能问题,用数据说话。
38. 避免在循环中调用远程服务 问题 :循环中调用远程 HTTP 或 RPC,会导致累积延迟,且可能因网络抖动导致整体超时。
List<CompletableFuture<Result>> futures = ids.stream()
.map(id -> CompletableFuture.supplyAsync(() -> remoteCall(id)))
.collect(Collectors.toList());
List<Result> results = futures.stream().map(CompletableFuture::join).collect(Collectors.toList());
39. 使用本地缓存替代分布式缓存(适当场景) 原理 :本地缓存(如 Caffeine)速度远快于分布式缓存(如 Redis),因为没有网络开销。但需要注意数据一致性。
40. 合理设置超时时间 场景 :网络请求、数据库连接等必须设置超时,避免线程无限等待。超时时间不宜过长或过短,需根据业务容忍度调整。
41. 使用长连接代替短连接 原理 :HTTP 1.1 默认支持 Keep-Alive,TCP 连接复用,减少握手开销。数据库连接池也是长连接的体现。
42. 使用 CompletableFuture 组合异步操作 原理 :CompletableFuture 提供了丰富的异步编排能力,可以避免回调地狱,同时利用 ForkJoinPool 提高并行度。
CompletableFuture<Double> future = CompletableFuture.supplyAsync(this ::getPrice)
.thenApply(price -> price * 0.9 )
.thenApply(this ::convertCurrency);
43. 注意 ArrayList 和 LinkedList 的选择 原理 :ArrayList 基于数组,随机访问快,但插入删除慢(除非在末尾);LinkedList 基于双向链表,插入删除快,但随机访问慢。根据实际场景选择。
优化 :如果需要频繁在列表中间插入删除,且不经常随机访问,用 LinkedList;否则用 ArrayList。
44. 使用 HashMap 时注意初始容量 原理 :HashMap 默认容量 16,负载因子 0.75。如果已知数据量,指定初始容量可避免频繁扩容(rehash),提高性能。
Map<String, Object> map = new HashMap <>(1024 );
45. 使用 EnumMap/EnumSet 优化枚举相关操作 原理 :EnumMap 内部用数组实现,根据枚举 ordinal 索引,性能比 HashMap 更高,内存更省。
Map<Color, String> map = new EnumMap <>(Color.class);
46. 使用位运算提高效率 原理 :位运算直接操作二进制,比算术运算快。适用于权限组合、状态标志等场景。
int READ = 1 << 0 ;
int WRITE = 1 << 1 ;
int EXECUTE = 1 << 2 ;
int permissions = READ | WRITE;
boolean canRead = (permissions & READ) != 0 ;
47. 避免使用异常控制流程 问题 :异常处理需要构建异常栈,开销极大。不要用 try-catch 包裹正常流程。
try {
int result = Integer.parseInt(str);
} catch (NumberFormatException e) {
}
if (str != null && str.matches("\\d+" )) {
int result = Integer.parseInt(str);
}
48. 使用零拷贝技术传输文件 原理 :传统文件读取需要从内核拷贝到用户空间再写回内核,零拷贝(如 FileChannel.transferTo)直接在内核空间传输,减少拷贝次数。
FileChannel source = new FileInputStream ("source.txt" ).getChannel();
WritableByteChannel dest = ...;
source.transferTo(0 , source.size(), dest);
49. 使用 JMH 进行微基准测试 原理 :JMH(Java Microbenchmark Harness)是 JVM 认可的基准测试工具,能避免 JIT 优化对测试结果的影响。
50. 使用 GraalVM 本地编译优化 原理 :GraalVM 可以将 Java 应用编译为本地可执行文件,启动速度极快,内存占用低,适合微服务场景。但可能不支持所有 Java 特性。
微信扫一扫,关注极客日志 微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
相关免费在线工具 Keycode 信息 查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
Escape 与 Native 编解码 JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
JavaScript / HTML 格式化 使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
JavaScript 压缩与混淆 Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
加密/解密文本 使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
Base64 字符串编码/解码 将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online