为什么64位JVM在到达xmx之前会抛出内存?

时间:2021-11-10 17:22:13

I am wrestling with large memory requirements for a java app.

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

In order to address more memory I have switch to a 64 bit JVM and am using a large xmx. However, when the xmx is above 2GB the app seems to run out of memory earlier than expected. When running with an xmx of 2400M and looking at GC info from -verbosegc I get...

为了处理更多的内存,我切换到64位JVM并使用一个大的xmx。然而,当xmx超过2GB时,应用程序似乎比预期更早地耗尽内存。当使用2400M的xmx运行并查看来自-verbosegc的GC信息时,我得到……

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

...and then it throws an out of memory exception. I would expect it to increase the heap above 2065024K before running out of memory.

…然后抛出一个内存异常。我期望它在耗尽内存之前将堆增加到2065024K以上。

In a trivial example i have a test program that allocates memory in a loop and prints out information from Runtime.getRuntime().maxMemory() and Runtime.getRuntime().totalMemory() until it eventually runs out of memory.

在一个简单的示例中,我有一个测试程序,它在循环中分配内存,并从运行时. getruntime (). maxmemory()和运行时. getruntime (). totalmemory()中输出信息,直到最终耗尽内存。

Running this over a range of xmx values it appears that Runtime.getRuntime().maxMemory() reports about 10% less than xmx and that total memory will not grow beyond 90% of Runtime.getRuntime().maxMemory().

在一个xmx值范围内运行它,运行时. getruntime (). maxmemory()报告的总内存比xmx少10%,并且总内存不会超过运行时. getruntime (). maxmemory()的90%。

I am using the following 64bit jvm:

我正在使用以下64位jvm:

java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

Here is the code:

这是代码:

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)));

}

}

I use this batch file to run it over multiple xmx settings. Its includes references to a 32 bit JVM, I wanted a comparison to a 32bit jvm - obviously this call fails as soon as xmx is larger than about 1500M

我使用这个批处理文件在多个xmx设置上运行它。它包含了对32位JVM的引用,我希望将其与32位JVM进行比较——显然,当xmx大于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

This spits out a csv which when put into excel looks like this (apologies for my poor formatting here)

这是一个csv,它在excel中是这样的(抱歉,这里格式不好)

32 bit

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 bit

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%

1 个解决方案

#1


13  

Why does it happen?

为什么会发生?

Basically, there are two strategies that the JVM / GC can use to decide when to give up and throw an OOME.

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

  • It can keep going and going until there is simply not enough memory after garbage collection to allocate the next object.

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

  • It can keep going until the JVM is spending more than a given percentage of time running the garbage collector.

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

The first approach has the problem that for a typical application the JVM will spend a larger and larger percentage of its time running the GC, in an ultimately futile effort to complete the task.

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

The second approach has the problem that it might give up too soon.

第二种方法的问题是它可能很快就放弃了。


The actual behaviour of the GC in this area is governed by JVM options (-XX:...). Apparently, the default behaviour differs between 32 and 64 bit JVMs. This kind of makes sense, because (intuitively) the "out of memory death spiral" effect for a 64 bit JVM will last longer and be more pronounced.

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


My advice would be to leave this issue alone. Unless you really need to fill every last byte of memory with stuff it is better for the JVM to die early and avoid wasting lots of time. You can then restart it with more memory and get the job done.

我的建议是不要管这个问题。除非你真的需要把内存的每一个字节都填满,否则JVM最好早点死,避免浪费很多时间。然后,您可以使用更多的内存重新启动它并完成任务。

Clearly, your benchmark is atypical. Most real programs simply don't try to grab all of the heap. It is possible that your application is atypical too. But it is also possible that your application is suffering from a memory leak. If that is the case, you should be investigating the leak rather than trying to figure out why you can't use all of memory.

显然,您的基准不是典型的。大多数真正的程序都不会试图获取所有的堆。您的应用程序也可能是非典型的。但是,您的应用程序也可能出现内存泄漏。如果是这样,您应该对泄漏进行调查,而不是试图弄清楚为什么不能使用所有内存。


However my issue is mainly with why it does not honor my xmx setting.

然而,我的问题主要是为什么它不支持我的xmx设置。

It is honoring it! The -Xmx is the upper limit on the heap size, not the criterion for deciding when to give up.

它是纪念吧!-Xmx是堆大小的上限,而不是决定何时放弃的标准。

I have set an XMX of 2432M but asking the JVM to return its understanding of max memory returns 2162M.

我设置了一个2432M的XMX,但是要求JVM返回它对最大内存的理解,返回2162M。

It is returning the max memory that it has used, not the max memory it is allowed to use.

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

Why does it 'think' the max memory is 11% less than the xmx?

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

See above.

见上图。

Furthermore why when the heap hits 2006M does it not extend the heap to at least 2162 ?

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

I presume that it is because the JVM has hit the "too much time spent garbage collecting" threshold.

我认为这是因为JVM达到了“花费太多时间进行垃圾收集”的阈值。

Does this mean in 64 bit JVMs one should fudge the XMX setting to be 11% higher than the intended maximum ?

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

Not in general. The fudge factor depends on your application. For instance, an application with a larger rate of object churn (i.e. more objects created and discarded per unit of useful work) is likely to die with an OOME sooner.

不一般。捏造因素取决于您的应用程序。例如,一个具有更大的对象流失率的应用程序(例如,在每一个有用的工作单元中创建和丢弃更多的对象)很可能很快就会死于OOME。

I can predict the requirments based on db size and have a wrapper that adjusts xmx, howeveri have the 11% problem whereby my montioring suggests the app needs 2 GB, so I set a 2.4GB xmx. however instead of having an expected 400MB of 'headroom' the jvm only allows the heap to grow to 2006M.

我可以根据db大小预测请求,并有一个调整xmx的包装器,但是我有11%的问题,我的montioring建议应用程序需要2g,所以我设置了2.4GB xmx。然而,与预期的400MB内存相比,jvm只允许堆增加到2006M。

IMO, the solution is to simply add an extra 20% (or more) on top of what you are currently adding. Assuming that you have enough physical memory, giving the JVM a larger heap is going to reduce overall GC overheads and make your application run faster.

在我看来,解决的办法就是在你现在添加的基础上再增加20%(或更多)。假设您有足够的物理内存,那么给JVM一个更大的堆将减少总的GC开销,并使您的应用程序运行得更快。

The other tricks that you could try is to set -Xmx and -Xms to the same value and adjusting the tuning parameter that sets the maximum "time spent garbage collecting" ratio.

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

#1


13  

Why does it happen?

为什么会发生?

Basically, there are two strategies that the JVM / GC can use to decide when to give up and throw an OOME.

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

  • It can keep going and going until there is simply not enough memory after garbage collection to allocate the next object.

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

  • It can keep going until the JVM is spending more than a given percentage of time running the garbage collector.

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

The first approach has the problem that for a typical application the JVM will spend a larger and larger percentage of its time running the GC, in an ultimately futile effort to complete the task.

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

The second approach has the problem that it might give up too soon.

第二种方法的问题是它可能很快就放弃了。


The actual behaviour of the GC in this area is governed by JVM options (-XX:...). Apparently, the default behaviour differs between 32 and 64 bit JVMs. This kind of makes sense, because (intuitively) the "out of memory death spiral" effect for a 64 bit JVM will last longer and be more pronounced.

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


My advice would be to leave this issue alone. Unless you really need to fill every last byte of memory with stuff it is better for the JVM to die early and avoid wasting lots of time. You can then restart it with more memory and get the job done.

我的建议是不要管这个问题。除非你真的需要把内存的每一个字节都填满,否则JVM最好早点死,避免浪费很多时间。然后,您可以使用更多的内存重新启动它并完成任务。

Clearly, your benchmark is atypical. Most real programs simply don't try to grab all of the heap. It is possible that your application is atypical too. But it is also possible that your application is suffering from a memory leak. If that is the case, you should be investigating the leak rather than trying to figure out why you can't use all of memory.

显然,您的基准不是典型的。大多数真正的程序都不会试图获取所有的堆。您的应用程序也可能是非典型的。但是,您的应用程序也可能出现内存泄漏。如果是这样,您应该对泄漏进行调查,而不是试图弄清楚为什么不能使用所有内存。


However my issue is mainly with why it does not honor my xmx setting.

然而,我的问题主要是为什么它不支持我的xmx设置。

It is honoring it! The -Xmx is the upper limit on the heap size, not the criterion for deciding when to give up.

它是纪念吧!-Xmx是堆大小的上限,而不是决定何时放弃的标准。

I have set an XMX of 2432M but asking the JVM to return its understanding of max memory returns 2162M.

我设置了一个2432M的XMX,但是要求JVM返回它对最大内存的理解,返回2162M。

It is returning the max memory that it has used, not the max memory it is allowed to use.

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

Why does it 'think' the max memory is 11% less than the xmx?

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

See above.

见上图。

Furthermore why when the heap hits 2006M does it not extend the heap to at least 2162 ?

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

I presume that it is because the JVM has hit the "too much time spent garbage collecting" threshold.

我认为这是因为JVM达到了“花费太多时间进行垃圾收集”的阈值。

Does this mean in 64 bit JVMs one should fudge the XMX setting to be 11% higher than the intended maximum ?

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

Not in general. The fudge factor depends on your application. For instance, an application with a larger rate of object churn (i.e. more objects created and discarded per unit of useful work) is likely to die with an OOME sooner.

不一般。捏造因素取决于您的应用程序。例如,一个具有更大的对象流失率的应用程序(例如,在每一个有用的工作单元中创建和丢弃更多的对象)很可能很快就会死于OOME。

I can predict the requirments based on db size and have a wrapper that adjusts xmx, howeveri have the 11% problem whereby my montioring suggests the app needs 2 GB, so I set a 2.4GB xmx. however instead of having an expected 400MB of 'headroom' the jvm only allows the heap to grow to 2006M.

我可以根据db大小预测请求,并有一个调整xmx的包装器,但是我有11%的问题,我的montioring建议应用程序需要2g,所以我设置了2.4GB xmx。然而,与预期的400MB内存相比,jvm只允许堆增加到2006M。

IMO, the solution is to simply add an extra 20% (or more) on top of what you are currently adding. Assuming that you have enough physical memory, giving the JVM a larger heap is going to reduce overall GC overheads and make your application run faster.

在我看来,解决的办法就是在你现在添加的基础上再增加20%(或更多)。假设您有足够的物理内存,那么给JVM一个更大的堆将减少总的GC开销,并使您的应用程序运行得更快。

The other tricks that you could try is to set -Xmx and -Xms to the same value and adjusting the tuning parameter that sets the maximum "time spent garbage collecting" ratio.

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