-Xmx 是硬性限制吗?

is -Xmx a hard limit?

SO answer 澄清了一些关于 -Xmx JVM 标志的事情。为了进行实验,我做了以下操作:

import java.util.List;
import java.util.ArrayList;

public class FooMain {

    private static String memoryMsg() {
        return String.format("%s. %s. %s"
                             , String.format("total memory is: [%d]",Runtime.getRuntime().totalMemory())
                             , String.format("free memory is: [%d]",Runtime.getRuntime().freeMemory())
                             , String.format("max memory is: [%d]",Runtime.getRuntime().maxMemory()));
    }

    public static void main(String args[]) {
        String msg = null;
        try {
            System.out.println(memoryMsg());
            List<Object> xs = new ArrayList<>();
            int i = 0 ;
            while (true) {
                xs.add(new byte[1000]);
                msg = String.format("%d 1k arrays added.\n%s.\n"
                                    ,  ++i
                                    , memoryMsg());
            }
        } finally {
            System.out.printf(msg);
        }
    }
}

javac FooMain.java编译它。当我 运行 它的最大堆大小为 500 万字节时,我得到:

java -Xmx5000000 FooMain
total memory is: [5242880]. free memory is: [4901096]. max memory is: [5767168]
4878 1k arrays added.
total memory is: [5767168]. free memory is: [543288]. max memory is: [5767168].
Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded
          at java.lang.String.toCharArray(String.java:2748)
          at java.util.Formatter$FormatSpecifier.print(Formatter.java:3048)
          at java.util.Formatter$FormatSpecifier.printInteger(Formatter.java:2744)
          at java.util.Formatter$FormatSpecifier.print(Formatter.java:2702)
          at java.util.Formatter.format(Formatter.java:2488)
          at java.util.Formatter.format(Formatter.java:2423)
          at java.lang.String.format(String.java:2792)
          at FooMain.memoryMsg(FooMain.java:7)
          at FooMain.main(FooMain.java:21)

虽然数字足够接近,但它们似乎不是很准确(除了最后的 total memory 达到 正好 max memory).特别是 5368 个 1000 字节的数组,每个数组应该占用超过 5000000 甚至 5242880 字节。这些数字应该怎么理解?

这是我正在使用的java:

java version "1.7.0_80"
Java(TM) SE Runtime Environment (build 1.7.0_80-b15)
Java HotSpot(TM) Server VM (build 24.80-b11, mixed mode)

查看OpenJDK源代码,确定最大堆大小的逻辑相当复杂,由很多变量决定。实际堆大小在 hotspot/src/share/vm/memory/collectorPolicy.cpp 中设置,它使用提供的 -Xmx 值作为输入,并使用以下代码将其对齐:

align_size_up(MaxHeapSize, max_alignment());

align_size_up 定义为:

#define align_size_up_(size, alignment) (((size) + ((alignment) - 1)) & ~((alignment) - 1))

而max_alignment是虚拟内存页大小和JVM垃圾回收卡大小的乘积。 VM 页面大小为 4096 字节,卡大小为 512 字节。插入这些值给出 6324224 字节的实际 MaxHeapSize,这将与您看到的数字很好地对应。

注意:我只是简单地查看了 JVM 代码,所以我可能遗漏了一些东西,但答案似乎与您所看到的相加。

is -Xmx a hard limit?

这取决于你所说的“困难”是什么意思。如果你的意思是“不能被程序改变”那么是的。如果您的意思是“精确”,那么不是。垃圾收集器使用的各种 space 的大小是字节 2 的某个幂的倍数。显然,JVM 向上舍入而不是向下舍入。

Example program which allocates 1000 byte arrays

How should these numbers be understood?

一个 1000 字节的 Java 数组并不正好占用 1000 字节:

  • 有一个对象头,包括 space 32 位 length 字段。 (通常总共 3 x 32 位)。

  • 堆节点以 2^N 字节的倍数分配。 (通常为 2^3 == 8)


然后你会问是什么导致了 OutOfMemoryError。您可能认为这是因为堆已满。但是,在这种情况下,消息显示“超出 GC 开销限制”。这意味着 JVM 已检测到 JVM 在垃圾收集器 CPU 总时间 运行 上花费的时间百分比过大。这就是杀死 GC 的原因……不是 运行 内存不足。

“GC 开销限制”的基本原理是当堆接近满时,GC 会频繁运行并且每次设法回收越来越少的内存。当您进入 GC“死亡螺旋”时,最好迅速拔掉插头,而不是让应用程序一直磨到它的最终故障点。

无论如何...这意味着您计算堆已满时分配了多少内存的启发式方法可能不正确。

你的问题现在几乎已经被其他人回答了,但出于兴趣我做了数学计算:-)

表示here:

This value must a multiple of 1024 greater than 2 MB.

5000000 不是 1024 的倍数。我的猜测是该参数四舍五入为 1024 的倍数,其他答案证实了这一点。您的总内存(不仅仅是声明的堆 space)是 5767168,即 5632 * 1024。它在运行时从最初的 5242880 扩大到适合不断增长的堆 space。请注意 -Xmx 声明最大值,因此不一定立即分配。在 this source 的帮助下,我们可以估计您的内存使用情况(假设 64 位):

  • 4878000 字节为字节
  • 4878 * 24 = 117072 字节数组开销
  • 其他创建的对象和字符串的几个字节
  • 由于垃圾收集而导致的一点内存波动(至少您每次迭代创建的字符串可能会被丢弃)

所以数组占用 4995072 字节(巧合?)已经是 1024 的倍数了。但是其他对象仍然有开销。所以堆 space 是大于 4995072 且小于 5767168 的 1024 的倍数。考虑到最后的空闲 space,这给剩余的堆和非堆内存使用留下了 228808 字节,这听起来像是一个合理的数字。

终于有空了就一句话space留在最后。 JVM 不一定会在崩溃前填满所有内存。如果内存接近 运行 out,垃圾收集会更频繁地运行。如果这占用了整个运行时间的一定百分比,the JVM exits,这发生在你的情况下。