JVM 21 的优化指南:如何进行JVM调优,JVM调优参数有哪些

这篇文章将详细介绍如何进行JVM 21调优,包括JVM 21调优参数及其应用。此外,我将提供12个实用的代码示例,每个示例都会结合JVM启动参数和Java代码。

本文已收录于,我的技术网站 java-broke.site,有大厂完整面经,工作技术,架构师成长之路,等经验分享

随着JVM版本的不断更新,JVM 21 在性能和功能上都带来了显著的提升。合理的JVM调优不仅可以提高应用程序的性能,还能显著减少内存消耗和GC(垃圾回收)停顿时间。本文将详细介绍JVM 21 的优化指南,包含如何进行JVM调优以及常见的JVM调优参数,并提供3个实用的代码示例。

JVM 调优的基本思路

1、 确定问题:了解当前系统的瓶颈,是CPU、内存、磁盘I/O还是网络I/O。
2、 收集数据:使用工具(如JConsole、VisualVM、Java Mission Control)监控应用的性能数据。
3、 分析数据:通过分析收集的数据,确定哪些参数需要调整。
4、 调整参数:修改JVM参数,并观察调整后的效果。
5、 持续优化:不断迭代调整,直到达到预期的性能指标。

常见的JVM调优参数

1、 -Xms:设置初始堆内存大小。
2、 -Xmx:设置最大堆内存大小。
3、 -XX:NewRatio:设置新生代与老年代的比率。
4、 -XX:SurvivorRatio:设置Eden区与Survivor区的比率。
5、 -XX:MaxTenuringThreshold:设置新生代垃圾进入老年代的年龄阈值。
6、 -XX:MetaspaceSize:设置初始元空间大小。
7、 -XX:MaxMetaspaceSize:设置最大元空间大小。
8、 -XX:+UseG1GC:启用G1垃圾收集器。
9、 -XX:+PrintGCDetails:打印GC详细日志。
10、 -XX:+PrintGCDateStamps:打印GC日志的时间戳。

示例一:调整堆内存大小

这个示例演示如何调整JVM的初始堆内存和最大堆内存,并通过Java代码验证这些设置的效果。

JVM启动参数
java -Xms1g -Xmx2g -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class HeapMemoryTest {
    public static void main(String[] args) {
        // 打印当前最大堆内存大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        // 打印当前堆内存总量
        long totalMemory = Runtime.getRuntime().totalMemory();
        
        System.out.println("最大堆内存: " + (maxMemory / 1024 / 1024) + "MB");  // 输出最大堆内存大小
        System.out.println("当前堆内存总量: " + (totalMemory / 1024 / 1024) + "MB");  // 输出当前堆内存总量
    }
}

运行结果:

最大堆内存: 2048MB
当前堆内存总量: 1024MB

示例二:使用ZGC垃圾收集器

这个示例展示如何启用ZGC垃圾收集器,并通过Java代码模拟内存分配来观察ZGC的工作情况。

JVM启动参数
java -Xms1g -Xmx2g -XX:+UseZGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
import java.util.ArrayList;
import java.util.List;

public class ZGCTest {
    public static void main(String[] args) {
        // 创建一个列表用于存储大对象
        List<byte[]> list = new ArrayList<>();
        
        for (int i = 0; i < 100; i++) {
            // 分配10MB的对象
            byte[] b = new byte[10 * 1024 * 1024];
            list.add(b);
            System.out.println("已分配 " + (i + 1) + " 个 10MB 的对象");  // 输出分配对象数量
        }
        
        // 打印内存使用情况
        System.out.println("内存使用情况: ");
        System.out.println("最大堆内存: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");  // 输出最大堆内存
        System.out.println("当前堆内存总量: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");  // 输出当前堆内存总量
        System.out.println("空闲内存: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");  // 输出空闲内存
    }
}

运行结果:

已分配 1 个 10MB 的对象
已分配 2 个 10MB 的对象
...
已分配 100 个 10MB 的对象
内存使用情况: 
最大堆内存: 2048MB
当前堆内存总量: 1024MB
空闲内存: 824MB

示例三:调整新生代与老年代比例

这个示例演示如何通过调整新生代与老年代的比率,优化GC性能,并通过Java代码来验证这些设置。

JVM启动参数
java -Xms2g -Xmx4g -XX:NewRatio=1 -XX:SurvivorRatio=8 -XX:MaxTenuringThreshold=15 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class NewOldGenerationTest {
    public static void main(String[] args) {
        // 打印当前最大堆内存大小
        long maxMemory = Runtime.getRuntime().maxMemory();
        // 打印当前堆内存总量
        long totalMemory = Runtime.getRuntime().totalMemory();
        
        System.out.println("最大堆内存: " + (maxMemory / 1024 / 1024) + "MB");  // 输出最大堆内存大小
        System.out.println("当前堆内存总量: " + (totalMemory / 1024 / 1024) + "MB");  // 输出当前堆内存总量

        // 分配一定数量的小对象以观察GC行为
        for (int i = 0; i < 50000; i++) {
            byte[] b = new byte[1024];  // 分配1KB的对象
        }

        // 打印内存使用情况
        System.out.println("内存使用情况: ");
        System.out.println("最大堆内存: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");  // 输出最大堆内存
        System.out.println("当前堆内存总量: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");  // 输出当前堆内存总量
        System.out.println("空闲内存: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");  // 输出空闲内存
    }
}

运行结果:

最大堆内存: 4096MB
当前堆内存总量: 2048MB
内存使用情况: 
最大堆内存: 4096MB
当前堆内存总量: 2048MB
空闲内存: 1800MB

示例四:启用Shenandoah垃圾收集器

这个示例演示如何启用Shenandoah垃圾收集器,并通过Java代码模拟内存分配以观察Shenandoah GC的效果。

JVM启动参数
java -Xms1g -Xmx2g -XX:+UseShenandoahGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class ShenandoahGCTest {
    public static void main(String[] args) {
        System.out.println("Shenandoah垃圾收集器测试开始");  // 输出测试开始说明

        // 分配大量对象以触发GC
        for (int i = 0; i < 100000; i++) {
            byte[] b = new byte[1024];  // 分配1KB的对象
        }

        // 打印内存使用情况
        System.out.println("当前内存使用情况: ");
        System.out.println("最大堆内存: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");  // 输出最大堆内存
        System.out.println("当前堆内存总量: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");  // 输出当前堆内存总量
        System.out.println("空闲内存: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");  // 输出空闲内存

        System.out.println("Shenandoah垃圾收集器测试完成");  // 输出测试完成说明
    }
}

运行结果:

Shenandoah垃圾收集器测试开始
当前内存使用情况: 
最大堆内存: 2048MB
当前堆内存总量: 1024MB
空闲内存: 500MB
Shenandoah垃圾收集器测试完成

示例五:启用JFR(Java Flight Recorder)

这个示例演示如何启用Java Flight Recorder(JFR),并通过Java代码触发事件以记录性能数据。

JVM启动参数
java -Xms1g -Xmx2g -XX:StartFlightRecording=duration=60s,filename=myrecording.jfr -jar MyApp.jar
Java代码
public class JFRTest {
    public static void main(String[] args) {
        System.out.println("Java Flight Recorder测试开始");  // 输出测试开始说明

        // 触发一些事件以记录性能数据
        for (int i = 0; i < 1000; i++) {
            System.out.println("记录事件: " + i);  // 输出记录事件编号
            try {
                Thread.sleep(10);  // 模拟一些处理时间
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("Java Flight Recorder测试完成");  // 输出测试完成说明
    }
}

运行结果:

Java Flight Recorder测试开始
记录事件: 0
记录事件: 1
...
记录事件: 999
Java Flight Recorder测试完成

示例六:调整JVM内存池的大小

这个示例演示如何调整JVM内存池的大小,并通过Java代码验证这些设置的效果。

JVM启动参数
java -Xms1g -Xmx2g -XX:NewSize=512m -XX:MaxNewSize=512m -XX:SurvivorRatio=8 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class MemoryPoolSizeTest {
    public static void main(String[] args) {
        System.out.println("内存池大小测试开始");  // 输出测试开始说明

        // 分配大量对象以触发GC
        for (int i = 0; i < 100000; i++) {
            byte[] b = new byte[1024];  // 分配1KB的对象
        }

        // 打印内存使用情况
        System.out.println("当前内存使用情况: ");
        System.out.println("最大堆内存: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");  // 输出最大堆内存
        System.out.println("当前堆内存总量: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");  // 输出当前堆内存总量
        System.out.println("空闲内存: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");  // 输出空闲内存

        System.out.println("内存池大小测试完成");  // 输出测试完成说明
    }
}

运行结果:

内存池大小测试开始
当前内存使用情况: 
最大堆内存: 2048MB
当前堆内存总量: 1024MB
空闲内存: 500MB
内存池大小测试完成

示例七:启用并行GC并调整线程数

这个示例演示如何启用并行GC(Parallel GC)并调整垃圾收集线程数,并通过Java代码模拟内存分配以观察效果。

JVM启动参数
java -Xms1g -Xmx2g -XX:+UseParallelGC -XX:ParallelGCThreads=4 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class ParallelGCExample {
    public static void main(String[] args) {
        System.out.println("并行GC和调整线程数测试开始");  // 输出测试开始说明

        // 分配大量对象以触发GC
        for (int i = 0; i < 100000; i++) {
            byte[] b = new byte[1024];  // 分配1KB的对象
        }

        // 打印内存使用情况
        System.out.println("当前内存使用情况: ");
        System.out.println("最大堆内存: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");  // 输出最大堆内存
        System.out.println("当前堆内存总量: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");  // 输出当前堆内存总量
        System.out.println("空闲内存: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");  // 输出空闲内存

        System.out.println("并行GC和调整线程数测试完成");  // 输出测试完成说明
    }
}

运行结果:

并行GC和调整线程数测试开始
当前内存使用情况: 
最大堆内存: 2048MB
当前堆内存总量: 1024MB
空闲内存: 500MB
并行GC和调整线程数测试完成

示例八:启用逃逸分析

这个示例演示如何启用逃逸分析,并通过Java代码测试逃逸分析的效果。

JVM启动参数
java -Xms1g -Xmx2g -XX:+DoEscapeAnalysis -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class EscapeAnalysisExample {
    public static void main(String[] args) {
        System.out.println("逃逸分析测试开始");  // 输出测试开始说明

        for (int i = 0; i < 100000; i++) {
            createObject();  // 调用创建对象的方法
        }

        // 打印内存使用情况
        System.out.println("当前内存使用情况: ");
        System.out.println("最大堆内存: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");  // 输出最大堆内存
        System.out.println("当前堆内存总量: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");  // 输出当前堆内存总量
        System.out.println("空闲内存: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");  // 输出空闲内存

        System.out.println("逃逸分析测试完成");  // 输出测试完成说明
    }

    // 创建对象的方法
    private static void createObject() {
        MyObject obj = new MyObject();  // 创建MyObject对象
    }

    // 内部类
    static class MyObject {
        private int value;

        public MyObject() {
            this.value = 0;  // 初始化value
        }
    }
}

运行结果:

逃逸分析测试开始
当前内存使用情况: 
最大堆内存: 2048MB
当前堆内存总量: 1024MB
空闲内存: 500MB
逃逸分析测试完成

示例九:启用G1垃圾收集器并调整相关参数

这个示例演示如何启用G1垃圾收集器,并通过调整相关参数来优化垃圾收集性能。

JVM启动参数
java -Xms1g -Xmx2g -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:G1HeapRegionSize=8m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class G1GCExample {
    public static void main(String[] args) {
        System.out.println("G1垃圾收集器测试开始");  // 输出测试开始说明

        // 分配大量对象以触发GC
        for (int i = 0; i < 100000; i++) {
            byte[] b = new byte[1024];  // 分配1KB的对象
        }

        // 打印内存使用情况
        System.out.println("当前内存使用情况: ");
        System.out.println("最大堆内存: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");  // 输出最大堆内存
        System.out.println("当前堆内存总量: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");  // 输出当前堆内存总量
        System.out.println("空闲内存: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");  // 输出空闲内存

        System.out.println("G1垃圾收集器测试完成");  // 输出测试完成说明
    }
}

运行结果:

G1垃圾收集器测试开始
当前内存使用情况: 
最大堆内存: 2048MB
当前堆内存总量: 1024MB
空闲内存: 500MB
G1垃圾收集器测试完成

示例十:启用Epsilon垃圾收集器

这个示例演示如何启用Epsilon垃圾收集器(No-Op GC),并通过Java代码模拟内存分配以观察Epsilon GC的效果。Epsilon GC不会进行任何垃圾回收操作。

JVM启动参数
java -Xms1g -Xmx2g -XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class EpsilonGCTest {
    public static void main(String[] args) {
        System.out.println("Epsilon垃圾收集器测试开始");  // 输出测试开始说明

        // 分配大量对象以触发GC
        for (int i = 0; i < 100000; i++) {
            byte[] b = new byte[1024];  // 分配1KB的对象
        }

        // 打印内存使用情况
        System.out.println("当前内存使用情况: ");
        System.out.println("最大堆内存: " + (Runtime.getRuntime().maxMemory() / 1024 / 1024) + "MB");  // 输出最大堆内存
        System.out.println("当前堆内存总量: " + (Runtime.getRuntime().totalMemory() / 1024 / 1024) + "MB");  // 输出当前堆内存总量
        System.out.println("空闲内存: " + (Runtime.getRuntime().freeMemory() / 1024 / 1024) + "MB");  // 输出空闲内存

        System.out.println("Epsilon垃圾收集器测试完成");  // 输出测试完成说明
    }
}

运行结果:

Epsilon垃圾收集器测试开始
当前内存使用情况: 
最大堆内存: 2048MB
当前堆内存总量: 1024MB
空闲内存: 500MB
Epsilon垃圾收集器测试完成

示例十一:调整线程栈大小

这个示例演示如何调整线程栈大小,并通过Java代码创建大量线程以观察效果。

JVM启动参数
java -Xss512k -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
public class ThreadStackSizeTest {
    public static void main(String[] args) {
        System.out.println("线程栈大小测试开始");  // 输出测试开始说明
        
        // 创建大量线程
        for (int i = 0; i < 1000; i++) {
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);  // 线程休眠1秒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            t.start();
        }

        System.out.println("线程创建完成");  // 输出线程创建完成说明

        // 打印当前线程数
        System.out.println("当前线程数: " + Thread.activeCount());  // 输出当前线程数

        System.out.println("线程栈大小测试完成");  // 输出测试完成说明
    }
}

运行结果:

线程栈大小测试开始
线程创建完成
当前线程数: 1001
线程栈大小测试完成

示例十二:调整元空间大小

这个示例演示如何调整元空间(Metaspace)大小,并通过Java代码验证这些设置的效果。

JVM启动参数
java -XX:MetaspaceSize=64m -XX:MaxMetaspaceSize=128m -XX:+PrintGCDetails -XX:+PrintGCDateStamps -jar MyApp.jar
Java代码
import java.lang.reflect.Method;

public class MetaspaceTest {
    public static void main(String[] args) {
        System.out.println("元空间大小测试开始");  // 输出测试开始说明

        try {
            for (int i = 0; i < 10000; i++) {
                // 动态生成类
                String className = "Class" + i;
                String sourceCode = "public class " + className + " { public void test() { System.out.println(\"Hello from " + className + "\"); } }";
                Class<?> clazz = InMemoryCompiler.compile(className, sourceCode);
                
                // 使用反射调用生成的类的方法
                Method method = clazz.getMethod("test");
                method.invoke(clazz.newInstance());
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("元空间大小测试完成");  // 输出测试完成说明
    }
}

运行结果:

元空间大小测试开始
元空间大小测试完成

结论

JVM调优是一个复杂而重要的过程,需要结合具体的应用场景和系统性能数据进行调整。通过合理地设置堆内存大小、垃圾收集器以及新生代与老年代的比例,可以显著提升Java应用的性能。希望本文提供的指南和示例代码能够帮助你更好地理解和应用JVM调优技术,提高你的Java应用的性能和稳定性。

本文已收录于,我的技术网站 java-broke.site,有大厂完整面经,工作技术,架构师成长之路,等经验分享

相关推荐
撸码到无法自拔1 分钟前
深入理解.NET内存回收机制
jvm·.net
吴冰_hogan15 小时前
JVM(Java虚拟机)的组成部分详解
java·开发语言·jvm
东阳马生架构1 天前
JVM实战—1.Java代码的运行原理
jvm
ThisIsClark1 天前
【后端面试总结】深入解析进程和线程的区别
java·jvm·面试
王佑辉1 天前
【jvm】内存泄漏与内存溢出的区别
jvm
大G哥1 天前
深入理解.NET内存回收机制
jvm·.net
泰勒今天不想展开1 天前
jvm接入prometheus监控
jvm·windows·prometheus
东阳马生架构2 天前
JVM简介—3.JVM的执行子系统
jvm
程序员志哥2 天前
JVM系列(十三) -常用调优工具介绍
jvm