文章目录

  • 问题提出
  • 理论思考
  • 1. 是否可以设置`-Xmx`超过物理内存?
  • 2. 是否可以将`-Xmx`设置的无限大?
  • 3. 当物理机内存耗尽时,会发生什么现象?
  • 4. JVM在堆内存不足和物理内存耗尽时会发生什么?
  • 5. JVM会因为临近物理内存大小而发生GC吗
  • 问题验证
  • 测试代码
  • 测试Xmx最大值
  • Windows
  • Linux
  • 测试内存溢出
  • 开启SWAP情况下
  • 关闭SWAP情况下
  • 测试GC现象
  • 关闭Swap
  • 打开Swap
  • 其他说明


问题提出

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
  1. 如果对象长期存活,或着GC后触发动态年龄判断,对象会进入老年代,如果老年代装满时,触发 FullGC
  2. 触发 FullGC 后会有以下几种情况
  1. 如果是刚启动时,由于JVM启动时会先使用一个较小的内存(默认1/64内存大小),当GC后内存不足且没有到达 -Xmx 最大值时,会发生 堆扩容
  1. 如果扩容时无法申请内存,将发生 JVM物理内存无法申请 错误
  1. 如果因启用 Swap 区后,系统性能下降,GC的时间持续太长后,可能会触发 OOM 超出GC开销限制 错误
  1. JVM如果花了98%的时间进行垃圾回收,而只得到2%可用的内存,频繁的进行内存回收(最起码已经进行了5次连续的垃圾回收)时,会报这个错误
  1. 如果在 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