ホームページ  >  記事  >  Java  >  Java メモリの割り当てとリサイクル戦略の分析

Java メモリの割り当てとリサイクル戦略の分析

怪我咯
怪我咯オリジナル
2017-07-02 10:35:351308ブラウズ

次のエディターは、Java のメモリ割り当てとリサイクル戦略についての深い理解をもたらします。編集者はこれがとても良いものだと思ったので、皆さんの参考として今から共有します。エディターをフォローして一緒に見てみましょう

1. はじめに

Java テクノロジー システムで言及されている自動メモリ管理は、最終的にはメモリ割り当てとリサイクルという 2 つの問題です。以前、Java のリサイクルについてお話しました。関連知識として、今日はメモリ内の Javaオブジェクトの割り当てについてお話しましょう。平たく言えば、オブジェクトのメモリ割り当てはヒープ上の割り当てです。オブジェクトは主に新しい世代の Eden に割り当てられます (メモリ内のオブジェクトの生成はガベージ コレクション中に補充されます)。詳しく知りたい場合は、次のことを確認してください。 「Java 仮想マシンの詳細な理解」も参照してください)。ローカル スレッド割り当てバッファが開始されると、スレッドの優先順位に従って TLAB 上に割り当てられます。場合によっては、古い世代に直接割り当てられることもあります。

2. 古典的な割り当て戦略

1. オブジェクトは最初に Eden に割り当てられます

通常、Eden に割り当て用の十分なスペースがない場合、jvm は最初に割り当てを開始します。マイナー GC。それでも十分なスペース割り当てがない場合は、後で説明する他の対策もあります。

仮想マシンの奇数ログパラメータ -XX:+PrintGCDetails を設定すると、ガベージコレクション中にメモリリサイクルログが出力され、プロセス終了時に各メモリ領域の現在の割り当てが出力されます。具体的な例を見てみましょう。まず、jvm パラメータ -Xms20m -Xmx20m -Xmn10m を設定する必要があります。これらの 3 つのパラメータは、Java ヒープ サイズが 20M で、残りの 10M は拡張できないことを示します。古い世代に割り当てられます。 -XX:SurvivorRatio=8 は、新世代の jvm における Eden と Survivor のデフォルトの比率です。デフォルトは 8:1 です。その理由は、新しい世代のオブジェクトの 98% が次の GC でリサイクルされるため、ガベージ コレクションにコピー アルゴリズムを使用するのが非常に適しているためです。そのため、新しい世代の 10M メモリのうち 8M が Eden になります。 1M は Survivor で、残りの 1M は未使用です。コピー アルゴリズムと連携するメモリ ブロックも Survivor です。

public class ReflectTest {

  private static final int _1MB = 1024*1024;
  
  public static void testAllocation(){
    byte[] allocation1 , allocation2 , allocation3 , allocation4;
    allocation1 = new byte[2 * _1MB];
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation4 = new byte[6 * _1MB];
  }
  
  public static void main(String[] args) {
    ReflectTest.testAllocation();
  }
  
}

出力は以下の通りです

Heap
 PSYoungGen   total 9216K, used 6651K [0x000000000b520000, 0x000000000bf20000, 0x000000000bf20000)
 eden space 8192K, 81% used [0x000000000b520000,0x000000000bb9ef28,0x000000000bd20000)
 from space 1024K, 0% used [0x000000000be20000,0x000000000be20000,0x000000000bf20000)
 to  space 1024K, 0% used [0x000000000bd20000,0x000000000bd20000,0x000000000be20000)
 PSOldGen    total 10240K, used 6144K [0x000000000ab20000, 0x000000000b520000, 0x000000000b520000)
 object space 10240K, 60% used [0x000000000ab20000,0x000000000b120018,0x000000000b520000)
 PSPermGen    total 21248K, used 2973K [0x0000000005720000, 0x0000000006be0000, 0x000000000ab20000)
 object space 21248K, 13% used [0x0000000005720000,0x0000000005a07498,0x0000000006be0000)

eden が 81% を占めていることがわかります。これは、allocation1、allocation2、allocation3 がすべて新世代 Eden に割り当てられていることを示しています。

2. ラージオブジェクトは、古い世代では直接割り当てられます。

ラージオブジェクトとは、非常に長い

文字列や配列と同様に、保存するために大量の連続メモリスペースを必要とするオブジェクトを指します。大きなオブジェクトは仮想マシンのメモリ分散にとって好ましくありません。1 ラウンドしか存続しない大きなオブジェクトが多数発生すると、コードを作成するときにそのような問題を回避する必要があります。 -XX:PretenureSizeThreshold パラメータは、この値より大きいオブジェクトが古い世代に直接割り当てられます。これは、Eden 領域と Survivor 領域の間で大量のメモリがコピーされることを避けるためです。前に述べた リサイクルアルゴリズムとコピーアルゴリズムについては前に述べたので、詳細は説明しません。

public class ReflectTestBig {

  private static final int _1MB = 1024*1024;
  
  public static void testAllocation(){
    byte[] allocation2 , allocation3 , allocation4;
    allocation2 = new byte[2 * _1MB];
    allocation3 = new byte[2 * _1MB];
    allocation4 = new byte[6 * _1MB];
  }
  
  public static void main(String[] args) {
    ReflectTestBig.testAllocation();
  }
  
}

出力は以下の通りです

Heap
 PSYoungGen   total 8960K, used 4597K [0x000000000b510000, 0x000000000bf10000, 0x000000000bf10000)
 eden space 7680K, 59% used [0x000000000b510000,0x000000000b98d458,0x000000000bc90000)
 from space 1280K, 0% used [0x000000000bdd0000,0x000000000bdd0000,0x000000000bf10000)
 to  space 1280K, 0% used [0x000000000bc90000,0x000000000bc90000,0x000000000bdd0000)
 PSOldGen    total 10240K, used 6144K [0x000000000ab10000, 0x000000000b510000, 0x000000000b510000)
 object space 10240K, 60% used [0x000000000ab10000,0x000000000b110018,0x000000000b510000)
 PSPermGen    total 21248K, used 2973K [0x0000000005710000, 0x0000000006bd0000, 0x000000000ab10000)
 object space 21248K, 13% used [0x0000000005710000,0x00000000059f7460,0x0000000006bd0000)

allocation4が設定した-XX:PretenureSizeThreshold=3145728を超えていることが分かります。Allocation4は古い世代に直接割り当てられており、古い世代の占有率は60%となっています。設定 -XX:PretenureSizeThreshold=3145728 を -XX:PretenureSizeThreshold=3m として記述することはできないことに注意してください。そうしないと、jvm がそれを認識しません。

3. 長期的に存続するオブジェクトは古い世代に入ります

仮想マシンはメモリを管理するためにストリップコレクションの考え方を採用しているため、メモリのリサイクルではどのオブジェクトを新しい世代に配置する必要があるかを特定する必要があります。どのオブジェクトを古い時代に置くべきか。この目的を達成するために、jvm はオブジェクトごとに年齢カウンター (Age) を定義します。オブジェクトが Eden で生まれ、最初のマイナー GC を生き延び、Survivor に保存できる場合、そのオブジェクトは Survivor に移動され、オブジェクトの年齢は 1 に設定されます。オブジェクトがマイナー GC をエスケープするたびに、その年齢は 1 ずつ増加します。オブジェクトの年齢がしきい値の 1 年を超えると、オブジェクトは古い世代に昇格します。このしきい値 jvm のデフォルトは 15 で、-XX:MaxTenuringThreshold で設定できます。

public class JavaTest { 
 
  static int m = 1024 * 1024; 
 
  public static void main(String[] args) { 
    byte[] a1 = new byte[1 * m / 4]; 

     byte[] a2 = new byte[7 * m]; 

     byte[] a3 = new byte[3 * m]; //GC 
  } 
}

出力は次のとおりです

[GC [DefNew: 7767K->403K(9216K), 0.0062209 secs] 7767K->7571K(19456K), 0.0062482 secs]  
[Times: user=0.00 sys=0.00, real=0.01 secs]  
a3 ok 
Heap 
 def new generation  total 9216K, used 3639K [0x331d0000, 0x33bd0000, 0x33bd0000) 
 eden space 8192K, 39% used [0x331d0000, 0x334f9040, 0x339d0000) 
 from space 1024K, 39% used [0x33ad0000, 0x33b34de8, 0x33bd0000) 
 to  space 1024K,  0% used [0x339d0000, 0x339d0000, 0x33ad0000) 
 tenured generation  total 10240K, used 7168K [0x33bd0000, 0x345d0000, 0x345d0000) 
  the space 10240K, 70% used [0x33bd0000, 0x342d0010, 0x342d0200, 0x345d0000) 
 compacting perm gen total 12288K, used 381K [0x345d0000, 0x351d0000, 0x385d0000) 
  the space 12288K,  3% used [0x345d0000, 0x3462f548, 0x3462f600, 0x351d0000) 
  ro space 10240K, 55% used [0x385d0000, 0x38b51140, 0x38b51200, 0x38fd0000) 
  rw space 12288K, 55% used [0x38fd0000, 0x396744c8, 0x39674600, 0x39bd0000)

a2 が一度存続し、年齢が 1 で、セット -XX:MaxTenuringThreshold=1 を満たしていることがわかります。そのため、a2 は古い世代に入り、a3 は新しい世代に入りました。世代。

4. 動的なオブジェクトの経過時間の決定

さまざまなプログラムのメモリ状態に適切に適応するために、仮想マシンはオブジェクトの経過時間が -XX:MaxTenuringThreshold で設定された値に達する必要があるとは限りません。古い年齢に昇格する前に、Survivor スペース内の同じ年齢のすべてのオブジェクトのサイズの合計が Survivor スペースの半分より大きい場合、この年齢以上の年齢のオブジェクトを使用できます。 -XX:MaxTenuringThreshold で設定した値に達せずに、古い領域に直接入力します。

5. スペース割り当ての保証

マイナー GC が発生すると、仮想マシンは古い世代への各昇格の平均サイズが古い世代の残りのスペースより大きいかどうかを検出し、それが大きい場合は、フル GC が直接実行されます。未満の場合は、HandlerPromotionFailyre 設定で保証失敗が許可されているかどうかを確認します。許可されている場合は、マイナー GC のみが実行されます。許可されていない場合は、FUll GC も改善されます。つまり、新世代 Eden が変更されたオブジェクトを保存できない場合、オブジェクトは旧世代に保存されます。

3. 一般的に使用される jvm パラメーター設定

1. -Xms: 初期ヒープ サイズ、デフォルト (MinHeapFreeRatio パラメーターは調整可能) 空きヒープ メモリが 40% 未満の場合、JVM はヒープを増加させます。 -Xmx 制限の最大値。

2.

3. -Xmn: 若い世代のサイズ (1.4 または lator)、ここでのサイズは (eden+2 survivor space) であり、jmap -heap で示される新しい世代とは異なります。

ヒープ全体のサイズ = 若い世代のサイズ + 古い世代のサイズ + 永続的な世代のサイズ。

若い世代を増やした後、古い世代のサイズは、システムのパフォーマンスに大きな影響を与えます。Sun は、ヒープ全体の 3/8 の構成を推奨します。

4. -XX:NewSize: 若い世代のサイズを設定します (1.3/1.4 の場合)。

5. -XX:MaxNewSize: 若い世代の最大値 (1.3/1.4 の場合)。

6. -XX:PermSize: 永続世代 (perm gen) の初期値を設定します。

7. -XX:MaxPermSize: 永続世代の最大サイズを設定します。

8、-Xss: 各スレッドのスタック サイズ。JDK5.0 以降、各スレッドのスタック サイズは 1M でした。アプリケーション スレッドが必要とするメモリ サイズは 256K でした。同じ物理アンダーメモリでは、この値を減らすとより多くのスレッドを生成できますが、オペレーティング システムにはプロセス内のスレッド数に制限があり、経験値は約 3000 ~ です。 5000。

9、-XX:NewRatio: 古い世代 (永続世代を除く) に対する若い世代 (エデンと 2 つの Survivor エリアを含む) の比率、-XX:NewRatio=4 は、若い世代と古い世代の比率を意味します。古い世代は 1:4、若い世代はスタック全体の 1/5 を占めます。 Xms=XmxかつXmnを設定した場合、本パラメータの設定は不要です。

10、-XX:SurvivorRatio: Eden エリアと Survivor エリアのサイズ比を 8 に設定すると、2 つの Survivor エリアと 1 つの Eden エリアの比率は 2:8 となり、1 つの Survivor エリアは 1/10 を占めます。若い世代全体の。

11. -XX:LargePageSizeInBytes: Perm のサイズに影響を与えるため、メモリ ページのサイズをあまり大きく設定することはできません。

12、-XX:+DisableExplicitGC: System.gc() をオフにします

13、-XX:MaxTenuringThreshold: ガベージの最大存続期間を 0 に設定すると、若い世代のオブジェクトは Survivor 領域を経由せずに直接実行されます。古い世代を多数含むアプリケーションの場合、この値をより大きな値に設定すると、若い世代のオブジェクトが Survivor 領域に複数回コピーされるため、生存時間が長くなる可能性があります。若い世代のオブジェクトの数を増やし、若い世代がリサイクルされる確率。このパラメータはシリアル GC でのみ有効です。

14、-XX:PretenureSizeThreshold: サイズを超える場合、オブジェクトは古い世代に直接割り当てられます。新しい世代が Parallel Scavenge GC を使用する場合、ユニット バイトは無効になります。大きな配列オブジェクトと配列 外部referenceオブジェクトはありません。

15、-XX:TLABWasteTargetPercent: eden エリア内の TLAB の割合。

4. 補足

マイナー GC とフル GC の違い:

新世代 GC (マイナー GC): 新世代で発生するガベージ コレクション アクションを指します。 Java オブジェクトが大きいため、データは GC の最初のラウンドから逃れることができないため、マイナー GC が頻繁に使用され、一般的に回復速度が速くなります。

旧世代 GC (FULL GC/Major GC): 旧世代で発生する GC を指します。Major GC が出現する場合、コレクション戦略では少なくとも 1 つのマイナー GC が伴うことがよくあります。 ParallelScavenge コレクターの)メジャー GC には直接選択プロセスがあります)。メジャー GC の速度は、通常、マイナー GC よりも 10 倍以上遅くなります。

以上がJava メモリの割り当てとリサイクル戦略の分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。