JVM-015-运行时数据区-堆(Heap)-设置堆内存大小和OOM

设置堆内存

Java 堆区用于存储Java对象实例,那么堆的大小在JVM启动时就已经设定好了,可以通过选项”-Xms“和”-Xmx“来进行设置。

  • -Xms用于表示堆区的起始内存,等价于-XX:InitialHeapSize
  • -Xmx则用于表示堆区的最大内存,等价于-XX:MaxHeapSize

-Xms

-Xms 用来设置堆空间(年轻代+老年代)的初始内存大小

  •  -X 是jvm的运行参数
    
  •  ms 是memory start
    

例如:-Xms1024、-Xms10K、-Xms20M、-Xms2G

-Xmx

-Xmx 用来设置堆空间(年轻代+老年代)的最大内存大小

例如:-Xmx1024、-Xmx10K、-Xmx20M、-Xmx2G

使用方法

  • 一旦堆区中的内存大小超过 -Xmx 所指定的最大内存时,将会抛出OutofMemoryError异常。

  • 通常会将-Xms和-Xmx两个参数配置相同的值,其目的是为了能够在Java垃圾回收机制清理完堆区后不需要重新分隔计算堆区的大小,从而提高性能

    假设两个不一样,初始内存小,最大内存大。在运行期间如果堆内存不够用了,会一直扩容直到最大内存。如果内存够用且多了,也会不断的缩容释放。频繁的扩容和释放造成不必要的压力,避免在GC之后调整堆内存给服务器带来压力。
    如果两个设置一样的就少了频繁扩容和缩容的步骤。内存不够了就直接报OOM

默认情况

  • 初始内存大小:物理电脑内存大小除以64

  • 最大内存大小:物理电脑内存大小除以4

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class HeapSpaceInitial {
public static void main(String[] args) {

//返回Java虚拟机中的堆内存总量
long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
//返回Java虚拟机试图使用的最大堆内存量
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

System.out.println("-Xms : " + initialMemory + "M");
System.out.println("-Xmx : " + maxMemory + "M");

System.out.println("系统内存大小为:" + initialMemory * 64.0 / 1024 + "G");
System.out.println("系统内存大小为:" + maxMemory * 4.0 / 1024 + "G");

try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

==============
-Xms : 245M
-Xmx : 3641M
系统内存大小为:15.3125G
系统内存大小为:14.22265625G

手动设置

以IDEA为例:

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class HeapSpaceInitial {
public static void main(String[] args) {

//返回Java虚拟机中的堆内存总量
long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
//返回Java虚拟机试图使用的最大堆内存量
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

System.out.println("-Xms : " + initialMemory + "M");
System.out.println("-Xmx : " + maxMemory + "M");

try {
Thread.sleep(1000000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

==============
-Xms : 575M
-Xmx : 575M

少25M的原因如下:

查看设置的参数

通过命令行(jps / jstat)查看

  1. 先查到进程id(通过 jps 命令)

    1
    2
    3
    $ jps
    13237 HeapSpaceInitial
    13339 Jps
  2. 在查看内存分配情况(通过 jstat 命令)

    1
    2
    3
    $ jstat -gc 13237
    S0C S1C S0U S1U EC EU OC OU MC MU CCSC CCSU YGC YGCT FGC FGCT GCT
    25600.0 25600.0 0.0 0.0 153600.0 15360.5 409600.0 0.0 4480.0 781.5 384.0 76.6 0 0.000 0 0.000 0.000

    字段解释:

    字段 含义 示例值
    S0C 年轻代的 S0 区总容量(单位:KB)。 25600 KB(25 MB)
    S1C 年轻代的 S1 区总容量(单位:KB)。 25600 KB(25 MB)
    S0U 年轻代的 S0 区已使用的内存(单位:KB)。 0 KB
    S1U 年轻代的 S1 区已使用的内存(单位:KB)。 0 KB
    EC 年轻代的 Eden 区总容量(单位:KB)。 153600 KB(150 MB)
    EU 年轻代的 Eden 区已使用的内存(单位:KB)。 15360.5 KB(15 MB)
    OC 老年代(Old Generation)总容量(单位:KB)。 409600 KB(400 MB)
    OU 老年代(Old Generation)已使用的内存(单位:KB)。 0 KB
    MC 元空间(Metaspace)的总容量(单位:KB)。 4480 KB(4.4 MB)
    MU 元空间(Metaspace)已使用的内存(单位:KB)。 781.5 KB
    CCSC 压缩类空间(Compressed Class Space)的总容量(单位:KB)。 384 KB
    CCSU 压缩类空间(Compressed Class Space)已使用的内存(单位:KB)。 76.6 KB
    YGC Young Generation 的 GC 次数,即 Minor GC 的次数。 0 次
    YGCT Young Generation 的 GC 总时间,以秒为单位。 0.000 秒
    FGC Full GC 的次数,即对整个堆(Young + Old Generation)的垃圾回收次数。 0 次
    FGCT Full GC 的总时间,以秒为单位。 0.000 秒
    GCT 垃圾回收的总时间,包括 Minor GC 和 Full GC 的时间(单位:秒)。 0.000 秒
  3. 计算年轻代和老年代(其中年轻代的S0区和S1区两个只有一个能使用,另一个用不了),所以:

    • 25600+25600+153600+409600 = 614400K

      614400 /1024 = 600M

    • 25600+153600+409600 = 588800K

      588800 /1024 = 575M

通过JVM参数(-XX:+PrintGCDetails)查看

意思是:程序执行完后打印 GC 过程中的细节

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class HeapSpaceInitial {
public static void main(String[] args) {

//返回Java虚拟机中的堆内存总量
long initialMemory = Runtime.getRuntime().totalMemory() / 1024 / 1024;
//返回Java虚拟机试图使用的最大堆内存量
long maxMemory = Runtime.getRuntime().maxMemory() / 1024 / 1024;

System.out.println("-Xms : " + initialMemory + "M");
System.out.println("-Xmx : " + maxMemory + "M");
}
}

==============
-Xms : 575M
-Xmx : 575M
Heap
PSYoungGen total 179200K, used 9216K [0x00000007b3800000, 0x00000007c0000000, 0x00000007c0000000)
eden space 153600K, 6% used [0x00000007b3800000,0x00000007b41001a0,0x00000007bce00000)
from space 25600K, 0% used [0x00000007be700000,0x00000007be700000,0x00000007c0000000)
to space 25600K, 0% used [0x00000007bce00000,0x00000007bce00000,0x00000007be700000)
ParOldGen total 409600K, used 0K [0x000000079a800000, 0x00000007b3800000, 0x00000007b3800000)
object space 409600K, 0% used [0x000000079a800000,0x000000079a800000,0x00000007b3800000)
Metaspace used 3075K, capacity 4496K, committed 4864K, reserved 1056768K
class space used 328K, capacity 388K, committed 512K, reserved 1048576K

分析方法同上。

手动模拟OOM

代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.buubiu;

import java.util.ArrayList;
import java.util.Random;

public class OOMTest {
public static void main(String[] args) {
ArrayList<Picture> list = new ArrayList<>();
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add(new Picture(new Random().nextInt(1024 * 1024)));
}
}
}

class Picture{
private byte[] pixels;

public Picture(int length) {
this.pixels = new byte[length];
}
}
  1. 设置堆内存

    -Xms600m -Xmx600m

  2. 输出结果:

    1
    2
    3
    Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
    at com.buubiu.Picture.<init>(OOMTest.java:24)
    at com.buubiu.OOMTest.main(OOMTest.java:15)
  3. 观察堆内存变化

  4. 原因:模拟的大对象导致堆内存溢出

JVM-015-运行时数据区-堆(Heap)-设置堆内存大小和OOM

https://blog.buubiu.com/JVM-015-运行时数据区-堆-Heap-设置堆内存大小和OOM/

作者

buubiu

发布于

2022-06-14

更新于

2024-11-29

许可协议