12

我正在努力解决 Java 应用程序的大内存需求。

为了解决更多内存问题,我切换到了 64 位 JVM 并使用了大型 xmlx。但是,当 xx 大于 2GB 时,应用程序似乎比预期更早地耗尽了内存。当以 2400M 的 xx 运行并查看 GC 信息时,-verbosegc我得到...

[Full GC 2058514K->2058429K(2065024K), 0.6449874 secs] 

...然后它会引发内存不足异常。我希望它在内存不足之前将堆增加到 2065024K 以上。

在一个简单的例子中,我有一个测试程序,它在循环中分配内存并打印出信息Runtime.getRuntime().maxMemory()Runtime.getRuntime().totalMemory()直到它最终耗尽内存。

在一系列 xx 值上运行它,似乎Runtime.getRuntime().maxMemory()报告比 xx 少 10%,并且总内存不会增长超过 90% Runtime.getRuntime().maxMemory()

我正在使用以下 64 位 jvm:

java版本“1.6.0_26”
Java(TM) SE 运行时环境 (build 1.6.0_26-b03)
Java HotSpot(TM) 64 位服务器 VM(内部版本 20.1-b02,混合模式)

这是代码:

import java.util.ArrayList;

public class XmxTester {


private static String xmxStr;

private long maxMem;
private long usedMem;
private long totalMemAllocated;
private long freeMem;


private ArrayList list;

/**
 * @param args
 */
public static void main(String[] args) {

xmxStr = args[0];
XmxTester xmxtester = new XmxTester();
}

public XmxTester() {

byte[] mem = new byte[(1024 * 1024 * 50)];

list = new ArrayList();
while (true) {
    printMemory();
    eatMemory();
}

}

private void eatMemory() {
// TODO Auto-generated method stub
byte[] mem = null;
try {
    mem = new byte[(1024 * 1024)];
} catch (Throwable e) {
    System.out.println(xmxStr + "," + ConvertMB(maxMem) + ","
        + ConvertMB(totalMemAllocated) + "," + ConvertMB(usedMem)
        + "," + ConvertMB(freeMem));

    System.exit(0);
}

list.add(mem);

}

private void printMemory() {
maxMem = Runtime.getRuntime().maxMemory();
freeMem = Runtime.getRuntime().freeMemory();
totalMemAllocated = Runtime.getRuntime().totalMemory();
usedMem = totalMemAllocated - freeMem;


}

double ConvertMB(long bytes) {

int CONVERSION_VALUE = 1024;

return Math.round((bytes / Math.pow(CONVERSION_VALUE, 2)));

}

}

我使用这个批处理文件在多个 xml 设置上运行它。它包括对 32 位 JVM 的引用,我想与 32 位 jvm 进行比较 - 显然,只要 xmlx 大于约 1500M,此调用就会失败

@echo off
set java64=<location of 64bit JVM>
set java32=<location of 32bit JVM>
set xmxval=64


:start


SET /a xmxval  = %xmxval% + 64

 %java64%  -Xmx%xmxval%m  -XX:+UseCompressedOops -XX:+DisableExplicitGC XmxTester %xmxval%

%java32% -Xms28m -Xmx%xmxval%m   XmxTester %xmxval%

if %xmxval% == 4500 goto end
goto start
:end
pause

这会吐出一个 csv,当放入 excel 时看起来像这样(为我的格式不佳道歉)

32 位

XMX max mem total mem free mem %of xmx used before out of mem exception
128 127 127 125 2 98.4%
192 191 191 189 1 99.0%
256 254 254 252 2 99.2%
320 318 318 316 1 99.4%
384 381 381 379 2 99.5%
448 445 445 443 1 99.6%
512 508 508 506 2 99.6%
576 572 572 570 1 99.7%
640 635 635 633 2 99.7%
704 699 699 697 1 99.7%
768 762 762 760 2 99.7%
832 826 826 824 1 99.8%
896 889 889 887 2 99.8%
960 953 953 952 0 99.9%
1024 1016 1016 1014 2 99.8%
1088 1080 1080 1079 1 99.9%
1152 1143 1143 1141 2 99.8%
1216 1207 1207 1205 2 99.8%
1280 1270 1270 1268 2 99.8%
1344 1334 1334 1332 2 99.9%

64 位

128 122 122 116 6 90.6%
192 187 187 180 6 93.8%
256 238 238 232 6 90.6%
320 285 281 275 6 85.9%
384 365 365 359 6 93.5%
448 409 409 402 6 89.7%
512 455 451 445 6 86.9%
576 512 496 489 7 84.9%
640 595 595 565 30 88.3%
704 659 659 629 30 89.3%
768 683 682 676 6 88.0%
832 740 728 722 6 86.8%
896 797 772 766 6 85.5%
960 853 832 825 6 85.9%
1024 910 867 860 7 84.0%
1088 967 916 909 6 83.5%
1152 1060 1060 1013 47 87.9%
1216 1115 1115 1068 47 87.8%
1280 1143 1143 1137 6 88.8%
1344 1195 1174 1167 7 86.8%
1408 1252 1226 1220 6 86.6%
1472 1309 1265 1259 6 85.5%
1536 1365 1317 1261 56 82.1%
1600 1422 1325 1318 7 82.4%
1664 1479 1392 1386 6 83.3%
1728 1536 1422 1415 7 81.9%
1792 1593 1455 1448 6 80.8%
1856 1650 1579 1573 6 84.8%
1920 1707 1565 1558 7 81.1%
1984 1764 1715 1649 66 83.1%
2048 1821 1773 1708 65 83.4%
2112 1877 1776 1769 7 83.8%
2176 1934 1842 1776 66 81.6%
2240 1991 1899 1833 65 81.8%
2304 2048 1876 1870 6 81.2%
2368 2105 1961 1955 6 82.6%
2432 2162 2006 2000 6 82.2%
4

1 回答 1

14

为什么会这样?

基本上,JVM / GC 可以使用两种策略来决定何时放弃并抛出 OOME。

  • 它可以继续前进,直到垃圾收集后没有足够的内存来分配下一个对象。

  • 它可以继续运行,直到 JVM 花费超过给定百分比的时间运行垃圾收集器。

第一种方法的问题是,对于典型的应用程序,JVM 将花费越来越多的时间来运行 GC,最终徒劳地完成任务。

第二种方法的问题是它可能过早放弃。


该区域中 GC 的实际行为由 JVM 选项(-XX:...)控制。显然,默认行为在 32 位和 64 位 JVM 之间有所不同。这是有道理的,因为(直观地)64 位 JVM 的“内存不足死亡螺旋”效应将持续更长时间并且更加明显。


我的建议是不要理会这个问题。除非你真的需要用东西填充内存的最后一个字节,否则最好让 JVM 早点死掉并避免浪费大量时间。然后,您可以使用更多内存重新启动它并完成工作。

显然,您的基准是非典型的。大多数真正的程序根本不会尝试获取所有堆。您的应用程序也可能是非典型的。但也有可能您的应用程序正在遭受内存泄漏。如果是这种情况,您应该调查泄漏而不是试图找出为什么不能使用所有内存。


但是我的问题主要是为什么它不尊重我的 xmlx 设置。

对它的尊重!-Xmx 是堆大小的上限,不是决定何时放弃的标准。

我已将 XMX 设置为 2432M,但要求 JVM 返回其对最大内存的理解返回 2162M。

它返回它已使用的最大内存,而不是允许使用的最大内存。

为什么它“认为”最大内存比 xx 少 11%?

往上看。

此外,为什么当堆达到 2006M 时它不会将堆扩展到至少 2162 ?

我认为这是因为 JVM 已经达到了“垃圾收集时间过长”的阈值。

这是否意味着在 64 位 JVM 中应该将 XMX 设置比预期的最大值高 11%?

一般不会。软糖因素取决于您的应用程序。例如,具有较大对象流失率(即每单位有用工作创建和丢弃更多对象)的应用程序可能会因 OOME 而更快死亡。

我可以根据 db 大小预测需求,并有一个调整 xmx 的包装器,但是我有 11% 的问题,即我的监控表明应用程序需要 2 GB,所以我设置了 2.4GB xmx。然而,jvm 没有预期的 400MB 的“净空”,只允许堆增长到 2006M。

IMO,解决方案是在您当前添加的内容之上简单地添加额外的20%(或更多)。假设您有足够的物理内存,为 JVM 提供更大的堆将减少总体 GC 开销并使您的应用程序运行得更快。

您可以尝试的其他技巧是将 -Xmx 和 -Xms 设置为相同的值,并调整设置最大“垃圾收集时间”比率的调整参数。

于 2011-10-25T06:31:23.083 回答