-Xmx是一个硬限制?

时间:2015-10-16 07:20:26

标签: java java-7

这个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)

3 个答案:

答案 0 :(得分:6)

查看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字节。插入这些值会给出实际的MaxHeapSize为6324224字节,这与您看到的数字很相符。

注意:我只是简单地查看了JVM代码,因此我可能错过了一些内容,但答案似乎与您所看到的相符。

答案 1 :(得分:5)

  

是-Xmx的硬限制吗?

这取决于你所说的“硬”。如果你的意思是“不能被程序改变”那么是。如果你的意思是“精确”,那么否。垃圾收集器使用的各种空间的大小是2字节的一些幂的倍数。显然,JVM向上而不是向下。

  

分配1000字节数组的示例程序

     

如何理解这些数字?

1000字节的Java数组不会精确占用1000个字节:

  • 有一个对象标题,包括32位length字段的空格。 (通常总共3 x 32位)。

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

然后你有了导致OutOfMemoryError的原因的问题。您可能认为这是因为堆已满。但是,在这种情况下,消息显示“超出GC开销限制”。这意味着JVM检测到JVM在运行垃圾收集器的整个CPU时间中所占的比例过大。这就是杀死GC的原因......没有内存耗尽。

“GC开销限制”的基本原理是当堆完全关闭时,GC会频繁运行,并且每次都会设法回收越来越少的内存。当你进入GC“死亡螺旋”时,最好快速拔出插头,而不是让应用程序磨到最终的失败点。

无论如何......这意味着你的启发式算法可以解决堆满了时分配多少内存的问题。

答案 2 :(得分:1)

到目前为止,其他人已经回答了你的问题,但出于兴趣,我已经完成了数学运算: - )

陈述here

  

此值必须是1024的倍数,大于2 MB。

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

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

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

最后还有一个关于自由空间的文字。在崩溃之前,JVM不一定会填满所有内存。如果内存即将耗尽,则垃圾收集会更频繁地运行。如果这占用整个运行时的一定百分比,the JVM exits,就在您的情况下发生。