分布式微服务系统架构第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,分析运行轨迹
相关推荐
振鹏Dong1 小时前
超大规模数据场景(思路)——面试高频算法题目
算法·面试
uhakadotcom1 小时前
Python 与 ClickHouse Connect 集成:基础知识和实践
算法·面试·github
uhakadotcom1 小时前
Python 量化计算入门:基础库和实用案例
后端·算法·面试
小萌新上大分1 小时前
SpringCloudGateWay
java·开发语言·后端·springcloud·springgateway·cloudalibaba·gateway网关
uhakadotcom2 小时前
使用Python获取Google Trends数据:2025年详细指南
后端·面试·github
uhakadotcom2 小时前
使用 Python 与 Google Cloud Bigtable 进行交互
后端·面试·github
uhakadotcom2 小时前
使用 Python 与 BigQuery 进行交互:基础知识与实践
算法·面试
uhakadotcom2 小时前
使用 Hadoop MapReduce 和 Bigtable 进行单词统计
算法·面试·github
直视太阳2 小时前
springboot+easyexcel实现下载excels模板下拉选择
java·spring boot·后端
追逐时光者3 小时前
C#/.NET/.NET Core技术前沿周刊 | 第 33 期(2025年4.1-4.6)
后端·.net