jvm调优技巧 - 内存抖动 、Xms和Xmx参数为什么要设置相同的值

前言

在网上看到大部分的帖子都有介绍性能调优的案例,其中有一项就是告诉你 XmsXmx 参数一定要设置成相同的,这样就可以达到优化的目的,就像这样

-Xms1024m -Xmx1024m

但是却没说为什么要这么设置,那么这篇文章就是来告诉你这样设置的目的。底层做了哪些事情。

jvm性能调优的最基本条件

    首先我们要知道,垃圾回收器(GC)在回收内存空间时候,所有的工作线程都会暂停,待回收工作完成后,工作线程才会继续运行。如果GC太频繁,工作线程的效率和响应时间肯定会受影响,所以,jvm性能调优的最基本条件就是要尽可能地减少垃圾回收的次数
在这里插入图片描述
那么说到这里,jvm性能调优跟内存抖动有什么关系呢?跟Xms和Xmx又有什么关联呢?往下看,你就会知道,他们都是息息相关的!

Xms和Xmx参数为什么要设置相同的值

首先来看看Xms和Xmx的作用

  • -Xms:堆内存的最小Heap值,默认为物理内存的1/64,但小于1G。默认当空余堆内存大于指定阈值时,JVM会减小heap的大小到-Xms指定的大小。比如我电脑内存是16G,那么默认的Xms就是 16*1024/64=256m

  • -Xmx:堆内存的最大Heap值,默认为物理内存的1/4。默认当空余堆内存小于指定阈值时,JVM会增大Heap到-Xmx指定的大小。比如我电脑内存是16G,那么默认的Xmx就是16/4=4G

Xms和Xmx不同值产生的内存抖动

其实Xms和Xmx参数设置成相同的值就是为了防止 内存抖动, 如果将Xms和Xmx参数设置为不同的值,比如将堆内存的初始值设置为256m,将最大值设为 4096m

-Xms256m -Xmx4096m

当项目在运行 过程中,

  • 默认空余堆内存小于40%时,就会进行扩容,注意这里是提前扩容的,不是达到256m的时候才扩容,跟HashMap类似,博主猜测里面应该也是有一个负载因子类似的参数;只要你一直往塞对象,堆内存就会向操作系统申请内存,基本上是要多少就申请多少,一直到达最大内存(Xmx)

  • 空余堆内存大于70%时,JVM会减少堆直到-Xms的最小限制。
    在这里插入图片描述

代码示例内存抖动

以下代码测试内存抖动,在项目一开始运行时,默认的的堆内存大小为256m(mac电脑计算内存的方式会有差异,所以显示的实际内存为245m),运行项目后,每隔2秒就会往list对象插入一个占用100M内存的对象,因为初始内存只有245m,所以每次都需要向操作系统申请新的内存来给对象使用;Runtime.getRuntime().totalMemory()这个方法会打印向操作系统申请到的内存总数。

package com.gc;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 *
 *在网上看到大部分的帖子都有介绍性能调优的案例,其中有一项就是告诉你 **`Xms`** 和 **`Xmx`** 参数一定要设置成相同的,这样就可以达到优化的目的,就像这样
 *-Xms1024m -Xmx1024m
 *但是却没说为什么要这么设置,那么这就是来告诉你这样设置的目的
 */
public class XmxAndXms {
    public static void main(String[] args) throws InterruptedException {

        XmxAndXms test = new XmxAndXms();
        // 添加内存前先打印默认的堆内存情况
        test.showHeapSpace();

        // 开始添加内存
        test.addMemory();
    }

    /**
     * 展示堆空间大小
     */
    public void showHeapSpace(){
        // 每次增加对象时,totalMemory都会增大100倍
        System.out.print("当前堆内存:" + Runtime.getRuntime().totalMemory()/1024/1024 + "M") ;
        System.out.print(",最大内存:"+Runtime.getRuntime().maxMemory()/1024/1024+ "M");
        System.out.print(",空闲内存:" + Runtime.getRuntime().freeMemory()/1024/1024+ "M");
        System.out.println();
    }

    /**
     * 每2秒钟往堆内存添加一个占用100m内存的对象
     * @throws InterruptedException
     */
    public void addMemory() throws InterruptedException {
        List<byte[]> list = new ArrayList<>();
        for (int j = 0; j < 10; j++) {
            System.out.print("第"+(j+ 1)+"次添加内存");
            // 每2秒增加100M内存
            list.add(new byte[1024 * 1024 * 100]);
            // 添加完后展示内存大小
            this.showHeapSpace();
            TimeUnit.SECONDS.sleep(2);
        }
    }
}

打印结果如下

当前堆内存:245M,最大内存:3641M,空闲内存:240M
第1次添加内存当前堆内存:245M,最大内存:3641M,空闲内存:140M
第2次添加内存当前堆内存:346M,最大内存:3641M,空闲内存:138M
第3次添加内存当前堆内存:446M,最大内存:3641M,空闲内存:138M
第4次添加内存当前堆内存:547M,最大内存:3641M,空闲内存:139M
第5次添加内存当前堆内存:647M,最大内存:3641M,空闲内存:139M
第6次添加内存当前堆内存:748M,最大内存:3641M,空闲内存:140M
第7次添加内存当前堆内存:848M,最大内存:3641M,空闲内存:140M
第8次添加内存当前堆内存:949M,最大内存:3641M,空闲内存:141M
第9次添加内存当前堆内存:1049M,最大内存:3641M,空闲内存:141M
第10次添加内存当前堆内存:1150M,最大内存:3641M,空闲内存:142M

我们主要看当前堆内存的值,可以看到一开始是245M,当我添加了100M的对象后,每2秒钟就多了 100M左右,每次多出来的100M内存就是jvm向操作系统申请的内存;网上有的帖子说是jvm向操作系统挖过来的内存,其实意思都一样;每次申请内存时的时序图如下
在这里插入图片描述

为什么要防止内存抖动

上面已经说过,Xms和Xmx参数设置成相同的值就是为了防止 内存抖动,为什么要防止内存抖动呢? 是因为每次jvm向操作系统申请内存时都需要一定的开销,

就比如我上街看到一个很喜欢的商品,想要买下这个商品,但是我身上的钱不够了, 于是我向你借钱,你同意了,然后你掏出钱包, 把钱拿出来,然后再交给我,你拿钱给我的这整个过程也是需要时间的所以我为了省下这个拿钱的时间,我在一开始出门的时候就直接带上足够的钱,这样就可以省下借钱的时间了;

jvm也一样,在启动项目的时候就直接设置好足够的堆内存大小,避免不够用的时候又要跟操作系统申请;省下了申请内存的开销,就达到了优化的目的。

下次有人问你为什么Xms和Xmx参数设置成相同的值,你就把我这篇文章推给他,这样它就明白了。

<think>好的,用户之前问过如何选择适合开发者的Windows 11版本,现在又转向了Spring Boot的JVM内存配置。我需要先理解他的需求。他可能是一个开发者,正在使用Spring Boot开发应用,遇到了内存配置的问题,想知道如何计算合适的堆内存大小。 首先,用户的问题是关于如何确定Spring Boot应用的JVM内存大小。这可能涉及到性能或者应用出现内存不足的问题。我需要考虑不同的因素,比如应用的类型、数据量、并发情况等。 接下来,我需要回忆JVM内存管理的相关知识。堆内存分为新生代老年代,垃圾回收机制会影响内存的使用。XmsXmx参数设置初始最大堆内存,但如何确定这些呢? 用户可能需要具体的步骤或公式来计算,但实际应用中并没有固定的公式,更多是基于监控分析。可能需要指导他使用工具如VisualVM、JConsole或者Prometheus+Grafana来监控内存使用情况。 然后,要考虑常见的错误,比如设置过小的堆内存导致OOM,或者过大的堆内存引发长时间的GC停顿。还要提到一些经验法则,比如分配系统内存的50%-70%给JVM,但具体还要看实际情况。 可能用户没有提到的是,他是否在容器化环境中运行,比如Docker,这时候内存限制会影响JVM的行为,需要特别处理。不过用户的问题中没有提到容器,可能暂时不需要深入这部分,但可以简要提及作为补充。 另外,需要解释一些关键参数,如XmxXms、Metaspace大小,以及垃圾回收器的选择对内存的影响。例如,G1GCZGC的不同内存需求。 用户可能需要一个逐步的指导:如何监控内存,如何分析日志,如何参数,然后测试验证。同时,提醒他在不同的环境(开发、测试、生产)中可能需要不同的配置。 还要注意他是否了解GC日志的分析,是否需要解释如何启用GC日志,以及如何根据日志中的Full GC频率持续时间来整堆大小。 最后,总结建议,给出一个推荐的步骤:监控现有应用的内存使用,根据峰波动Xmx,留出安全余量,并定期复查。同时,强没有万能公式,需要根据具体情况整。</think>--- ### **一、核心思路:动态监控 + 经验公式** JVM内存不足会导致频繁GC甚至OOM崩溃,过大则浪费资源。需结合**实际内存使用峰**与**系统可用资源**综合计算。以下是分步计算方法: --- ### **二、关键参数与工具** 1. **基础参数** - `-Xms`: 初始堆内存(建议与`-Xmx`相同,避免运行时扩容抖动- `-Xmx`: 最大堆内存(核心整目标) - `-XX:MaxMetaspaceSize`: 元空间上限(默认无限制,建议设为256M-1G) 2. **监控工具** - **VisualVM/JConsole**:图形化查看堆内存波动 - **jstat**:命令行实时统计GC与内存占用 ```bash jstat -gcutil <pid> 1000 # 每秒输出一次GC内存利用率 ``` - **GC日志分析**(添加参数): ```bash -Xlog:gc*:file=gc.log:time,uptime:filecount=5,filesize=10M ``` --- ### **三、计算步骤** #### **步骤1:确定系统可用内存** - 物理内存总量:例如服务器内存为8GB - **推荐分配范围**: $$ \text{JVM最大堆内存} \leq \text{系统可用内存} \times 70\% $$ 若系统独占运行Spring Boot,可分配: $$ 8\text{GB} \times 70\% = 5.6\text{GB} \quad (\text{即} -Xmx5632m) $$ #### **步骤2:监控实际内存需求** - **运行压力测试**:模拟真实用户请求,观察堆内存 - **关键指标**: - **老年代使用量**(Old Generation Usage):决定`-Xmx`下限 - **Full GC频率**:若频繁触发(如每分钟1次以上),需增大堆内存 ```plaintext 示例监控结果: Eden区峰:800MB 老年代峰:2.1GB 元空间峰:280MB Full GC间隔:30分钟/次 ``` #### **步骤3:设置安全余量** 在监控到的峰基础上增加**20%-30%**缓冲,避免突发流量导致OOM: $$ Xmx = \text{老年代峰} \times 1.3 = 2.1\text{GB} \times 1.3 \approx 2.7\text{GB} $$ #### **步骤4:容器化环境特殊处理** 若部署在Docker/K8s: - 设置容器内存限制**高于`-Xmx`至少1GB**(预留空间给堆外内存、OS等) - 添加JVM参数适配容器内存: ```bash -XX:+UseContainerSupport -XX:MaxRAMPercentage=70.0 # 使用容器内存的70%作为堆内存 ``` --- ### **四、经验公式参考** | 场景 | 推荐堆内存范围 | 备注 | |-----------------------|----------------------|-----------------------------| | 小型单体应用(低并发) | 512MB - 2GB | 如内部管理后台 | | 微服务实例(中等并发) | 2GB - 4GB | 需配合APM工具监控 | | 高并发/大数据处理 | 4GB - 16GB | 避免单实例过大,先横向扩容 | | 内存计算型任务 | 根据数据量动态整 | 需实测内存与性能的平衡点 | --- ### **五、案例** #### **问题现象** Spring Boot应用频繁Full GC,日志报`OutOfMemoryError: Java heap space` #### **分析过程** 1. 通过`jstat`发现老年代占用持续达90% 2. GC日志显示Full GC后可用堆内存仅剩50MB 3. 压力测试期间堆峰达到2.8GB #### **解决方案** ```bash # 原参数 -Xmx2g # 修改后 -Xms3g -Xmx3g -XX:MaxMetaspaceSize=512m ``` 整后Full GC频率从10次/小时降至1次/小时。 --- ### **六、注意事项** 1. **避免盲目增大堆内存**:过大的堆会增加GC停顿时间(如G1GC的Mixed GC阶段) 2. **关注堆外内存**:Netty、缓存框架等可能占用大量堆外内存(通过`-XX:MaxDirectMemorySize`限制) 3. **定期复查**:业务增长后需重新评估内存需求 --- 通过以上方法,可精准平衡性能与资源成本。建议首次部署后持续监控至少24小时,覆盖业务高低峰周期。
评论 17
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

java叶新东老师

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值