blog

4.実行時データ領域[ヒープ]

Javaヒープは、JVM起動時に作成され、その空間サイズが確認されます。ヒープ・メモリのサイズは調整可能です。ヒープ内のオブジェクトは、メソッドが終了してもすぐには削除されず、ゴミ収集時にのみ削除され...

Jan 17, 2021 · 18 min. read
シェア

概要

  • JVMインスタンスのヒープ・メモリは1つだけであり、ヒープはJavaメモリ管理の中核領域でもあります。
  • Javaヒープは、JVMの起動時に作成され、その領域のサイズが確認されます。ヒープ・メモリのサイズは調整可能です。
  • Java仮想マシン仕様では、ヒープは物理的には非連続なメモリ空間に存在することができますが、論理的には連続したメモリ空間として扱われるべきであるとされています。
  • すべてのスレッドはJavaヒープを共有し、そこでスレッドプライベートバッファを分割することもできます。
  • ここで「ほとんど」すべてのオブジェクト・インスタンスが割り当てられます。
  • スタック・フレームは、ヒープ内のオブジェクトや配列の位置を指す参照を保持するので、配列やオブジェクトがスタックに格納されることはありません。
  • ヒープ内のオブジェクトは、メソッドが終了してもすぐには削除されません。
  • ヒープはGCがゴミ回収を行うための重点エリア

jvmの設定とjvmプロセスの表示

  • コード
public class HeapDemo {
 public static void main(String[] args) {
 System.out.println("start...");
 try {
 Thread.sleep();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 System.out.println("end...");
 }
}
  • 実行時に、-Xms10m -Xmx10m を設定します。
  • HeapDemoの構成 - Xms10mを見るために、割り当てられた10mは新しい世代の3mと古い世代の7mに割り当てられています。

ヒープ内部の内訳

  • JDK7以前:ニュージェネレーション・スペース+サービオ・スペース+パーマネント・スペース
    • ヤング・ジェネレーション・スペース:エデン・ゾーンとサービオ・ゾーンに細分化 ヤング/ニュー
    • Tenure generation Space古い/Tenure
    • Permanent Space: Perm

  • JDK 8以降:ニュージェネレーション・スペース+サービオル・スペース+メタ・スペース
    • ヤング・ジェネレーション・スペース:エデン・ゾーンとサービオ・ゾーンに細分化 ヤング/ニュー
    • Tenure generation Space古い/Tenure
    • Meta Spaceメタ

ヒープメモリサイズとOOMの設定

  • Javaヒープ領域はJavaオブジェクトのインスタンスを格納するために使用されます。ヒープのサイズはjvm起動時に設定され、"-Xmx "と"-Xms "で設定できます。
    • -Xmsはヒープの開始メモリを示すために使用され、-XX:InitialHeapSizeに相当します。
    • -Xmxはヒープの最大メモリを設定するのに使われ、-XX:MaxHeapSizeに相当します。
    • -X はjvmのランタイム・パラメータ msはメモリ・スタート
  • ヒープ・サイズが-Xmxで指定された最大メモリを超えると、OOM例外がスローされます。
  • Xmsパラメータと-Xmxパラメータは通常同じ値で設定されます。その目的は、ヒープのサイズを計算するためにヒープを再分離することなく、javaのゴミ収集メカニズムの後にヒープをクリーンアップできるようにすることで、パフォーマンスを向上させることです。
  • デフォルト

    初期メモリサイズ:物理メモリサイズ/64;

    最大メモリサイズ:物理メモリサイズ/4
  • 設定されたパラメータを確認してください:
    • 方法1:ターミナルタイプjps、次にjstat -gcプロセスID
    • 方法2:-XXを追加:+PrintGCDetails

ヒープ・メモリー・サイズの表示

すべてのJavaアプリケーションにはRuntimeクラスのインスタンスがあり、アプリケーションを実行中の環境と相互作用させることができます。現在のランタイムは、getRuntime メソッドから取得できます。

getRuntime():このメソッドは、現在の Java アプリケーションに関連付けられたインスタンスまたは Runtime オブジェクトを返します。

public class HeapSpaceInitial {
 public static void main(String[] args) {
 //Java仮想マシン内のヒープ・メモリの総量を返す
 long initialMemory = Runtime.getRuntime().totalMemory() / ;
 //Java仮想マシンが使用しようとしているヒープ・メモリの最大量を返す
 long maxMemory = Runtime.getRuntime().maxMemory() / ;
 System.out.println("-Xms : " + initialMemory + "M");//-Xms : 245M
 System.out.println("-Xmx : " + maxMemory + "M");//-Xmx : 3641M
 System.out.println("システム・メモリー・サイズは次のとおりである。+ initialMemory * 24 + "G");//システム・メモリ・サイズはG
 System.out.println("システム・メモリー・サイズは次のとおりである。+ maxMemory * 4.0 / 1024 + "G");//システム・メモリ・サイズは625G
 try {
 Thread.sleep();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
}

ヒープ・サイズ解析

OOM

java.lang.OutOfMemoryError:Javaのヒープ領域

/**
 * -Xms600m -Xmx600m
 */
public class OOMTest {
 public static void main(String[] args) {
 ArrayList<Picture> list = new ArrayList<>();
 while(true){
 try {
 Thread.sleep(20);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 list.add(new Picture(new Random().nextInt()));
 }
 }
}
class Picture{
 private byte[] pixels;
 public Picture(int length) {
 this.pixels = new byte[length];
 }
}

若い世代と古い世代

  • JVMに格納されるJavaオブジェクトは、2つのカテゴリーに分類できます:
    • ひとつは、ライフサイクルの短い一時的なオブジェクトで、非常に素早く作成され、破棄されます。
    • もう1つのタイプのオブジェクトは、ライフサイクルが非常に長く、場合によってはJVMのライフサイクルと一致することもあります。
  • ジャワヒープのエリアは、さらに若い世代と古い世代に分けられます。
  • ここで、若い世代はエデン空間、サバイバー0空間、サバイバー1空間に分けられます。
  • ヒープ構造における新世代と旧世代の比率の設定
    • デフォルトの-XX:NewRatio=2は、新世代が1、旧世代が2、新世代がヒープの1/3を占めることを意味します。
    • XX:NewRatio=4を変更すると、新世代が1、旧世代が4、新世代がヒープの1/5を占めます。

  • hotSpotでは、Edenスペースと他の2つのSurvivorスペースのデフォルトの比率は8:1:1です。開発者は、-XX:SurvivorRatio=8のように、-XX:SurvivorRatioオプションでスペース比率を調整できます。
  • ほとんどのJavaオブジェクトは、エデン・エリアで新しく作成されます。
  • Javaオブジェクトの大半は新世代で破壊されます。
  • オプション -Xmn を使用すると、新世代の最大メモリ・サイズを設定できます。
/**
 * -Xms600m -Xmx600m
 *
 * -XX:NewRatio 新世代と旧世代の比率を設定する。デフォルト値は2である。.
 * -XX:SurvivorRatio 新世代のエデン領域とサバイバー領域の比率を設定する。デフォルト値は8である。
 * -XX:-UseAdaptiveSizePolicy 適応的メモリ割り当てポリシーをオフにする'-' ,'+' 
 * -Xmn:新しい世代のための空間のサイズを設定する。
 *
 */
public class EdenSurvivorTest {
 public static void main(String[] args) {
 System.out.println("ちょっと寄り道だ。~");
 try {
 Thread.sleep();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
}

オブジェクト割り当てプロセスの図解

概要

新しいオブジェクトのためにメモリを割り当てることは、非常に厳密で複雑な作業であり、JVM設計者は、メモリを割り当てる方法と場所だけでなく、メモリ割り当てアルゴリズムがメモリ再利用アルゴリズムと密接に関連しているため、メモリ再利用後にGCがメモリ空間にメモリ断片を作成するかどうかも考慮する必要があります。

  • 1.新しいオブジェクトは、まずエデン領域を配置します。このエリアにはサイズ制限があります。
  • 2.新しいオブジェクトが作成され、Eden領域がいっぱいになると、Minor GCが起動し、Eden内の他のオブジェクトから参照されなくなったオブジェクトを破棄します。その後、新しいオブジェクトをEden領域にロードします。
  • 3.次に、エデンに残っているオブジェクトを生存者0に移動します。
    1. ゴミ回収が再び発動した場合、この時点で生存者0に生き残ったものは、回収されなければ生存者1に入れられます
  • 5.再びゴミ収集に回った場合、この時点でサバイバー0に戻され、再びサバイバー1に回されます。
  • 6.デフォルトは15回以上引退エリアに移動するには、回数を設定することができ、デフォルトは15回です。パラメータ:-XX:MaxTenuringThreshold=を設定することができます。
  • 7.高齢者エリアでは、比較的のんびり。高齢者エリアがメモリ不足になると、高齢者エリアのメモリクリーンアップのためにGC:Major GCが再度起動まとめ
  1. 生存者s0,s1の場合:コピーの後、交換があり、空の人は、次のようになります。
  2. ゴミ収集について:新生児ゾーンでは頻繁に収集、老年ゾーンではほとんど収集せず、常設ゾーン/メタスペースではほとんど収集せず。

オブジェクト割り当ての特殊なケース

コード・デモ

public class HeapInstanceTest {
 byte[] buffer = new byte[new Random().nextInt()];
 public static void main(String[] args) {
 ArrayList<HeapInstanceTest> list = new ArrayList<HeapInstanceTest>();
 while (true) {
 list.add(new HeapInstanceTest());
 try {
 Thread.sleep(10);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 }
}

Minor GCメジャーGC、フルGC

JVMがGCを実行するとき、常に上記の3つのメモリ領域を一緒に再利用するわけではなく、ほとんどの場合、再利用は新しい世代を指します。

hotSpot VMの実装では、GCは再利用される領域によって2つのタイプに分けられます:1つは部分的なコレクションで、もう1つはヒープ全体のコレクションです。

  • 部分収集:Javaヒープ全体の完全な収集ではないゴミ収集。さらに次のように分けられます:
    • 新世代コレクション: 新世代だけのゴミコレクション
    • 旧世代の収集:旧世代のゴミ収集のみ
      • 現在、CMS GCだけが古い世代の振る舞いを個別に収集します。
      • 多くの場合、MajorGC は FullGC と混同されて使用され、旧世代収集とヒープ全体収集を明確に区別します。
    • 混合収集:新世代全体と旧世代の一部のゴミ収集。
      • 現在、G1GC以降はこのような挙動になります。
  • ヒープ全体の収集:Javaヒープとメソッド領域全体のゴミ収集

若い世代のGCトリガー・メカニズム

  • マイナーGCは若い世代がスペース不足になったときに発動され、若い世代が一杯になるとエデン世代が一杯になり、サバイバー世代が一杯になってもGCは発動されません。
  • Javaのオブジェクトはほとんど生まれては死ぬので、MinorGCは非常に頻繁に発生します。
  • マイナーGCがSTWをトリガーし、他のユーザーのスレッドを一時停止し、ユーザー・スレッドが実行を再開する前にゴミ収集が終了するのを待ちます。

古いGC(メジャーGC)/Full GC)トリガー・メカニズム

  • メジャーGCまたはフルGCは、オブジェクトが古い世代から消滅したときに発生します。
  • Major GCが発生すると、多くの場合、少なくとも1回のMinor GCを伴います。
    • つまり、老年期に十分な空き容量がない場合、まずMinor GCを発動しようとし、それでもまだ十分な空き容量がない場合、Major GCを発動することになります。
  • メジャーGCは一般的にマイナーGCの10倍以上遅く、STW時間も長くなります。
  • Major GC後もメモリ不足の場合、OOMが報告されます。

Full GCトリガー・メカニズム

  • System.gc()を呼び出すと、システムはFull GCの実行を提案しますが、必ずしも
  • 旧世代での領域不足
  • メソッド領域の領域不足
  • MinorGCを通過して老年期に入る平均サイズは、老年期に利用可能なメモリよりも大きい
  • エデン領域、サバイバーS0領域からS1領域にコピーする場合、オブジェクトのサイズは、Toスペースの利用可能なメモリに起因し、その後、オブジェクトは、古い時代にダンプされ、古い時代の利用可能なメモリは、オブジェクトのサイズよりも小さいです。
  • エデン、サバイバー0からサバイバー1へコピーする際、オブジェクトのサイズが利用可能なToSpaceメモリより大きい場合、オブジェクトは旧時代にダンプされ、旧時代の利用可能なメモリはオブジェクトのサイズより小さくなります。
  • FullGCは、開発やチューニングにおいて、できるだけ避けるべきものです。

コード・デモ ヤングGC>Full GC -> OOM

/** テスト GC分割生成 リクレイム
 * テスト MinorGC , MajorGC, FullGC
 * -Xms9m -Xmx9m -XX:+PrintGCDetails
 */
public class GCTest {
 public static void main(String[] args) {
 int i = 0;
 try {
 List<String> list = new ArrayList<>();
 String a = "testGC";
 while (true) {
 list.add(a);
 a = a + a;
 i++;
 }
 } catch (Throwable t) {
 t.printStackTrace();
 System.out.println("探索回数は次のとおりである。+ i);
 }
 }
}

ヒープ空間置換のアイデア

なぜJavaのヒープをサブ生成する必要があるのですか?置換なしには動かないのですか?

  • オブジェクトによってライフサイクルが異なることが研究されています。
    • 新世代:エデン、サバイバー・コンポジションがあり、常に空。
    • 旧世代:何度も生き残り、まだ生きているオブジェクトを新世代に保存
  • 実際には、サブ生成しないことは全く問題ありません、サブ生成する唯一の理由は、GCのパフォーマンスを最適化することです。世代がない場合は、ピースのすべてのオブジェクトは、人々の学校のように教室に閉じ込められています。GCの時間は、オブジェクトが使用されていない見つけるために、それはスキャンするすべての領域のヒープ上になるので、多くのオブジェクトが死んで生まれている、世代は、新しく作成されたオブジェクトは、特定の場所に、GCが最初に回復するために "死んだ "オブジェクト領域のこの部分を格納する場合、ので、それは多くのスペースを解放します。GCが最初にリサイクルされる領域に "死にかけ "オブジェクトを格納するとき、これは多くのスペースを解放します。

メモリー割り当て戦略

  • そのオブジェクトがエデンで生まれ、最初のマイナーGC以降も生存しており、サバイバーに収容できる場合、そのオブジェクトはサバイバーのスペースに移動され、年齢が1に設定されます。古い年齢
    • オブジェクトが古い年齢に昇格する年齢のしきい値は、オプション -XX:MaxTenuringThreshold で設定できます。
  • 各年齢層への割り当ての原則は以下の通りです:
    • エデンへの優先割り当て
    • 大型オブジェは直接オールドエイジに割り当て
    • 長期生存オブジェクトはオールドエイジに割り当て
    • 動的オブジェクト年齢判定
      • サバイバー領域内の同じ年齢のオブジェクトのサイズの合計がサバイバー領域の半分より大きい場合、その年齢以上のオブジェクトは直接オールドエイジに入ることができます。MaxTenuringThreshold で要求される年齢を待つ必要はありません。
  • スペース割り当て保証
    • -XX: HandlePromotionFailure
    • マイナーGCの場合、VMは旧世代の最大利用可能連続スペースが新世代の全オブジェクトの合計スペースより大きいかどうかをチェックします。

コード・デモ

/** テスト:大きなオブジェクトは古い時代に直行する
 * -Xms60m -Xmx60m -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+PrintGCDetails
 */
public class YoungOldAreaTest {
 // Neo 20m, Eden 16m, s0 2m, s1 2m
 // 古い時代 40m
 public static void main(String[] args) {
 //Eden ゾーンはバッファを保持できない 老朽化を促進する
 byte[] buffer = new byte[ * 20];//20m
 }
}

オブジェクトへのメモリ割り当て:TLAB

TLABが存在する理由

  • ヒープはスレッド共有領域であり、どのスレッドもヒープ内の共有データにアクセスできます。
  • JVMではオブジェクト・インスタンスが非常に頻繁に生成されるため、同時実行環境でヒープからメモリー空間を分割することはスレッドセーフではありません。
  • 複数のスレッドが同じアドレスで動作するのを避けるために、ロックなどのメカニズムを使用する必要があります。

TLABとは

  • ゴミ収集の観点ではなく、メモリ・モデルの観点からエデン領域の分割を続けると、JVMは各スレッドにプライベート・キャッシュ領域を割り当てます。
  • 複数のスレッドが同時にメモリ割り当てを行う場合、TLABを使用することで、一連のスレッド安全性以外の問題を回避でき、メモリ割り当てのスループットも向上するため、このメモリ割り当て方法を高速割り当て戦略と呼ぶことができます。
  • すべてのOpenJDK派生JVMは、TLAB設計を提供します。

説明

  • すべてのオブジェクト・インスタンスがTLABでうまくメモリを割り当てられるわけではありませんが、JVMはメモリ割り当ての最初の選択肢としてTLABを使用します。
  • 開発者はTLABスペースを有効にするかどうかを-XX:UseTLABで設定します。
  • デフォルトでは、TLAB空間のメモリは非常に小さく、エデン空間全体の1%しか占めません。-XX:TLABWasteTargetPercentで、エデン空間のうちTLAB空間が占める割合を設定します。
  • オブジェクトがTLAB空間でのメモリ確保に失敗すると、JVMはエデン空間で直接メモリを確保するロック機構を使用することで、データ操作のアトミック性を確保しようとします。

TLABArgsTest プロセス ID のターミナル タイプ jsp jinfo -flag UseTLAB 54666

TLABオブジェクト割り当てプロセス

要約ヒープ空間のパラメータ化

  • -XX:PrintFlagsInitial: すべてのパラメータのデフォルト初期値を表示します。
  • -XX:PrintFlagsFinal:すべてのパラメータの最終値を参照します。
  • 特定のパラメータのコマンドを表示します:
    • jps: 現在実行中のプロセスを表示
    • jinfo -flag SurvivorRatio プロセス ID: 新世代の Eden と S0/S1 スペースの比率を表示します。
  • -Xms: 初期ヒープ空間メモリー
  • -Xmx: 最大ヒープ空間メモリー
  • -Xmn: 新世代サイズの設定
  • -XX:NewRatio:ヒープ構造における旧世代に対する新世代の比率を設定します。
  • -XX:SurvivorRatio:新世代のエデンとS0/S1スペースの比率を設定します。
  • -XX:MaxTenuringThreshold:新しい世代のゴミの最大年齢を設定します。
  • -XX:+PrintGCDetails:詳細なGC処理ログを出力します。
  • gcの簡単な情報を印刷します:
    • 1. -XX:+PrintGC
    • 2. -verbose:gc
  • -XX:HandlePromotionFailure:スペース割り当て保証を設定するかどうか

オブジェクトの割り当てはヒープだけ?

  • JITコンパイラの開発とエスケープ分析技術の漸進的な成熟により、スタック上の割り当て、スカラー置換最適化技術は、いくつかのマイクロ秒の変化につながる、ヒープに割り当てられたすべてのオブジェクトは、徐々に絶対的な少なくなります。
  • エスケープ解析の結果、オブジェクトがメソッドからエスケープしていないことが判明した場合、オブジェクトがスタックに割り当てられるように最適化される特殊なケースがあります。
  • TaoBaoVMは、革新的なGCIH技術は、オフヒープを達成するために、ヒープからJavaオブジェクトの長いライフサイクルを達成するためにヒープ外に移動し、GCは、GCIH内のJavaオブジェクトを管理することはできませんGCの回復の頻度の減少を達成するために、GCのリサイクル目的の効率を向上させるために

エスケープ解析

  • エスケープ分析の基本的な動作は、オブジェクトの動的スコープを分析することです。
  • オブジェクトがメソッド内で定義され、そのオブジェクトがメソッド内でのみ使用される場合、エスケープは発生しないとみなされます。
  • オブジェクトがメソッド内で定義され、それが外部メソッドから参照される場合、エスケープが発生したとみなされます。
  • エスケープ分析が行われたかどうかを判断する簡単な方法は、新しいオブジェクトの実体がメソッドの外で呼び出される可能性があるかどうかを確認することです。

脱出分析コードの分析

/**
 * エスケープ解析
 *
 * エスケープ解析が発生したかどうかを判断する簡単な方法は、newオブジェクト・エンティティがメソッドの外部で呼び出される可能性があるかどうかを確認することである。
 */
public class EscapeAnalysis {
 public EscapeAnalysis obj;
 /*
 メソッドがEscapeAnalysisオブジェクトを返すと、エスケープが発生する。
 */
 public EscapeAnalysis getInstance(){
 return obj == null? new EscapeAnalysis() : obj;
 }
 /*
 メンバー属性に値を割り当てると、エスケープが発生する
 */
 public void setObj(){
 this.obj = new EscapeAnalysis();
 }
 //考えてみよう:もし現在のobj参照がstaticと宣言されていたらどうだろう?それでもエスケープは起こるだろう。
 /*
 オブジェクトのスコープは現在のメソッドに対してのみ有効で、エスケープは行われない。
 */
 public void useEscapeAnalysis(){
 EscapeAnalysis e = new EscapeAnalysis();
 }
 /*
 メンバ変数の値を参照すると、エスケープが発生する
 */
 public void useEscapeAnalysis1(){
 EscapeAnalysis e = getInstance();
 //getInstance().xxx()同じエスケープが起こる
 }
}

パラメータ設定

  • JDK バージョン 6u23 以降、HotSpot ではエスケープ解析がデフォルトで有効になっています。
  • それ以前のバージョンを使用している場合、開発者は
    • -XX:DoEscapeAnalysisで明示的にエスケープ解析を有効にします。
    • -XX:+PrintEscapeAnalysisでエスケープ解析のフィルタリング結果を見ることができます。

もし開発中にローカル変数を使えるのであれば、メソッドの外では使わないようにしましょう。

コードの最適化

  • 1.スタックへの割り当て
    • オブジェクトへのポインタが決してエスケープしないようにオブジェクトがサブルーチン内で割り当てられる場合、ヒープ割り当てをスタック割り当てに変換すると、そのオブジェクトはヒープ割り当てではなくスタック割り当ての候補になる可能性があります。
    • JIT コンパイラは、エスケープ解析の結果に基づいて、コンパイル時にスタックへの割り当てを最適化することができます。割り当てが完了すると、呼び出しスタック上で実行が続行され、最終的にスレッドが終了し、スタック領域が取り戻され、ローカル変数オブジェクトが取り戻されます。これにより、ゴミ収集の必要がなくなります。
/**
 * オンスタック・アロケーション・テスト
 * -Xmx1G -Xms1G -XX:-DoEscapeAnalysis -XX:+PrintGCDetails
 */
public class StackAllocation {
 public static void main(String[] args) {
 long start = System.currentTimeMillis();
 for (int i = 0; i < ; i++) {
 alloc();
 }
 // 実行時間を表示する
 long end = System.currentTimeMillis();
 System.out.println("費やされる時間は次のとおりである。+ (end - start) + " ms");
 // ヒープ・メモリー内のオブジェクトの数を見やすくするために、スレッド・スリープの
 try {
 Thread.sleep();
 } catch (InterruptedException e1) {
 e1.printStackTrace();
 }
 }
 private static void alloc() {
 User user = new User();//エスケープは発生していない
 }
 static class User {
 }
}
  • 2.同期の省略
    • オブジェクトが単一のスレッドからのみアクセス可能であることが判明した場合、そのオブジェクトに対する操作は同期を考慮せずに実行できます。
    • 同期ブロックを動的にコンパイルする場合、JITコンパイラはエスケープ解析を使用して、同期ブロックで使用されるロック・オブジェクトが、他のスレッドに解放されることなく1つのスレッドからのみアクセスできるかどうかを判断できます。そうでない場合、JITコンパイラはブロックをコンパイルするときにコードのこの部分を非同期化します。これにより、同時実行性とパフォーマンスが大幅に向上します。この非同期化のプロセスは同期省略と呼ばれ、ロック除去としても知られています。
/** * 同期省略注 */ public class SynchronizedTest { public void f() { Object hollis = new Object(); synchronized(hollis) { System.out.println(hollis); } } //コードはホリス・オブジェクトをロックしているが、ホリス・オブジェクトのライフサイクルはf()メソッドの中だけである。 //は他のスレッドからアクセス制御されないため、JITコンパイル段階で最適化される。 //に最適化されている。 public void f2() { Object hollis = new Object(); System.out.println(hollis); } }
  • 3.分離オブジェクトまたはスカラー置換
    • 連続したメモリ構造として存在する必要がなく、アクセスすることができるオブジェクトがあります。代わりにCPUのレジスタに格納されます。
    • スカラーとは、もはや小さなデータに分解することができないデータの一部です。
    • 分解できるデータは、集約量と呼ばれます。Javaのオブジェクトは、他の集約量やスカラーに分解できるので、集約量です。
    • スカラー置換パラメータ:-XX:EliminateAllocations、デフォルトon
    public class ScalarTest {
     public static void main(String[] args) {
     alloc(); 
     }
     public static void alloc(){
     Point point = new Point(1,2);
     }
    }
    class Point{
     private int x;
     private int y;
     public Point(int x,int y){
     this.x = x;
     this.y = y;
     }
    }
    
    スカラー置換後の上記のコードは、次のようになります。
    public static void alloc(){
     int x = 1;
     int y = 2;
    }
    

ご覧のように、集計された量であるポイントは、脱出分析によって脱出しなかったことが判明した後、2つのスカラーに置き換えられました。では、スカラー置換のメリットは何でしょうか?それは、ヒープメモリの占有を大幅に削減できることです。一度、オブジェクトを作成する必要がないため、ヒープメモリを確保する必要がなくなります。

スカラー置換は、スタック上の割り当てのための良い基礎を提供します。

脱出分析のまとめ

  • エスケープ分析に関する論文は1999年に発表されましたが、実装されたのはJDK 1.6からで、現在でもその技術はあまり成熟していません!
  • その根本的な理由は、エスケープ解析の性能消費が彼の消費よりも高くなるという保証がないからです。エスケープ分析は、スカラー置換、スタック上の割り当て、およびロック除去を行うことができますが。しかし、エスケープ分析自体も一連の複雑な分析を実行する必要があり、実際には比較的時間のかかるプロセスです!
  • 極端な例としては、脱出分析の結果、脱出しないオブジェクトが存在しないことが判明した場合です。それでは、この脱出分析のプロセスは無駄になってしまいます!
  • この技法はあまり成熟していませんが、オンザフライ・コンパイラの最適化技法において非常に重要なツールです。

ヒープ・サマリー

  • 若い世代とは、物体が生まれ、成長し、そして死んでいく場所であり、物体が創造され、応用され、最終的にゴミ回収業者に回収され、その生涯を終える場所なのです。
  • 古い世代は、長いライフサイクルのオブジェクトを防ぎます。それは通常、サバイバー領域からフィルターされたコピーであるJavaオブジェクトです。もちろん、通常のオブジェクトがTLABに割り当てられることを知っている特殊なケースもあり、オブジェクトが大きければ、JVMはエデンの他の場所に直接割り当てようとします。オブジェクトが大きすぎて、新しい世代で十分に長い連続した空き領域を見つけることが完全に不可能な場合、JVMは古い世代に直接割り当てます。
  • 一般に、MajorGCよりもMinorGCの方が発生頻度が高く、すなわち、ゴミの回収は若い世代よりも古い世代の方が発生頻度が低い。

オブジェクトのメモリ割り当ての2つの方法

オブジェクトの領域を確保する作業は、Javaヒープから一定値のメモリチャンクを分割することと同じです。

  • ポインタの衝突

    Javaのヒープ内のメモリが絶対的に規則的で、使用済みメモリが片側に、空きメモリがもう片側に配置され、ポインタが区切り点として中央に配置されていると仮定すると、割り当てられたメモリは、オブジェクトのサイズに等しい距離だけ空き領域側にポインタを移動させるだけであり、この割り当てを「ポインタ衝突」と呼びます。"

  • 空きリスト

    Javaのヒープ内のメモリが規則的でなく、使用済みメモリと空きメモリが互いに点在している場合、単純にポインタの衝突を実行する方法がないため、仮想マシンは、どのメモリ・ブロックが使用可能かのリストを保持し、割り当て時にオブジェクト・インスタンス間で分割するのに十分な大きさの領域をリストから見つけ、「空きリスト」と呼ばれるリスト上のレコードを更新する必要があります。"空きリスト"



























Read next

手書き

コンピュータは2進数のストレージを使用し、0.1は2進数では無限にループする数値の山であり、多くの10進小数も同様に無限にループします。そして、jsは浮動小数点規格を使用するため、一部の数値が切り取られます。これらの無限にループする数値は切り取られ、精度の問題が発生します。この結果、0

Jan 16, 2021 · 18 min read