加群联系作者vx:xiaoda0423
仓库地址:webvueblog.github.io/JavaPlusDoc...
1. 减少对象创建:使用对象复用(如线程池)
频繁创建和销毁对象会加剧 GC 压力。
ini
// 替代频繁 new Thread
ExecutorService executor = Executors.newFixedThreadPool(10);
for (int i = 0; i < 100; i++) {
executor.submit(() -> {
// 执行业务逻辑
System.out.println("处理任务:" + Thread.currentThread().getName());
});
}
executor.shutdown();
2. 使用 StringBuilder 替代字符串拼接
字符串拼接容易产生大量中间对象。
ini
// 差的写法(每次拼接都会创建新对象)
String result = "";
for (int i = 0; i < 1000; i++) {
result += i;
}
// 优化写法
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i);
}
String result = sb.toString();
3. 缓存热点对象,避免重复计算(局部变量表优化)
比如计算经常使用的日期格式化器等。
typescript
// 线程不安全(会创建很多 SimpleDateFormat)
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
// 优化方法:使用 ThreadLocal 缓存
private static final ThreadLocal<SimpleDateFormat> dateFormatThreadLocal =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public static String formatDate(Date date) {
return dateFormatThreadLocal.get().format(date);
}
4. 手动触发 Full GC 做性能基准测试前清场
例如你在做某些性能测试之前:
scss
System.gc(); // 请求 JVM 执行一次 GC(注意不能频繁用,生产不建议)
5. JVM 参数优化(启动参数)
这些是 JVM 层的设置,在运行时配置。例如:
ruby
java -Xms512m -Xmx2048m -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+PrintGCDetails -XX:+HeapDumpOnOutOfMemoryError
解释:
-Xms
、-Xmx
:设置初始/最大堆大小-XX:+UseG1GC
:使用 G1 垃圾收集器-XX:MaxGCPauseMillis=200
:最大 GC 停顿时间-XX:+PrintGCDetails
:打印 GC 详情-XX:+HeapDumpOnOutOfMemoryError
:OOM 时 dump 堆
6. 避免在循环中频繁创建对象
ini
// 差写法
for (int i = 0; i < 10000; i++) {
Pattern pattern = Pattern.compile("\d+");
Matcher matcher = pattern.matcher("12345");
matcher.matches();
}
// 优化写法
Pattern pattern = Pattern.compile("\d+");
for (int i = 0; i < 10000; i++) {
Matcher matcher = pattern.matcher("12345");
matcher.matches();
}
7. 使用懒加载 + 单例模式(减少类加载和对象创建)
csharp
public class ConfigManager {
private ConfigManager() {}
private static class Holder {
private static final ConfigManager INSTANCE = new ConfigManager();
}
public static ConfigManager getInstance() {
return Holder.INSTANCE;
}
}
为什么优化:
- 类加载是惰性加载的,只有在调用
getInstance()
时才会加载。 - 避免了提前创建对象,减少内存浪费。
8. 使用 -XX:+PrintGCDetails
分析 GC,优化代码定位内存泄漏
示例:定位哪里内存泄露:
javascript
Map<String, Object> cache = new HashMap<>();
while (true) {
Object value = new byte[1024 * 1024]; // 模拟大对象
cache.put(UUID.randomUUID().toString(), value); // 没有限制大小,容易内存泄漏
}
优化方式:加缓存上限 + LRU
typescript
LinkedHashMap<String, Object> cache = new LinkedHashMap<>(1000, 0.75f, true) {
protected boolean removeEldestEntry(Map.Entry<String, Object> eldest) {
return size() > 1000;
}
};
9. 避免频繁的 System.currentTimeMillis() 调用(热点调用)
scss
// 差写法:每次调用都陷入到系统调用
System.currentTimeMillis();
优化:使用缓存时间更新线程
csharp
public class TimeUtil {
private static volatile long currentTime = System.currentTimeMillis();
static {
ScheduledExecutorService timer = Executors.newSingleThreadScheduledExecutor();
timer.scheduleAtFixedRate(() -> currentTime = System.currentTimeMillis(), 0, 1, TimeUnit.MILLISECONDS);
}
public static long currentTimeMillis() {
return currentTime;
}
}
10. Zero-copy 技术:避免 IO 缓冲拷贝(NIO)
传统方式:
arduino
// 传统 IO 每次读写都会复制缓冲区
FileInputStream fis = new FileInputStream("file.txt");
byte[] buffer = new byte[1024];
while (fis.read(buffer) != -1) {
// 处理数据
}
优化方式:Java NIO(零拷贝)
ini
FileChannel srcChannel = new FileInputStream("file.txt").getChannel();
FileChannel destChannel = new FileOutputStream("file_copy.txt").getChannel();
srcChannel.transferTo(0, srcChannel.size(), destChannel); // 零拷贝方式
11. 使用 G1/ZGC 等新型 GC,结合代码优化垃圾生成
JVM 启动参数:
ruby
java -XX:+UseG1GC -Xms512m -Xmx2g -XX:+PrintGCDetails -XX:+UseStringDeduplication
代码配合优化:
ini
// 避免大量临时字符串重复创建(开启 G1 的字符串去重)
List<String> list = new ArrayList<>();
for (int i = 0; i < 100000; i++) {
list.add(("用户" + i).intern()); // intern 可以帮助字符串去重(慎用)
}
12. 使用 ByteBuffer.allocateDirect 避免堆内存占用
ini
// 默认是在堆内存
ByteBuffer buffer = ByteBuffer.allocate(1024);
// 优化:使用直接内存(减少 GC 压力,但分配成本略高)
ByteBuffer directBuffer = ByteBuffer.allocateDirect(1024);
13. 避免频繁装箱/拆箱操作
csharp
// 差写法:频繁装箱
for (int i = 0; i < 10000; i++) {
list.add(i); // i 自动装箱成 Integer
}
// 优化:使用基本类型数组或专用集合类(如 Trove、fastutil)
int[] arr = new int[10000];
使用对象池、线程池,减少对象频繁创建
ini
ExecutorService executor = Executors.newFixedThreadPool(10); // 重用线程,避免频繁创建销毁
// 比如数据库连接池使用 HikariCP
HikariDataSource ds = new HikariDataSource();
ds.setMaximumPoolSize(20); // 限制连接数量
避免重复创建大对象 / 使用缓存
arduino
// 缓存 DateFormat、Pattern、等重量对象
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
使用 G1 GC 配置,适用于大堆和低延迟需求
JVM 启动参数:
ruby
-XX:+UseG1GC
-XX:+PrintGCDetails
-XX:MaxGCPauseMillis=200
-XX:+UseStringDeduplication
内存泄漏定位示例
arduino
// 模拟内存泄漏:不断创建对象加入静态集合
private static final List<byte[]> list = new ArrayList<>();
public static void main(String[] args) {
while (true) {
byte[] b = new byte[1024 * 1024];
list.add(b);
}
}
开启 JVM 参数 -XX:+HeapDumpOnOutOfMemoryError
,使用 jvisualvm
或 MAT
分析堆。
使用并发容器代替同步
arduino
Map<String, String> map = new ConcurrentHashMap<>();
避免类加载过程中执行大量静态代码
arduino
// 不建议在类加载时就执行重量级逻辑
static {
// 连接数据库、大文件加载等操作不要放在这里
}
(模拟内存使用 + 线程池)
java
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class JvmTuningDemo {
// 模拟一个缓存,未做清理,易导致内存泄漏
private static final List<byte[]> memoryLeakList = new ArrayList<>();
// 模拟线程池处理任务
private static final ExecutorService executor = new ThreadPoolExecutor(
4, // 核心线程数
8, // 最大线程数
60L, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(100), // 有界队列
new ThreadPoolExecutor.AbortPolicy() // 拒绝策略
);
public static void main(String[] args) throws InterruptedException {
System.out.println("模拟 JVM 压力启动...");
for (int i = 0; i < 100_000; i++) {
// 模拟大对象分配,占用堆内存
memoryLeakList.add(new byte[1024 * 10]); // 10KB
// 提交任务给线程池(模拟异步请求)
final int taskId = i;
executor.submit(() -> {
try {
// 模拟业务逻辑执行
Thread.sleep(10);
if (taskId % 10_000 == 0) {
System.out.println("处理任务: " + taskId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
// 控制内存增长速度
if (i % 1000 == 0) {
Thread.sleep(100);
}
}
System.out.println("任务提交完毕,等待执行完成...");
executor.shutdown();
executor.awaitTermination(10, TimeUnit.MINUTES);
}
}
内存泄漏模拟 + 自动释放优化
java
import java.util.Map;
import java.util.WeakHashMap;
public class MemoryLeakFixedDemo {
// 会被 GC 回收的缓存实现
private static final Map<Object, byte[]> safeCache = new WeakHashMap<>();
public static void main(String[] args) throws InterruptedException {
System.out.println("开始缓存填充测试...");
for (int i = 0; i < 100000; i++) {
Object key = new Object(); // key 不保留引用
safeCache.put(key, new byte[1024 * 100]); // 100KB per entry
if (i % 1000 == 0) {
System.gc(); // 手动触发 GC 观察 WeakHashMap 是否被清理
System.out.printf("第 %d 次插入,缓存大小:%d\n", i, safeCache.size());
}
}
System.out.println("测试结束");
}
}
✅ JVM 启动参数建议
ruby
-Xmx256m -Xms256m -Xss512k -XX:+UseG1GC -XX:+PrintGCDetails -Xloggc:gc.log
📌 调优点说明:
- 使用
WeakHashMap
避免强引用导致的内存泄漏 - 用于模拟和验证"缓存未清理"导致 OOM 的问题
线程池配置不当导致内存暴涨 + 有界线程池优化
java
import java.util.concurrent.*;
public class ThreadPoolMemoryDemo {
public static void main(String[] args) throws InterruptedException {
// 错误的做法:不限制任务队列大小(容易 OOM)
ExecutorService badPool = new ThreadPoolExecutor(
2, 4,
60, TimeUnit.SECONDS,
new LinkedBlockingQueue<>() // 无限队列
);
// 推荐做法:限制队列大小 + 拒绝策略
ExecutorService goodPool = new ThreadPoolExecutor(
2, 4,
60, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(1000),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝时在主线程跑任务
);
for (int i = 0; i < 10_000; i++) {
final int taskId = i;
try {
goodPool.submit(() -> {
try {
Thread.sleep(100); // 模拟 IO/业务耗时
if (taskId % 1000 == 0) {
System.out.println("执行任务:" + taskId);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
} catch (RejectedExecutionException e) {
System.out.println("任务被拒绝:" + taskId);
}
}
goodPool.shutdown();
goodPool.awaitTermination(5, TimeUnit.MINUTES);
}
}
JVM 调优常见场景汇总
类型 | 场景问题 | 典型表现 | 常用工具 | 优化思路 |
---|
🚀 1. 内存溢出(OOM)问题
表现 | OutOfMemoryError: Java heap space |
原因 | 对象创建过多、GC 无法及时回收 |
工具 | jmap , MAT , VisualVM , HeapDump |
优化 | 排查大对象、检查缓存、内存泄露、增加堆大小(-Xmx) |
🧱 2. Metaspace(类元数据)溢出
表现 | OutOfMemoryError: Metaspace |
原因 | 动态加载太多类(如反射、动态代理、频繁热部署) |
工具 | jcmd , jmap , VisualVM |
优化 | 合理设置 -XX:MaxMetaspaceSize ,排查类加载器泄漏 |
🧵 3. 线程过多,无法创建新线程
表现 | OutOfMemoryError: unable to create new native thread |
原因 | 线程创建无上限,系统线程资源耗尽 |
工具 | jstack , top -H , jconsole |
优化 | 使用线程池,限制最大线程数,优化业务阻塞逻辑 |
⏱ 4. 频繁 Full GC / GC 暴涨
表现 | GC 日志频繁出现 Full GC,应用卡顿、吞吐低 |
原因 | 堆老年代频繁满,Survivor 区调优不合理 |
工具 | jstat -gcutil , GClog , Arthas , VisualGC |
优化 |
- 调整堆内存大小:
-Xmx
,-Xms
- 调整新生代大小:
-Xmn
- 优化对象生命周期和回收代策略
🐢 5. 应用响应慢、卡顿、吞吐低
表现 | RT(响应时间)变高,系统 TPS 下降 |
原因 | GC 频繁、线程阻塞、IO 等待、锁竞争 |
工具 | jstack , jfr , perf , Arthas , BTrace |
优化 |
- 减少大对象创建
- 优化锁(如 synchronized → ReentrantLock)
- 异步处理、限流、线程池调整
📦 6. 直接内存(DirectMemory)泄漏
表现 | OutOfMemoryError: Direct buffer memory |
原因 | Netty 或 NIO 使用的堆外内存未及时释放 |
工具 | jcmd VM.native_memory summary , Netty leak detector |
优化 | 增加 -XX:MaxDirectMemorySize ,手动释放 ByteBuffer,升级框架版本 |
📈 7. 内存使用高但 GC 不回收
表现 | 应用占用大量内存,GC 没触发或没效果 |
原因 | 持有对象引用、内存泄露、Soft/Weak 引用被滥用 |
工具 | jmap , MAT , HeapDump |
优化 | 定位强引用链,清理缓存对象,避免静态变量泄漏 |
🧬 8. 类加载过多 / 类卸载失败
表现 | Metaspace 持续增长,PermGen 泄漏(Java 7) |
原因 | 动态加载类没有被正确卸载(如 Web 容器热部署) |
工具 | jvisualvm , jcmd VM.class_histogram |
优化 | 确保类加载器能被 GC 回收,使用统一的加载器 |
🧪 9. JVM 参数不合理
表现 | GC 效率低、内存空间浪费、OOM |
原因 | 堆太小、线程栈太大、GC 参数不匹配 |
工具 | -XX:+PrintGCDetails , jstat , Arthas |
优化 | 调整参数:-Xmx , -Xms , -Xss , -Xmn , GC 算法选择 |
🧨 10. 服务崩溃 / JVM Crash
表现 | JVM 崩溃、core dump、hs_err_pid.log 文件生成 |
原因 | 本地方法调用异常、native 内存溢出、JNI 错误 |
工具 | hs_err_pid.log , gdb , jfr , perf |
优化 | 分析 core dump,检查 native 库,避免 JNI 滥用 |
🚧 JVM 调优常用工具列表
工具 | 作用 |
---|---|
jmap |
导出堆快照、对象统计 |
jstack |
查看线程栈 |
jstat |
GC 监控 |
VisualVM / JMC |
图形化调试内存、CPU |
GClog + GCViewer |
分析垃圾回收日志 |
Arthas |
热诊断神器,线程、堆、方法调用监控 |
MAT |
内存泄漏分析工具 |
jcmd |
启动诊断命令、元空间统计 |
jfr |
Java Flight Recorder,分析运行轨迹 |