12

私は、Java アプリケーションの大量のメモリ要件に取り組んでいます。

より多くのメモリに対応するために、64 ビット JVM に切り替え、大きな xmx を使用しています。ただし、xmx が 2GB を超えると、アプリは予想よりも早くメモリ不足になるようです。2400M の xmx で実行し、GC 情報を見ると-verbosegc...

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

...そして、メモリ不足の例外をスローします。メモリが不足する前に、2065024K を超えるヒープを増やすことが期待されます。

些細な例では、ループでメモリを割り当て、最終的にメモリが不足するまで情報Runtime.getRuntime().maxMemory()を出力するテスト プログラムがあります。Runtime.getRuntime().totalMemory()

これを xmx 値の範囲で実行すると、Runtime.getRuntime().maxMemory()xmx よりも約 10% 少なく、合計メモリが の 90% を超えて増加しないように見えますRuntime.getRuntime().maxMemory()

次の64ビットjvmを使用しています:

Java バージョン「1.6.0_26」
Java(TM) SE ランタイム環境 (ビルド 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)));

}

}

このバッチ ファイルを使用して、複数の 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

これは、Excelに入れると次のように見えるcsvを吐き出します(ここで私のフォーマットが悪いことをお詫びします)

32ビット

XMX max mem total mem free mem %of xmx used before out of me 例外
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 をスローするかを決定するために使用できる 2 つの戦略があります。

  • ガベージ コレクションの後、次のオブジェクトを割り当てるのに十分なメモリがなくなるまで、処理を続けることができます。

  • JVM がガベージ コレクタの実行に一定の割合以上の時間を費やすまで、継続できます。

最初のアプローチには、典型的なアプリケーションの場合、JVM が GC の実行に費やす時間の割合がますます大きくなり、タスクを完了するための最終的に無駄な努力になるという問題があります。

2 番目のアプローチには、すぐにあきらめてしまう可能性があるという問題があります。


この領域での GC の実際の動作は、JVM オプション (-XX:...) によって制御されます。どうやら、デフォルトの動作は 32 ビットと 64 ビットの JVM で異なります。(直感的に) 64 ビット JVM の「メモリ不足の死のスパイラル」効果はより長く続き、より顕著になるため、この種のことは理にかなっています。


私のアドバイスは、この問題を放っておくことです。メモリの最後のバイトをすべて埋める必要が本当にない限り、JVM が早期に終了し、多くの時間を無駄にしないようにすることをお勧めします。その後、メモリを増やして再起動し、ジョブを完了できます。

明らかに、あなたのベンチマークは非典型的です。ほとんどの実際のプログラムは、すべてのヒープを取得しようとはしません。アプリケーションも非典型的である可能性があります。ただし、アプリケーションでメモリ リークが発生している可能性もあります。その場合は、すべてのメモリを使用できない理由を突き止めようとするのではなく、リークを調査する必要があります。


ただし、私の問題は主に、私の xmx 設定を尊重しない理由にあります。

それそれを尊重しています!-Xmx はヒープ サイズの上限であり、いつあきらめるかを決定する基準ではありません。

XMX を 2432M に設定しましたが、JVM に最大メモリの理解を返すように要求すると、2162M が返されます。

使用できる最大メモリではなく、使用した最大メモリを返します。

最大メモリが xmx よりも 11% 少ないと「考える」のはなぜですか?

上記を参照。

さらに、ヒープが 2006M に達すると、ヒープが少なくとも 2162 に拡張されないのはなぜですか?

これは、JVM が「ガベージ コレクションに費やした時間が長すぎる」というしきい値に達したためだと思います。

これは、64 ビット JVM では、XMX 設定を意図した最大値よりも 11% 高くする必要があるということですか?

一般的ではありません。ファッジ係数は、アプリケーションによって異なります。たとえば、オブジェクト チャーンの割合が大きいアプリケーション (つまり、有効な作業単位ごとに作成および破棄されるオブジェクトの数が多い) は、OOME でより早く停止する可能性があります。

データベースのサイズに基づいて要件を予測し、xmx を調整するラッパーを使用できますが、11% の問題があり、監視ではアプリに 2 GB が必要であることが示唆されているため、2.4 GB の xmx を設定しました。ただし、予想される 400MB の「ヘッドルーム」を持つ代わりに、jvm はヒープを 2006M までしか拡大できません。

IMO、解決策は、現在追加しているものにさらに 20% (またはそれ以上) を追加することです十分な物理メモリがあると仮定すると、JVM に大きなヒープを与えると、全体的な GC オーバーヘッドが削減され、アプリケーションの実行が高速になります。

あなたが試すことができる他のトリックは、 -Xmx と -Xms を同じ値に設定し、最大の「ガベージ コレクションに費やされた時間」の比率を設定するチューニング パラメーターを調整することです。

于 2011-10-25T06:31:23.083 に答える