这篇文章将详细介绍如何进行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,有大厂完整面经,工作技术,架构师成长之路,等经验分享