JVM调优你必须知道这些。。。
前言
最近缺项目经历想快速提升项目实战能力(包含多个AI项目),或者最近找工作,或者想学习AI的小伙伴,可以看看下面👇🏻的这个链接(或许真的能够帮到你)。
今天我们来聊聊一个让很多Java开发者头疼的话题——JVM调优。
有些小伙伴在工作中,一遇到系统卡顿、内存溢出、GC频繁等问题就手足无措,要么胡乱调整几个参数,要么直接重启了事。
其实,JVM调优就像中医看病,需要望闻问切,找到病根才能对症下药。
作为一个经历过无数次线上故障的老兵,我见过太多因为JVM调优不当导致的"血案"。
今天这篇文章带大家彻底搞懂JVM调优的本质,希望对你会有所帮助。
一、JVM调优到底在调什么?
在深入技术细节之前,我们先明确一个基本概念:JVM调优的本质是在资源约束、性能要求和稳定性之间寻找最佳平衡点。
它不是简单的参数调整,而是一个系统工程。
举个例子,有些小伙伴在工作中可能会遇到这样的场景:系统运行一段时间后越来越卡,重启后恢复正常,但过几天又出现同样的问题。
这就像一个人的身体,表面看起来没事,但内部已经出现了问题。
为了让大家更直观地理解JVM的运行时结构,我画了一个内存模型图:

这张图展示了JVM运行时的主要内存区域。
接下来,我们深入理解为什么要进行JVM调优:
- 避免内存溢出:合理的内存分配可以防止OOM(OutOfMemoryError)
- 降低GC停顿:优化垃圾回收,减少STW(Stop-The-World)时间
- 提升吞吐量:让应用在单位时间内处理更多请求
- 增强稳定性:避免因内存问题导致的系统崩溃
有些小伙伴可能会问:"我的应用刚上线,需要立即调优吗?"
我的经验是:不要过早优化,但要有监控和预案。
当出现性能瓶颈时,再根据数据进行分析和调优。
二、JVM内存模型深度解析
要理解调优,首先要深入理解JVM的内存模型。 让我们从堆内存开始,这是调优的重点区域。
堆内存结构详解
堆内存是Java对象生存的地方,也是GC主要工作的区域。
现代JVM通常采用分代收集算法,将堆分为新生代和老年代。
为了更好地理解堆内存的结构,我画了一个详细的堆内存结构图: 
这张图清晰地展示了堆内存的完整结构。
现在让我们通过代码来验证和理解这个结构:
/**
* 演示内存分配和GC行为的示例
* 通过这个例子我们可以观察对象在堆中的生命周期
*/
public class MemoryAllocationDemo {
private static final int _1MB = 1024 * 1024;
/**
* VM参数:-Xms20M -Xmx20M -Xmn10M -XX:+PrintGCDetails
* -XX:SurvivorRatio=8 -XX:+UseSerialGC
*/
public static void testAllocation() {
byte[] allocation1, allocation2, allocation3, allocation4;
// 第1次分配:2MB,在Eden区
allocation1 = new byte[2 * _1MB];
System.out.println("分配2MB - allocation1");
// 第2次分配:2MB,在Eden区
allocation2 = new byte[2 * _1MB];
System.out.println("分配2MB - allocation2");
// 第3次分配:2MB,在Eden区
allocation3 = new byte[2 * _1MB];
System.out.println("分配2MB - allocation3");
// 第4次分配:4MB,触发Minor GC
// 前3个6MB对象无法全部放入Survivor区,直接晋升到老年代
allocation4 = new byte[4 * _1MB];
System.out.println("分配4MB - allocation4,触发GC");
}
public static void main(String[] args) {
testAllocation();
}
}代码逻辑深度解析:
- 内存分配:新对象首先尝试在Eden区分配
- GC触发:当Eden区空间不足时,触发Minor GC
- 对象晋升:存活对象从Eden区复制到Survivor区,年龄达到阈值(默认15)后晋升到老年代
- 空间担保:大对象可能直接进入老年代
运行这个程序,配合GC日志参数,我们可以清晰地看到内存分配和GC的过程:
[GC (Allocation Failure) [DefNew: 7303K->512K(9216K), 0.0043152 secs] 7303K->4656K(19456K), 0.0043652 secs]这个GC日志告诉我们:
DefNew:新生代收集器7303K->512K:新生代回收前7.3MB,回收后0.5MB9216K:新生代总大小9MB7303K->4656K:堆内存回收前7.3MB,回收后4.6MB
内存溢出实战分析
有些小伙伴在工作中最怕的就是内存溢出,下面我们通过代码重现几种典型的OOM场景:
import java.util.ArrayList;
import java.util.List;
/**
* 演示各种内存溢出场景
*/
public class OOMDemo {
/**
* Java堆内存溢出
* VM Args: -Xms20m -Xmx20m -XX:+HeapDumpOnOutOfMemoryError
*/
static class HeapOOM {
public static void main(String[] args) {
List<OOMObject> list = new ArrayList<>();
while (true) {
list.add(new OOMObject());
}
}
}
/**
* 虚拟机栈和本地方法栈溢出
* VM Args: -Xss128k
*/
static class StackLeak {
private int stackLength = 1;
public void stackLeak() {
stackLength++;
stackLeak(); // 递归调用导致栈深度增加
}
public static void main(String[] args) {
StackLeak stackLeak = new StackLeak();
try {
stackLeak.stackLeak();
} catch (Throwable e) {
System.out.println("栈深度: " + stackLeak.stackLength);
throw e;
}
}
}
/**
* 运行时常量池溢出(JDK 1.7+)
* VM Args: -XX:PermSize=10M -XX:MaxPermSize=10M
*/
static class RuntimeConstantPoolOOM {
public static void main(String[] args) {
// 使用List保持常量池引用,避免Full GC回收常量池行为
List<String> list = new ArrayList<>();
int i = 0;
while (true) {
list.add(String.valueOf(i++).intern());
}
}
}
static class OOMObject {
// 64KB对象
private byte[] placeholder = new byte[64 * 1024];
}
}OOM类型深度解析:
- Heap OOM:对象数量达到堆容量限制,常见于内存泄漏或数据量过大
- Stack Overflow:线程请求的栈深度超过虚拟机允许的最大深度,常见于无限递归
- Metaspace OOM:类元数据信息超过Metaspace大小,常见于动态生成类
三、垃圾收集器原理与选择
垃圾收集器是JVM调优的核心,不同的收集器适用于不同的场景。让我们深入理解主流收集器的工作原理。
垃圾收集算法基础
在了解具体收集器之前,我们先理解三种基本的垃圾收集算法:

主流垃圾收集器对比
现代JVM提供了多种垃圾收集器,每种都有其适用场景:
最近缺项目经历想快速提升项目实战能力(包含多个AI项目),或者最近找工作,或者想学习AI的小伙伴,可以看看下面👇🏻的这个链接(或许真的能够帮到你)。
/**
* 垃圾收集器性能测试类
* 通过不同的VM参数测试各种收集器的表现
*/
public class GCBenchmark {
private static final int SIZE = 1024;
private List<byte[]> data = new ArrayList<>();
/**
* 测试Serial收集器
* VM Args: -XX:+UseSerialGC -Xms512m -Xmx512m -XX:+PrintGCDetails
*/
public void testSerialGC() {
System.out.println("=== Testing Serial GC ===");
allocateMemory(1000);
}
/**
* 测试Parallel收集器
* VM Args: -XX:+UseParallelGC -Xms512m -Xmx512m -XX:+PrintGCDetails
*/
public void testParallelGC() {
System.out.println("=== Testing Parallel GC ===");
allocateMemory(1000);
}
/**
* 测试CMS收集器
* VM Args: -XX:+UseConcMarkSweepGC -Xms512m -Xmx512m -XX:+PrintGCDetails
*/
public void testCMSGC() {
System.out.println("=== Testing CMS GC ===");
allocateMemory(1000);
}
/**
* 测试G1收集器
* VM Args: -XX:+UseG1GC -Xms512m -Xmx512m -XX:+PrintGCDetails
*/
public void testG1GC() {
System.out.println("=== Testing G1 GC ===");
allocateMemory(1000);
}
/**
* 测试ZGC收集器(JDK 11+)
* VM Args: -XX:+UseZGC -Xms512m -Xmx512m -XX:+PrintGCDetails
*/
public void testZGC() {
System.out.println("=== Testing ZGC ===");
allocateMemory(1000);
}
private void allocateMemory(int count) {
long startTime = System.currentTimeMillis();
for (int i = 0; i < count; i++) {
// 分配1MB内存
data.add(new byte[SIZE * SIZE]);
// 模拟业务逻辑,每100次清理一次
if (i % 100 == 0) {
data.subList(0, data.size() / 2).clear();
System.gc(); // 建议GC,实际由JVM决定是否执行
}
}
long endTime = System.currentTimeMillis();
System.out.println("执行时间: " + (endTime - startTime) + "ms");
System.out.println("最终数据大小: " + data.size() + "MB");
}
public static void main(String[] args) {
GCBenchmark benchmark = new GCBenchmark();
// 根据VM参数选择测试方法
String gcType = System.getProperty("gc.type", "g1");
switch (gcType) {
case "serial":
benchmark.testSerialGC();
break;
case "parallel":
benchmark.testParallelGC();
break;
case "cms":
benchmark.testCMSGC();
break;
case "g1":
benchmark.testG1GC();
break;
case "zgc":
benchmark.testZGC();
break;
default:
benchmark.testG1GC();
}
}
}收集器选择策略
有些小伙伴在工作中不知道如何选择垃圾收集器,这里我给出一个实用的选择矩阵:
| 场景特征 | 推荐收集器 | 关键参数 | 理由 |
|---|---|---|---|
| 单核CPU、小内存 | Serial | -XX:+UseSerialGC | 简单高效,无线程开销 |
| 多核CPU、追求吞吐量 | Parallel | -XX:+UseParallelGC | 并行处理,吞吐量高 |
| 响应时间敏感、多核 | CMS | -XX:+UseConcMarkSweepGC | 低停顿,并发收集 |
| 大内存、平衡型 | G1 | -XX:+UseG1GC | 预测停顿,分区收集 |
| 超大堆、极低停顿 | ZGC | -XX:+UseZGC | 亚毫秒停顿,可扩展 |
为了更直观地理解各收集器的工作机制,我画了一个收集器工作流程图:

四、实战调优:从问题定位到解决方案
理论说了这么多,现在让我们进入实战环节。有些小伙伴在工作中遇到性能问题时往往无从下手,下面我通过一个真实案例展示完整的调优流程。
案例背景
假设我们有一个电商订单系统,在促销活动期间出现以下症状:
- 响应时间从50ms增加到2s+
- CPU使用率持续90%以上
- Full GC频繁,每分钟2-3次
- 偶尔出现OOM异常
第一步:问题定位与数据收集
首先,我们需要收集足够的监控数据来定位问题:
import java.lang.management.*;
import java.util.*;
/**
* JVM监控数据收集工具
* 用于实时收集JVM运行状态
*/
public class JVMMonitor {
/**
* 获取内存使用情况
*/
public static void printMemoryUsage() {
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
// 堆内存使用情况
MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
System.out.println("=== 堆内存使用情况 ===");
System.out.println("初始: " + (heapMemoryUsage.getInit() / 1024 / 1024) + "MB");
System.out.println("已使用: " + (heapMemoryUsage.getUsed() / 1024 / 1024) + "MB");
System.out.println("提交: " + (heapMemoryUsage.getCommitted() / 1024 / 1024) + "MB");
System.out.println("最大: " + (heapMemoryUsage.getMax() / 1024 / 1024) + "MB");
// 非堆内存使用情况
MemoryUsage nonHeapMemoryUsage = memoryMXBean.getNonHeapMemoryUsage();
System.out.println("=== 非堆内存使用情况 ===");
System.out.println("初始: " + (nonHeapMemoryUsage.getInit() / 1024 / 1024) + "MB");
System.out.println("已使用: " + (nonHeapMemoryUsage.getUsed() / 1024 / 1024) + "MB");
System.out.println("提交: " + (nonHeapMemoryUsage.getCommitted() / 1024 / 1024) + "MB");
System.out.println("最大: " + (nonHeapMemoryUsage.getMax() / 1024 / 1024) + "MB");
}
/**
* 获取GC统计信息
*/
public static void printGCStats() {
List<GarbageCollectorMXBean> gcMXBeans = ManagementFactory.getGarbageCollectorMXBeans();
System.out.println("=== GC统计 ===");
for (GarbageCollectorMXBean gcMXBean : gcMXBeans) {
System.out.println("收集器名称: " + gcMXBean.getName());
System.out.println("收集次数: " + gcMXBean.getCollectionCount());
System.out.println("收集时间: " + gcMXBean.getCollectionTime() + "ms");
}
}
/**
* 获取线程信息
*/
public static void printThreadStats() {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
System.out.println("=== 线程统计 ===");
System.out.println("活动线程数: " + threadMXBean.getThreadCount());
System.out.println("峰值线程数: " + threadMXBean.getPeakThreadCount());
System.out.println("守护线程数: " + threadMXBean.getDaemonThreadCount());
// 检测死锁
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
System.out.println("发现死锁线程: " + Arrays.toString(deadlockedThreads));
}
}
/**
* 模拟内存泄漏的代码
*/
public static class MemoryLeakDemo {
private static final List<byte[]> LEAK_LIST = new ArrayList<>();
public static void causeMemoryLeak() {
// 模拟内存泄漏:对象被静态集合持有,无法被GC
for (int i = 0; i < 100; i++) {
LEAK_LIST.add(new byte[1024 * 1024]); // 每次泄漏1MB
}
}
}
}第二步:分析GC日志
GC日志是调优的重要依据,让我们学习如何分析GC日志:
/**
* GC日志分析示例
* 通过解析GC日志来识别问题
*/
public class GCLogAnalyzer {
/**
* 模拟分析GC日志的方法
*/
public static void analyzeGCLog(String gcLog) {
// 在实际应用中,我们会使用专业的日志分析工具
// 这里简单演示分析逻辑
String[] lines = gcLog.split("\n");
int fullGCCount = 0;
int minorGCCount = 0;
long totalPauseTime = 0;
for (String line : lines) {
if (line.contains("Full GC")) {
fullGCCount++;
// 解析停顿时间
long pauseTime = extractPauseTime(line);
totalPauseTime += pauseTime;
} else if (line.contains("GC") && !line.contains("Full GC")) {
minorGCCount++;
long pauseTime = extractPauseTime(line);
totalPauseTime += pauseTime;
}
}
System.out.println("=== GC分析结果 ===");
System.out.println("Full GC次数: " + fullGCCount);
System.out.println("Minor GC次数: " + minorGCCount);
System.out.println("总停顿时间: " + totalPauseTime + "ms");
System.out.println("平均每次GC停顿: " + (totalPauseTime / (fullGCCount + minorGCCount)) + "ms");
// 判断是否存在问题
if (fullGCCount > 2) {
System.out.println("⚠️ 警告: Full GC过于频繁,可能存在内存泄漏或内存分配不合理");
}
if (totalPauseTime > 1000) {
System.out.println("⚠️ 警告: GC总停顿时间过长,影响系统响应");
}
}
private static long extractPauseTime(String gcLine) {
// 简化实现,实际需要复杂解析
if (gcLine.contains("secs")) {
String[] parts = gcLine.split("secs]");
if (parts.length > 0) {
String timePart = parts[0];
String[] timeParts = timePart.split("\\s+");
if (timeParts.length > 0) {
try {
double seconds = Double.parseDouble(timeParts[timeParts.length - 1]);
return (long) (seconds * 1000); // 转换为毫秒
} catch (NumberFormatException e) {
// 解析失败
}
}
}
}
return 0;
}
/**
* 生成模拟的GC日志用于测试
*/
public static String generateMockGCLog() {
return "[GC (Allocation Failure) [PSYoungGen: 65536K->10732K(76288K)] 65536K->23892K(251392K), 0.0234567 secs]\n" +
"[Full GC (Ergonomics) [PSYoungGen: 10732K->0K(76288K)] [ParOldGen: 13160K->22886K(175104K)] 23892K->22886K(251392K), 0.123456 secs]\n" +
"[GC (Allocation Failure) [PSYoungGen: 65432K->12345K(76288K)] 88318K->35231K(251392K), 0.0345678 secs]\n" +
"[Full GC (Ergonomics) [PSYoungGen: 12345K->0K(76288K)] [ParOldGen: 22886K->33147K(175104K)] 35231K->33147K(251392K), 0.145678 secs]";
}
public static void main(String[] args) {
String mockGCLog = generateMockGCLog();
analyzeGCLog(mockGCLog);
}
}第三步:调优参数配置
基于分析结果,我们制定调优方案:
/**
* JVM调优参数配置示例
* 针对不同场景提供调优模板
*/
public class JVMOptimizationTemplate {
/**
* 电商高并发场景调优模板
* 特点:低延迟、高吞吐、大内存
*/
public static class ECommerceOptimization {
// G1收集器配置(推荐)
public static String[] getG1Config() {
return new String[] {
"-XX:+UseG1GC", // 使用G1收集器
"-Xms4g", // 堆初始大小4G
"-Xmx8g", // 堆最大大小8G
"-XX:MaxGCPauseMillis=200", // 目标最大GC停顿200ms
"-XX:G1NewSizePercent=35", // 年轻代最小占比35%
"-XX:G1MaxNewSizePercent=60", // 年轻代最大占比60%
"-XX:InitiatingHeapOccupancyPercent=45", // 堆占用45%时开始混合GC
"-XX:G1HeapRegionSize=16m", // Region大小16MB
"-XX:ConcGCThreads=4", // 并发GC线程数
"-XX:ParallelGCThreads=8", // 并行GC线程数
"-XX:+UnlockExperimentalVMOptions",
"-XX:G1OldCSetRegionThresholdPercent=30", // 老年代回收比例
"-XX:+PrintGCDetails",
"-XX:+PrintGCDateStamps",
"-Xloggc:/opt/logs/gc.log"
};
}
// 内存溢出保护配置
public static String[] getOOMProtectionConfig() {
return new String[] {
"-XX:+HeapDumpOnOutOfMemoryError", // OOM时生成堆转储
"-XX:HeapDumpPath=/opt/logs/heapdump.hprof", // 堆转储路径
"-XX:OnOutOfMemoryError=/opt/scripts/restart.sh" // OOM时执行脚本
};
}
}
/**
* 大数据处理场景调优模板
* 特点:高吞吐、大内存、计算密集型
*/
public static class BigDataOptimization {
public static String[] getParallelConfig() {
return new String[] {
"-XX:+UseParallelGC", // 并行收集器
"-XX:+UseParallelOldGC", // 老年代并行收集
"-Xms8g",
"-Xmx16g",
"-XX:ParallelGCThreads=16", // GC线程数=CPU核心数
"-XX:MaxGCPauseMillis=500", // 目标停顿时间500ms
"-XX:GCTimeRatio=19", // GC时间与应用时间比例1:19
"-XX:+UseAdaptiveSizePolicy", // 自适应策略
"-XX:SurvivorRatio=8", // Eden与Survivor比例
"-XX:NewRatio=2" // 年轻代与老年代比例
};
}
}
/**
* 微服务场景调优模板
* 特点:快速启动、低内存占用、容器化
*/
public static class MicroserviceOptimization {
public static String[] getContainerConfig() {
return new String[] {
"-XX:+UseG1GC",
"-Xms512m", // 小内存启动
"-Xmx2g", // 根据容器限制设置
"-XX:MaxRAMPercentage=75.0", // 使用容器内存的75%
"-XX:InitialRAMPercentage=50.0", // 初始内存比例
"-XX:MinRAMPercentage=25.0", // 最小内存比例
"-XX:MaxGCPauseMillis=100", // 低延迟要求
"-XX:+UseContainerSupport", // 容器支持
"-XX:ActiveProcessorCount=2", // 限制CPU使用
"-XX:PretenureSizeThreshold=1M", // 大对象直接进入老年代
"-XX:+ExplicitGCInvokesConcurrent" // 并发处理System.gc()
};
}
}
/**
* 根据应用特征推荐配置
*/
public static String[] recommendConfig(String appType, String trafficLevel) {
switch (appType) {
case "ecommerce":
return ECommerceOptimization.getG1Config();
case "bigdata":
return BigDataOptimization.getParallelConfig();
case "microservice":
return MicroserviceOptimization.getContainerConfig();
default:
// 默认配置
return new String[] {
"-XX:+UseG1GC",
"-Xms1g",
"-Xmx2g",
"-XX:MaxGCPauseMillis=200"
};
}
}
}五、高级调优技巧与最佳实践
有些小伙伴在掌握了基础调优后,还想了解更高级的技巧,下面我分享一些实战经验。
内存泄漏检测与解决
内存泄漏是Java应用最常见的问题之一,下面演示如何检测和解决:
import java.util.*;
import java.lang.ref.*;
/**
* 内存泄漏检测与解决方案
*/
public class MemoryLeakDetector {
/**
* 常见内存泄漏场景演示
*/
public static class CommonMemoryLeaks {
// 场景1:静态集合引起的内存泄漏
private static final Map<String, Object> STATIC_MAP = new HashMap<>();
public static void staticCollectionLeak() {
for (int i = 0; i < 100000; i++) {
// 对象被静态集合持有,无法被GC
STATIC_MAP.put("key-" + i, new byte[1024]); // 每个对象1KB
}
System.out.println("静态集合泄漏: 添加了100,000个对象到静态Map");
}
// 场景2:监听器未注销
private static final List<EventListener> LISTENERS = new ArrayList<>();
public static void listenerLeak() {
EventListener listener = new EventListener() {
// 匿名内部类隐式持有外部类引用
};
LISTENERS.add(listener);
System.out.println("监听器泄漏: 监听器未正确移除");
}
// 场景3:线程局部变量未清理
private static final ThreadLocal<byte[]> THREAD_LOCAL = new ThreadLocal<>();
public static void threadLocalLeak() {
THREAD_LOCAL.set(new byte[1024 * 1024]); // 1MB
// 忘记调用remove()会导致内存泄漏
System.out.println("ThreadLocal泄漏: 未调用remove()方法");
}
}
/**
* 内存泄漏解决方案
*/
public static class MemoryLeakSolutions {
// 解决方案1:使用WeakReference避免内存泄漏
private static final Map<String, WeakReference<Object>> WEAK_MAP = new HashMap<>();
public static void weakReferenceSolution() {
Object largeObject = new byte[1024 * 1024]; // 1MB对象
WEAK_MAP.put("key", new WeakReference<>(largeObject));
// 当内存不足时,WeakReference引用的对象会被GC回收
System.out.println("使用WeakReference: 对象可在内存不足时被回收");
}
// 解决方案2:使用LRU缓存限制内存使用
public static class LRUCache<K, V> extends LinkedHashMap<K, V> {
private final int maxSize;
public LRUCache(int maxSize) {
super(16, 0.75f, true);
this.maxSize = maxSize;
}
@Override
protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
return size() > maxSize;
}
}
// 解决方案3:正确管理资源
public static void resourceManagementSolution() {
// 使用try-with-resources确保资源释放
try (Scanner scanner = new Scanner(System.in)) {
// 使用资源
String input = scanner.nextLine();
System.out.println("输入: " + input);
} // 自动调用close()方法
// 对于ThreadLocal,使用后清理
ThreadLocal<Object> threadLocal = new ThreadLocal<>();
try {
threadLocal.set(new Object());
// 使用threadLocal...
} finally {
threadLocal.remove(); // 确保清理
}
}
}
/**
* 内存分析工具使用示例
*/
public static void analyzeMemory() {
Runtime runtime = Runtime.getRuntime();
// 执行GC
runtime.gc();
// 计算内存使用
long usedMemory = runtime.totalMemory() - runtime.freeMemory();
System.out.println("已使用内存: " + (usedMemory / 1024 / 1024) + "MB");
System.out.println("总内存: " + (runtime.totalMemory() / 1024 / 1024) + "MB");
System.out.println("最大内存: " + (runtime.maxMemory() / 1024 / 1024) + "MB");
// 监控内存趋势
Thread monitoringThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
long currentUsed = runtime.totalMemory() - runtime.freeMemory();
System.out.println("内存监控 - 已使用: " + (currentUsed / 1024 / 1024) + "MB");
try {
Thread.sleep(5000); // 每5秒监控一次
} catch (InterruptedException e) {
break;
}
}
});
monitoringThread.setDaemon(true);
monitoringThread.start();
}
}JVM调优检查清单
为了帮助大家系统化进行JVM调优,我整理了一个检查清单:
/**
* JVM调优检查清单
* 按照这个清单顺序检查和优化
*/
public class JVMOptimizationChecklist {
public static void performChecklist() {
System.out.println("=== JVM调优检查清单 ===");
// 1. 基础检查
checkBasicConfiguration();
// 2. 内存检查
checkMemoryConfiguration();
// 3. GC检查
checkGCConfiguration();
// 4. 监控检查
checkMonitoringConfiguration();
// 5. 故障处理检查
checkFailureHandling();
}
private static void checkBasicConfiguration() {
System.out.println("\n1. 基础配置检查:");
// 检查JVM版本
String javaVersion = System.getProperty("java.version");
System.out.println("✓ JVM版本: " + javaVersion);
// 检查服务器内存
Runtime runtime = Runtime.getRuntime();
long maxMemory = runtime.maxMemory() / 1024 / 1024;
System.out.println("✓ 最大堆内存: " + maxMemory + "MB");
// 检查CPU核心数
int processors = runtime.availableProcessors();
System.out.println("✓ CPU核心数: " + processors);
}
private static void checkMemoryConfiguration() {
System.out.println("\n2. 内存配置检查:");
// 检查堆内存设置
String xmx = System.getProperty("Xmx");
String xms = System.getProperty("Xms");
System.out.println((xmx != null ? "✓" : "✗") + " Xmx设置: " + xmx);
System.out.println((xms != null ? "✓" : "✗") + " Xms设置: " + xms);
// 检查 metaspace 设置
String maxMetaspace = System.getProperty("MaxMetaspaceSize");
System.out.println((maxMetaspace != null ? "✓" : "✗") + " Metaspace设置: " + maxMetaspace);
// 建议
System.out.println("💡 建议: Xms和Xmx设置相同值,避免运行时调整");
}
private static void checkGCConfiguration() {
System.out.println("\n3. GC配置检查:");
// 检查使用的GC算法
String useG1GC = System.getProperty("UseG1GC");
String useParallelGC = System.getProperty("UseParallelGC");
String useCMS = System.getProperty("UseConcMarkSweepGC");
if (useG1GC != null) {
System.out.println("✓ 使用G1收集器");
} else if (useParallelGC != null) {
System.out.println("✓ 使用Parallel收集器");
} else if (useCMS != null) {
System.out.println("⚠️ 使用CMS收集器(已废弃)");
} else {
System.out.println("✓ 使用默认收集器");
}
// 检查GC日志配置
String printGC = System.getProperty("PrintGCDetails");
System.out.println((printGC != null ? "✓" : "✗") + " GC日志详细输出");
System.out.println("💡 建议: 生产环境开启GC日志,使用G1收集器");
}
private static void checkMonitoringConfiguration() {
System.out.println("\n4. 监控配置检查:");
// 检查JMX配置
String jmxPort = System.getProperty("com.sun.management.jmxremote.port");
System.out.println((jmxPort != null ? "✓" : "✗") + " JMX监控端口: " + jmxPort);
// 检查堆转储配置
String heapDumpOnOOM = System.getProperty("HeapDumpOnOutOfMemoryError");
System.out.println((heapDumpOnOOM != null ? "✓" : "✗") + " OOM时堆转储");
System.out.println("💡 建议: 开启JMX监控和堆转储,便于问题排查");
}
private static void checkFailureHandling() {
System.out.println("\n5. 故障处理检查:");
// 检查OOM处理脚本
String oomScript = System.getProperty("OnOutOfMemoryError");
System.out.println((oomScript != null ? "✓" : "✗") + " OOM处理脚本: " + oomScript);
// 检查错误日志
String errorFile = System.getProperty("XX:ErrorFile");
System.out.println((errorFile != null ? "✓" : "✗") + " 错误日志文件: " + errorFile);
System.out.println("💡 建议: 配置OOM自动处理脚本和错误日志路径");
}
}六、调优决策流程图
为了帮助大家在实际工作中快速做出调优决策,我总结了一个调优流程图:

总结
经过上面的深度剖析,我们来总结一下JVM调优的关键要点。
调优核心原则
- 数据驱动:不要凭感觉调优,要基于监控数据和日志分析
- 循序渐进:每次只调整一个参数,观察效果后再继续
- 理解业务:调优必须结合业务特点,不同场景需要不同策略
- 预防为主:建立监控告警,在问题发生前发现征兆
实用调优口诀
有些小伙伴在工作中容易忘记调优步骤,我总结了一个口诀:
一测二看三分析,四调五验六监控
- 测:性能测试建立基线
- 看:查看GC日志和监控指标
- 分析:分析瓶颈根本原因
- 调:针对性调整参数
- 验:验证调优效果
- 监控:建立长期监控机制
不同场景的调优重点
- Web应用:关注响应时间和并发能力,推荐G1收集器
- 大数据处理:关注吞吐量,推荐Parallel收集器
- 微服务:关注快速启动和低内存,合理设置堆大小
- 实时系统:关注低延迟,推荐ZGC或Shenandoah
最后的建议
JVM调优是一个需要不断学习和实践的过程。我建议大家:
- 建立性能基线:在系统正常时记录关键指标
- 模拟压力测试:定期进行压力测试,提前发现问题
- 持续监控:建立完善的监控告警体系
- 知识沉淀:记录调优案例,形成组织知识库
记住:没有最好的JVM参数,只有最适合的配置。
调优的本质是在理解业务需求、技术约束和资源限制的基础上,找到那个最优的平衡点。
最近缺项目经历想快速提升项目实战能力(包含多个AI项目),或者最近找工作,或者想学习AI的小伙伴,可以看看下面👇🏻的这个链接(或许真的能够帮到你)。