三分钟带你弄清楚JVM调优到底是什么

本文介绍了JVM调优的基本概念,通过一个测试代码展示了如何通过调整JVM参数(如-Xmx, -Xms, -Xmn, XX:SurvivorRatio等)来优化内存分配,减少Full GC次数,从而避免应用卡顿。通过实战演练,分析了对象分配、新生代与老年代的关系,以及GC回收时机。优化策略包括增大新生代大小和调整Eden区与Survivor区比例,最终实现降低GC频率,提高系统性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

相信很多人在学习或者面试的过程中,都会听到很多人提到JVM,以及JVM调优,那么JVM调优,到底调的是什么呢?

JVM到底是什么

JVM的相关概念,相信网上都有很多介绍,简单来说,JVM就是一个java程序的运行环境。我们编写的代码可以在这个环境中运行。

项目准备

很多人的项目运行的时候,并没有考虑过对JVM的相关参数进行设置,而是采用的默认设置。下面,先通过一个简单的代码和一些简单的参数,来看一下JVM可以设置哪些参数,这些参数有什么作用,为什么我们要调整JVM的默认参数。

首先完成下面的准备工作:
在IDEA中设置JVM相关参数,将下面的参数直接复制到VM options中
在这里插入图片描述
在这里插入图片描述

-Xmx30m -Xms30m -Xmn10m -XX:SurvivorRatio=8 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC

导入如下测试代码

public class Test {

    public static void main(String[] args) throws InterruptedException {
        TimeUnit.SECONDS.sleep(10);
        for (int i = 0; i < 20; i++) {
            byte[] arr1 = new byte[2 * 1024 * 1024];
            arr1 = new byte[2 * 1024 * 1024];
            arr1 = new byte[2 * 1024 * 1024];
            byte[] arr2 = new byte[1512 * 1024];
            arr1 = new byte[2 * 1024 * 1024];
            TimeUnit.SECONDS.sleep(1);
        }
    }
}

利用jstat -gcutil查看整个代码执行过程中,GC到底怎么运作的。
想要知道我们刚才这些操作,到底做了些什么,需要首先了解一些理论知识。(如果对JVM的相关概念理解比较清晰,可以跳过这部分内容)

  • 对象的分配与回收
    在java代码中,绝大部分的对象是分配在堆内存里。比如上面代码里的new byte[_1MB]。就是在堆内存中申请了1M左右的大小,用来存储byte数组。
    当内存中的对象全部填满以后,就需要释放那些废弃对象所占用的内存空间,这就是GC。在上面的代码中,byte[] arr = new byte[_1MB],创建了一个对象数组,随后arr又指向了一个新的new byte[_1MB]。这时候,刚才创建的数组就失去了引用,成为了内存中的垃圾。在代码的最后,arr的引用置为了null。此时创建的3个byte数组就均为垃圾对象。需要在GC的时候被释放。

  • 什么是新生代,什么是老年代
    新生代往往存放的是系统中出现比较短暂的,很快会失去引用的对象。老年代往往存放的是系统中生命周期比较长的对象。对象在刚开始创建的时候都会存放在新生代,随着不断的GC,一些生命周期较长的对象就会转移到老年代。

  • GC回收时机与耗时
    JVM中的GC分为新生代回收和老年代回收,简单理解,当年轻代的内存空间不足以容纳新创建的对象时,就会触发一次GC操作。而GC是需要耗费时间的,并且在GC的过程中,会短暂的暂停我们的应用程序。因此,GC越频繁,耗时越久,我们的应用就会越发的卡顿。
    在理解了上面这些概念以后,我们再来回顾这段代码。其实这段代码就是不断的创建新的byte数组,再不断的修改对象的引用,制造垃圾对象,从而不断触发GC。

  • JVM相关参数
    上面的VM options中,对JVM进行了若干参数设置。

Xmx :设置最大堆内存大小 Xms:设置最小堆内存大小 Xmn:设置新生代堆内存大小
XX:SurvivorRatio :设置新生代中Eden与Survivor区的比例
XX:+UseParNewGC 与 XX:+UseConcMarkSweepGC 分别指定新生代与老年代的垃圾回收器

  • jstat
    jstat是jdk中自带的一个工具,利用他可以很方便的观测程序运行过程中的一些状态。

实战演练

在介绍完上述理论知识后,我们运行上面的测试代码,并在命令行中,输入jps找到我们项目的端口号在这里插入图片描述

156,执行 jstat -gcutil 156 1000 来查看项目运行过程中的GC情况
在这里插入图片描述
上图打印的是项目的GC过程,我们首先关注两个核心的值YGCFGC。这两个参数的含义是项目运行过程中,新生代GC的次数与老年代GC的次数。可以发现,随着代码的执行,老年代GC次数在不断增加,而老年代的GC速度往往要比新生代慢很多。因此,这段代码在执行的过程中,会因为GC频繁而持续的卡顿。

GC频繁分析

GC为什么会发生的如此频繁,我们都知道老年代往往存放的是生命周期较长的对象,而上述代码里的变量都是在for循环内的局部变量,随着一次for循环的结束,这些对象都会变成垃圾对象,那为什么老年代会不断有对象产生呢?
其实是因为对象一开始都会在新生代中创建,而一旦新生代内存满了,并且在一次YGC之后,还是无法存放新对象,这时候就会将一部分对象转移到老年代,随着程序的不断运行,老年代里的对象积累越来越多,最终就会触发FullGC,回收老年代里的垃圾对象。

如何优化

优化的方法有两点

  • 调大新生代大小

首先,因为新生代的内存不足,导致对象被迫被转移到老年代。因此,我们可以设置新生代的内存大小,将其稍微设置大一些。利用Xmn调整新生代大小。我们设置-Xmn15m,再来观察GC的表现
在这里插入图片描述
可以发现,随着新生代内存大小的调整,FGC的次数较之前有了明显的下降,YGC的次数也不频繁,说明调整新生代大小可以让对象尽可能停留在新生代。但是我们发现FGC还没有完全消失,因此,我们可以采用第二个优化方案。

  • 调整新生代中Eden区与Survivor区的比例

通过上面的GC我们可以看到,S0与S1几乎占用率达到了100%,因此我们可以调整Eden区与S0、S1区域的内存比例,让对象尽可能的停留在Survivor区,等待下一次YGC,而不必进入老年代。利用XX:SurvivorRatio 参数调整新生代的内存分配比例,我们设置 -XX:SurvivorRatio=4,再次观察一次GC表现。
在这里插入图片描述
这次我们发现,通过增大新生代的大小,并调整新生代的内存分配区间,FGC一直保持在0,并且老年代的内存也没有一直增长。我们仅仅通过JVM相关参数的调整,就实现了一次JVM调优,成功将项目的GC频率大大降低,避免了系统的卡顿。

总结

本次示例代码,主要是带大家熟悉,什么是JVM调优,为什么我们需要对JVM进行调优,调优前后,我们能够改善哪些问题。想要真正对我们的生产环境进行相关调优,并且熟练的掌握JVM相关的知识与技术,还需要学习很多相关知识。后续我会就本文所涉及到的相关理论知识写一些理论型的博文,大家对不懂的概念也可以进行百度查阅。
文中如果有不正确的地方还望大家指正。

<think>好的,我现在需要处理用户关于JVM的问题。首先,用户提供了详细的系统级指令,要求生成相关问题和回答,并且回答要结构清晰,引用站内资料。用户引用了四个参考资料,我需要确保在回答中正确引用这些来源。 首先,我需要理解JVM的基本概念。根据引用[1],JVM涉及GC、内存管理、线程等方面。接下来,用户的问题是想了解的概念和方法,所以需要分步骤解释。 我应该先概述JVM的目标,比如提升性能、减少延迟等,然后分点方法。比如内存管理部分,需要提到堆内存设置,年轻代和老年代的比例,使用工具如jstat和jmap,引用[3]提到了jps和jinfo,可能相关。然后是GC,包括选择合适的垃圾回收器,比如G1,引用[4]提到G1的适用场景和方法。线程部分,需要涉及线程池配置和锁化,可能引用[2]中的面试题相关的内容。 还需要考虑的步骤,比如分析现状、设定目标、选择策略、验证结果。这部分可能需要结合引用[1]中的实战指南。此外,工具部分要列举常用的命令行工具和可视化工具,引用[3]和引用[4]中的文档链接可以作为参考资料。 生成相关问题的时候,要基于用户提供的引用内容,比如GC的具体方法、内存溢出处理、G1参数、线程案例等,确保每个问题都能在提供的资料中找到相关答案。 最后,需要检查引用标识是否正确添加,比如在提到GC时引用[2][4],内存管理引用[1][3]等。确保回答符合用户要求的中文、LaTeX格式正确,以及结构清晰分步骤明。</think>### JVM性能指南 #### 一、JVM核心目标 1. **降低GC频率与耗时**:减少因垃圾回收导致的应用程序停顿时间[^2][^4] 2. **合理分配内存资源**:根据应用特征设置堆内存、元空间等参数,避免内存溢出(OOM)[^1][^3] 3. **化线程管理**:控制线程创建/销毁开销,避免死锁和资源争用 #### 二、关键方法 1. **内存管理** - 设置堆内存参数: ```bash -Xms4g -Xmx4g # 初始堆=最大堆,避免动态扩展 -XX:NewRatio=2 # 年轻代:老年代=1:2 ``` - 使用工具监控内存状态: ```bash jstat -gcutil <pid> 1000 # 每秒采集GC数据 jmap -histo <pid> # 对象分布统计 ``` 2. **GC策略** - 选择GC算法: | GC类型 | 适用场景 | 启动参数 | |--------|----------|----------| | G1 | 大内存低延迟 | -XX:+UseG1GC | | ZGC | 超大堆内存 | -XX:+UseZGC | - G1关键参数: ```bash -XX:MaxGCPauseMillis=200 # 目标暂停时间 -XX:G1HeapRegionSize=32m # 区域大小 ``` 3. **线程与锁化** - 线程池参数整: $$ 最佳线程数 = \frac{CPU核心数 \times (1 + 等待时间/计算时间)}{1} $$ - 锁检测工具: ```bash jstack <pid> | grep "BLOCKED" -A 3 # 检测阻塞线程 ``` #### 三、实施步骤 1. **性能瓶颈分析** - 使用`jvisualvm`监控堆内存和CPU使用 - 通过GC日志分析回收效率: ```bash -Xloggc:/path/to/gc.log -XX:+PrintGCDetails ``` 2. **策略验证** - 压力测试工具: ```java // JMH基准测试示例 @Benchmark public void testMethod() { // 被测逻辑 } ``` #### 四、工具链参考 | 工具类型 | 代表工具 | 功能 | |----------|----------|------| | 命令行 | jstat/jmap | 内存分析[^3] | | 可视化 | VisualVM | 综合监控 | | APM | SkyWalking | 分布式追踪 |
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值