JVM 内存溢出原因

huangapple go评论69阅读模式
英文:

JVM Out of Memory Causes

问题

问题基于Oracle Hotspot JDK8。

当应用程序遇到java.lang.OutOfMemory: Java heap space异常时,我认为有两个可能的原因。

  1. 分配的JVM堆大小达到了-Xmx指定的大小,而GC系统无法释放出足够的空间。
  2. 分配的JVM堆没有达到-Xmx,但是没有足够的物理内存供JVM堆增长。假设-Xms < -Xmx

我知道1是JVM抛出java.lang.OutOfMemory: Java heap space异常的原因之一。2是一个合理的原因吗?

我在一些文章中找到了提到java.lang.OutOfMemoryError: native memory exhausted,但它们都限于IBM的网站。这个异常是否仅限于IBM实现的JVM,还是它是JVM规范中的标准异常?


我根据@Eugene提供的代码进行了一些实验。正如@Holger指出的,结果在不同的环境中会有所不同。我在CentOS x64和Win7 x64上进行了测试,使用的是Hotspot JDK8 x64。
为了简单起见,已禁用了交换和虚拟内存。

我逐步增加了内存限制(-Xmx和-Xms)。

I. -Xmx < 可用逻辑内存

  • 在CentOS和Windows上都显示OutOfMemoryError: Java heap space

II. 可用逻辑内存 < -Xmx < 最大物理内存

  • CentOS:GC尝试多次进行Full GC,出现Allocation Failure,然后进程被系统杀死,留下一个消息Killed
  • Windows:GC尝试多次进行Full GC,出现Allocation Failure,然后抛出OutOfMemoryError: Java heap space

III. -Xmx > 最大物理内存

  • CentOS:与II中相同
  • Windows:与II中相同

IV. -Xms > 最大物理内存

  • CentOS:JVM似乎无法启动。错误消息类似于:

> Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000e62a0000, 349569024, 0) failed; error='Cannot allocate memory' (errno=12)

  • Windows:JVM无法启动。错误消息类似于:
    > Error occurred during initialization of VM
    Could not reserve enough space for object heap

因此,相同的JVM在不同的操作系统中表现不同。

  • 在Windows上,操作系统不会终止JVM。当内存使用量超过限制时,JVM总是抛出OutOfMemoryError: Java heap space

  • 在Linux上,操作系统会在内存不足时终止进程。

  • 在两种操作系统上,当可用内存不满足JVM的最小要求时,JVM无法启动。

英文:

Questions are based on Oracle Hotspot JDK8.

When applicaton come across java.lang.OutOfMemory: Java heap space exception, I suppose, where are two possible reasons.

  1. Allocated JVM heap size reaches -Xmx specified size and GC system cann't squeeze out enough space.
  2. Allocated JVM heap doesn't reach -Xmx, but there are not enough physical memory for JVM heap to grow. Suppose -Xms < -Xmx.

I know 1 is a reason for JVM to throw out java.lang.OutOfMemory: Java heap space exception. Is 2 a reasonable one cause?

I find some articles mentioned java.lang.OutOfMemoryError: native memory exhausted, but they are all limited to IBM website. Is this expetion limited to IBM implemented JVM or it is a standard expetion in JVM Specification?


I did some experiments with code supplied by @Eugene in answer. As @Holger noted the result varies in different environments. I tested in on both CentOS x64 and Win7 x64, with Hotspot JDK8 x64.
For simplicity swap and virtual memory are disabled.

I increase memory bound (-Xmx and -Xms) step by step.

I. -Xmx < available logic memory

  • On both CentOS and Windows it shows OutOfMemoryError: Java heap space

II. available logic memory < -Xmx < max physical memory

  • CentOS: The GC try to Full GC serveral times, with Allocation Failure, and process be killed by system, leaving a message Killed.
  • Windows: The GC try to Full GC serveral times, with Allocation Failure, and throw out OutOfMemoryError: Java heap space

III. -Xmx > max physical memory

  • CentOS: same as in II
  • Windows: same as in II

IV. -Xms > max physical memory

  • CentOS: JVM seems fail to start. Error message is like:

> Java HotSpot(TM) 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000e62a0000, 349569024, 0) failed; error='Cannot allocate memory' (errno=12)

  • Windows: JVM failed to start. Error message is like:
    > Error occurred during initialization of VM
    Could not reserve enough space for object heap

So, the same JVM behave differently in different OS.

  • On windows, the OS doesn't kill JVM. And JVM always throw out OutOfMemoryError: Java heap space when memory usage grow exceeds.

  • On Linux, the OS kill processes when there is not enough memory.

  • On both OS JVM failed to start when available memory doesn't satisfy JVM minimal requirement.

答案1

得分: 1

首先,有更多的原因可以导致垃圾回收出现“内存不足”的失败,正如您问题下的评论所解释的那样。

证明第二点很简单,只需创建一些总是在分配内存的代码:

public static void main(String[] args) {
    test(1);
}

static void test(int x){
    List<byte[]> list = new ArrayList<>();
    while(x == 1){
        byte [] b =new byte[1 * 1024 * 1024];
        b[100] = 42;
        list.add(b);
    }

    System.out.println(list.hashCode());
}

然后使用 -Xms1g -Xmx100g 运行此代码,在一个拥有少于 100g 内存的系统上运行。您可以启用 GC 日志(例如,我使用了 "-Xlog:heap*=debug" "-Xlog:gc*=debug" 在 Java 9 的标志中),查看 GC 如何努力应对这个不断的内存分配,最终失败。

英文:

First of all there are more reasons for a GC to fail with "out of memory" as the comment under your question explains.

Proving point number (2) is easy, just create some code that always allocates:

public static void main(String[] args) {
    test(1);
}

static void test(int x){
    List&lt;byte[]&gt; list = new ArrayList&lt;&gt;();
    while(x == 1){
        byte [] b =new byte[1 * 1024 * 1024];
        b[100] = 42;
        list.add(b);
    }

    System.out.println(list.hashCode());
}

And run this with -Xms1g -Xmx100g, on a system that has less then 100g of RAM. You can enable GC logs (I did with &quot;-Xlog:heap*=debug&quot; &quot;-Xlog:gc*=debug&quot; in java-9 flags for example) and see how hard GC is trying to cope with this constant allocation, ultimately failing.

huangapple
  • 本文由 发表于 2020年8月20日 11:03:44
  • 转载请务必保留本文链接:https://go.coder-hub.com/63497696.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定