JVM -Xmx内存设置超过物理内存问题思考

本文探讨了JVM的-Xmx参数是否可以超过物理内存,以及当内存接近物理内存耗尽时JVM的行为。通过实验验证,在Linux和Windows环境下,JVM可以设置较大的-Xmx值,但受限于操作系统和JVM自身限制。当物理内存耗尽,系统可能使用SWAP空间,导致性能下降。JVM的垃圾回收并不直接由物理内存大小触发,而是根据堆内存使用情况。测试中观察到,内存溢出和GC效率降低与物理内存接近饱和有关。
摘要由CSDN通过智能技术生成

问题提出

JVM是否可以设置-Xmx超过物理内存?如果设置,当内存使用接近物理内存后会发生什么现象?会发生GC吗?

理论思考

1. 是否可以设置-Xmx超过物理内存?

  • 可以。
  • JVM并不在启动时申请如此大的内存。适当的超出物理内存是允许的。

2. 是否可以将-Xmx设置的无限大?

  • 不可以。
  • JVM的一些垃圾回收算法,会使用到卡表(card tables)、 bitmap等,如果JVM检验到Heap区内存大到无法创建这些引用,或着直接超出操作系统可以预定的最大内存,会报错。
  • 这个具体的值跟操作系统、JVM版本、物理机内存等都有关系

3. 当物理机内存耗尽时,会发生什么现象?

  • 以Linux为例,如果 开启了Swap
    • 内存耗尽时会使用Swap,系统会出现抖动 (thrashing) 现象,系统性能会急剧下降
  • 如果未开启Swap,内存耗尽,或着 Swap 区也耗尽 时会发生什么?
    • 会发生无法分配内存

4. JVM在堆内存不足和物理内存耗尽时会发生什么?

  1. 持续分配对象,首先在Eden区上无法分配对象时,会先触发 YoungGC
    1. 在触发YoungGC前,如果老年代剩余空间小于年轻代现有所有对象,且小于历次进入老年代的平均大小时,会先触发老年代空间分配担保,进行FullGC
  2. 如果对象长期存活,或着GC后触发动态年龄判断,对象会进入老年代,如果老年代装满时,触发 FullGC
  3. 触发 FullGC 后会有以下几种情况
    1. 如果是刚启动时,由于JVM启动时会先使用一个较小的内存(默认1/64内存大小),当GC后内存不足且没有到达 -Xmx 最大值时,会发生 堆扩容
      1. 如果扩容时无法申请内存,将发生 JVM物理内存无法申请 错误
    2. 如果因启用 Swap 区后,系统性能下降,GC的时间持续太长后,可能会触发 OOM 超出GC开销限制 错误
      1. JVM如果花了98%的时间进行垃圾回收,而只得到2%可用的内存,频繁的进行内存回收(最起码已经进行了5次连续的垃圾回收)时,会报这个错误
    3. 如果在 FullGC 之后,未回收太多的空间,仍然无法放下对象时,会触发 OOM 堆内存溢出 错误

5. JVM会因为临近物理内存大小而发生GC吗

  1. 不会。
  2. JVM是否发生YoungGCFullGC始终与JVM堆内存的使用情况有关,与物理内存没有直接的关系
  3. 如果不考虑Swap,通常未到达物理内存大小时,JVM就因为无法扩容而崩溃了

问题验证

测试代码

不断往static list中添加一个10M的对象,让内存溢出

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;
public class TestGC {
    private static final List<TestGC> list = new LinkedList<>();
    public byte[] bytes = new byte[10*1024*1024];
    public static void main(String[] args) throws InterruptedException {
        // TimeUnit.SECONDS.sleep(15);
        long count = 0;
        for(;;){
            System.out.println(count++);
            list.add(new TestGC());
            // TimeUnit.MILLISECONDS.sleep(1000);
        }
    }
}

测试Xmx最大值

Windows
  • 测试环境

    • 操作系统 Window 10
    • JDK 1.8.0_241
    • 物理内存 16GB
  • java -Xmx100g TestGC

    • 正常启动
  • java -Xmx1400g TestGC

Error occurred during initialization of VM
Unable to allocate 2867200KB card tables for parallel garbage collection for the requested 1468006400KB heap.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.
  • java -Xmx1600g TestGC
Error occurred during initialization of VM
Unable to allocate 52428800KB bitmaps for parallel garbage collection for the requested 1677721600KB heap.
Error: Could not create the Java Virtual Machine.
Error: A fatal exception has occurred. Program will exit.

不在Windows下测试OOM情况,只测试是否可以启动

Linux
  • 测试环境

    • 操作系统:CentOS 7.9
    • JDK: openjdk 1.8.9_292
    • 物理内存:2GB
    • SWAP: 2GB
  • java -Xmx40t TestGC

    • 正常启动
  • java -Xmx87t TestGC

    • 报错
Error occurred during initialization of VM
Could not reserve enough space for 93415538688KB object heap

测试内存溢出

开启SWAP情况下
  • -Xmx 大于物理内存,但小于(物理内存+Swap)
    • java -Xmx3g TestGC
    • 在打印到 142 (内存接近1.5GB)时,速度开始明显下降
    • 最后打印到 295 (内存接近3GB)时,报错 OOM 堆内存溢出
...
293
294
295
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at TestGC.<init>(TestGC.java:7)
    at TestGC.main(TestGC.java:14)
  • -Xmx 大于物理内存,且大于(物理内存+SWAP)
    • java -Xmx8g TestGC
    • 在打印到 142 时,速度开始明显下降
    • 最后打印到 199 时,报错 JVM 无法分配内存
...
196
197
198
199
OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000006e2e95000, 1338490880, 0) failed; error='无法分配内存' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 1338490880 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /root/test-java/hs_err_pid3770.log
关闭SWAP情况下
  • 无论 java -Xmx3g TestGC 还是 java -Xmx8g TestGC 结果均如下
    • 最后打印到 89 时,报错 JVM 无法分配内存
...
83
84
85
86
87
88
89
OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000006f5c83000, 601100288, 0) failed; error='无法分配内存' (errno=12)
#
# There is insufficient memory for the Java Runtime Environment to continue.
# Native memory allocation (mmap) failed to map 601100288 bytes for committing reserved memory.
# An error report file with more information is saved as:
# /root/test-java/hs_err_pid3917.log

测试GC现象

  • 将代码中的注释放开
关闭Swap
  • 使用 jstat -gc PID 1000 10000 跟踪如下
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT   
1024.0 1024.0  0.0    0.0    8192.0   507.9    20480.0      0.0     4480.0 875.7  384.0   76.6       0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8192.0   507.9    20480.0    10240.0   4480.0 875.7  384.0   76.6       0    0.000   0      0.000    0.000
1024.0 1024.0  0.0    0.0    8256.0    0.0     30724.0    20743.0   4864.0 3003.3 512.0  287.0       1    0.001   1      0.001    0.002
1728.0 1728.0  0.0    0.0   13888.0  10240.0   34572.0    20743.0   4864.0 3003.6 512.0  287.0       2    0.001   2      0.003    0.004
1728.0 1728.0  0.0    0.0   13888.0  10240.0   34572.0    30983.0   4864.0 3004.0 512.0  287.0       3    0.003   2      0.003    0.006
3392.0 3392.0  0.0    0.0   27584.0  10240.0   68708.0    41223.1   4864.0 3004.0 512.0  287.0       4    0.005   3      0.004    0.009
3392.0 3392.0  0.0    0.0   27584.0  21022.0   68708.0    41223.1   4864.0 3004.0 512.0  287.0       4    0.005   3      0.004    0.009
...
33280.0 33280.0  0.0   30721.4 266496.0 230392.1  666044.0   624904.5  4864.0 3007.0 512.0  287.0      11    0.233   6      0.025    0.258
33280.0 33280.0  0.0   30721.4 266496.0 240632.1  666044.0   624904.5  4864.0 3007.0 512.0  287.0      11    0.233   6      0.025    0.258
33280.0 33280.0  0.0   30721.4 266496.0 250872.2  666044.0   624904.5  4864.0 3007.0 512.0  287.0      11    0.233   6      0.025    0.258
33280.0 33280.0  0.0   30721.4 266496.0 261112.2  666044.0   624904.5  4864.0 3007.0 512.0  287.0      11    0.233   6      0.025    0.258

并没有看到 在接近物理内存时的 GC,发生时机与预期一致

打开Swap
  • 现象与关闭时的类似
  • 在临近报错时,可以看到最后两次明显的GC时间变长,说明GC效率下降,系统变慢
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT  
73408.0 73408.0 71680.7 71683.3 587328.0 584592.5 1478424.0  1474827.5  4864.0 3027.2 512.0  287.0      14    1.836   7      0.029    1.865
73408.0 73408.0 71681.4 71683.3 587328.0 584592.5 1652572.0  1638667.7  4864.0 3027.2 512.0  287.0      14    1.836   7      0.029    1.865
73408.0 73408.0 71682.0 71683.3 587328.0 584592.5 1836964.0  1833228.0  4864.0 3027.2 512.0  287.0      14    1.836   7      0.029    1.865
73408.0 73408.0 71682.3  0.0   587328.0   0.0    1970136.0  1966351.2  4864.0 3027.2 512.0  287.0      14   22.270   8      0.029   22.299
73408.0 73408.0  0.0     0.0   587328.0 71682.3  1970136.0  1966351.3  4864.0 3027.2 512.0  287.0      14   22.270   8      0.589   22.859

其他说明

  • 以下现象未复现
    • 由于该现象的条件比较苛刻,不容易复现
    • 复现要求:JVM如果花了98%的时间进行垃圾回收,而只得到2%可用的内存,频繁的进行内存回收(最起码已经进行了5次连续的垃圾回收) 超出GC开销限制
java.lang.OutOfMemoryError: GC overhead limit exceeded
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值