分布式微服务系统架构第98集:优化jvm的代码

加群联系作者vx:xiaoda0423

仓库地址:webvueblog.github.io/JavaPlusDoc...

1024bat.cn/

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,使用 jvisualvmMAT 分析堆。

使用并发容器代替同步

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,分析运行轨迹
相关推荐
怪兽20141 分钟前
请谈谈什么是同步屏障?
android·面试
间彧4 分钟前
Redis缓存穿透、缓存雪崩、缓存击穿详解与代码实现
后端
摸鱼的春哥8 分钟前
【编程】是什么编程思想,让老板对小伙怒飙英文?Are you OK?
前端·javascript·后端
尘世中一位迷途小书童8 分钟前
版本管理实战:Changeset 工作流完全指南(含中英文对照)
前端·面试·架构
吃饺子不吃馅17 分钟前
【八股汇总,背就完事】这一次再也不怕webpack面试了
前端·面试·webpack
逛逛GitHub1 小时前
这个牛逼的股票市场平台,在 GitHub 上开源了。
前端·github
Max8121 小时前
Agno Agent 服务端文件上传处理机制
后端
调试人生的显微镜1 小时前
苹果 App 怎么上架?从开发到发布的完整流程与使用 开心上架 跨平台上传
后端
顾漂亮1 小时前
Spring AOP 实战案例+避坑指南
java·后端·spring
间彧1 小时前
Redis Stream相比阻塞列表和发布订阅有哪些优势?适合什么场景?
后端