目次
1. オブジェクトの作成とメモリ割り当て
仮想マシンは新しいバイトコード命令を検出すると、まずこのタイプがロードされているかどうかを確認します。そうでない場合は、クラスのロードプロセスに進みます。
クラス読み込みチェックに合格すると、仮想マシンは新しいオブジェクトにメモリを割り当てます。
オブジェクトに必要なメモリサイズはクラスロードが完了した後に決定できるため、ヒープから一定サイズのメモリを分割するだけで済みます。
1. ヒープからメモリを分割する方法
Java ヒープ メモリを管理するには 2 つの方法があります。
- ポインタの衝突: ヒープ メモリは完全に規則的で、一方の側にデータが保存され、もう一方の側には空きデータがあり、中央のポインタによって区切られています。メモリの割り当てにはポインタを移動するだけで済みます
- フリー リスト: 使用済みメモリと空きメモリが混在しているため、仮想マシンはどのメモリ ブロックが使用可能であるかを記録するリストを維持する必要があります。メモリを割り当てるときにリストから分割します。
仮想マシンがどのメモリ管理方法を選択するかは、Java ヒープが正規かどうかに関係します。ヒープが正規かどうかは、仮想マシンが使用するガベージコレクタに「領域の圧縮と並べ替え」機能があるかどうかによって決まります。
- Serial や ParNew などのスペース圧縮とソートを備えたコレクターを使用する場合は、シンプルで効率的なポインター衝突が使用されます。
- スイープ アルゴリズムに基づくコレクターである CMS を使用する場合、理論的には空きリストを使用してメモリを割り当てる必要があります。
2. メモリを分割して同時実行の安全性を確保する方法
オブジェクトの作成は仮想マシンで非常に頻繁に行われる操作です。オブジェクトの作成が処理されない場合は、メモリが A に割り当てられているものの完了しておらず、B が元のメモリ状態を使用してメモリを割り当てている可能性があります。
仮想マシンは、スレッドの安全性を確保するために 2 つの方法を使用します。
-
メモリ領域を割り当てるアクションを同期します。(具体的には楽観ロック+失敗リトライの方法を使用します)
-
メモリ割り当てアクションは、スレッドごとに異なる空間で実行されます。
各スレッドは、
堆中的Eden区
小さなメモリを「ローカル スレッド割り当てバッファ (TLAB)」として事前に割り当て、线程优先在自己的TLAB中分配内存
不足した場合に同期します。
仮想マシンで TLAB を有効にするかどうかは、次のパラメータによって設定されます。
-XX:+/-TLAB を使用する
3. メモリ確保完了後の作業
メモリが割り当てられ、仮想マシンは割り当てられた領域を処理し、オブジェクト ヘッダーを除くすべてをゼロに初期化し、オブジェクト ヘッダーを埋めます。
この時点で、仮想マシンのオブジェクトが作成されます。しかし、これは空のオブジェクトであり、Java コード内のコンストラクターがまだ実行されていないため、オブジェクトはまだ初期化されていません。
新しい命令が実行され、<init>() コンストラクターが実行された後でのみ、使用可能な Java オブジェクトが完全に作成されます。
4. Java オブジェクトの作成プロセスを要約する
Java オブジェクトの作成プロセス:
型のチェック、メモリの割り当て、ゼロ値の初期化、オブジェクト ヘッダーの設定、コンストラクター メソッドの実行
-
新しいキーワードが見つかった場合は、まず、この命令のパラメータが定数プール内でこのタイプのシンボル参照を見つけることができるかどうかを確認します。
-
見つかった場合は、その型がロードされ初期化されているかどうかを確認します。
-
見つからない場合は、クラスがまだロードされていないことを意味するため、最初にクラスのロード処理が実行されます。
-
クラスロードチェックフェーズが完了すると、このクラスのオブジェクトが占有する必要があるメモリサイズが決定されます。JVM はオブジェクトにメモリを割り当てます
-
メモリの割り当てには、次の 3 つの詳細が含まれます。
-
メモリ割り当ての 2 つの方法: ポインタ衝突とフリー リスト
-
メモリ割り当てのスレッド セーフ: 楽観的ロック + 失敗時の再試行
-
小さなオブジェクトの場合、スレッドはまずヒープ内の独自の「スレッド ローカル割り当てバッファ TLAB」にメモリを割り当てます。
大きなオブジェクトの場合は、古い世代に直接入れることを選択できます。
-
-
-
割り当てられたメモリ空間を処理し、オブジェクトのメンバー変数がデフォルト値を持つように、オブジェクト ヘッダーを除くすべてをゼロ値に初期化します。
-
オブジェクト ヘッダーを入力し、オブジェクト タイプ、世代年齢、バイアス ロックを有効にするかどうかを設定します。ハッシュコードは最初の呼び出し時に遅延してロードされます。
-
オブジェクトのコンストラクターメソッドを実行する
その後、使用可能な Java オブジェクトが取得されます。
5. オブジェクトメモリ割り当ての基本戦略
全体:
- 新しいオブジェクトは最初に Eden エリアに割り当てられます
- 大きなオブジェクトは古い世代に直接入ります
- 長寿命のオブジェクトは古い世代に入る
1. 新しいオブジェクトは最初に Eden 領域に割り当てられます。
Eden 領域に十分なスペースがない場合、マイナー GC がトリガーされます。
- 新世代の GC 中に、エデン領域に多くの生存オブジェクトがあり、生存領域に収まらない場合、新世代のオブジェクトは、割り当て保証メカニズムを通じて古い世代にコピーされます。
- 古い世代に十分なスペースがない場合、フル GC がトリガーされ、時間がかかります。
2. なぜ大きな物体は直接老年期に入るのか?
ラージ オブジェクトは、大量の連続メモリ スペースを必要とするオブジェクト (文字列や配列など) です。
考慮すべき点は 2 つあります。
- おそらく新世代のエデン領域はメモリ容量が不足しており、事前にGCをトリガーする必要があると思われます。オブジェクトが大きいとメモリ不足になる可能性が高くなるためです。
- 将来の新世代 GC では、ラージ オブジェクトが生き残った場合、生存領域にそれを収容できない可能性があり、割り当て保証メカニズムを通じて古い世代に入る可能性があります。したがって、古い世代に直接入れることを選択できます。
パラメータが 1 つあります。
-XX:PretenureSizeThreshold
これより大きい番号は古い世代で直接割り当てられます。デフォルトは 0 で、古い世代に直接割り当てられないことを意味します。
3. 長期間存続するオブジェクトは古い世代に入ります。
各オブジェクトには世代年齢が保存され、GC を乗り越えるたびに世代年齢が +1 されます。
世代年齢がしきい値を超えると、旧世代に昇格します。
このパラメータは調整できます。デフォルトは 15 です。
-XX:MaxTenuringThreshold
HotSpot はここで動的な世代年齢メカニズムを使用します。これは世代コレクション理論で文書化されています。
2. オブジェクトメモリのレイアウト
HotSpot 仮想マシンでは、ヒープ メモリ内のオブジェクトのストレージ レイアウトは 3 つの部分に分割できます。
- オブジェクトヘッダー (Header)
- インスタンスデータ
- 位置合わせパディング
1. オブジェクトヘッダー
オブジェクトのヘッダー部分には、次の 2 種類の情報が含まれます。
- マークワード: ハッシュ値、CG 生成経過時間、ロックステータスフラグ、スレッドが保持するロック、偏ったスレッド ID、偏ったタイムスタンプなど、オブジェクト自体の実行時データを保存するために使用されます。
- klass word: 型ポインタ、つまり、その型メタデータへのオブジェクトのポインタ。JVM はこのポインタを使用して、オブジェクトがどのクラスのインスタンスであるかを判断します。
オブジェクトが配列の場合、オブジェクト ヘッダーにはその長さも格納する必要があります。そうしないと、配列オブジェクトのサイズを決定できません。
2. インスタンスデータ
コード内で定義された情報や親クラスから継承された情報など、オブジェクトの有効な情報を格納します。保管順序には次の 2 つの効果があります。
- コードの記述順序
- 仮想マシンの割り当て戦略
デフォルトの割り当て戦略では、同じ幅のフィールドが割り当てられ、一緒に保存されます。この条件に基づいて、親クラスの変数はサブクラスの変数よりも優先されます。
HotSpot がこのパラメーターをオンにすると、サブクラス内のより狭い変数を親クラス変数の隙間に挿入でき、スペースを少し節約できます。
+XX:CompactFields:true //デフォルトは true
3. 位置合わせと塗りつぶし
この部分はプレースホルダーとして機能します。
HotSpot 仮想マシンの自動メモリ管理システムでは、オブジェクトの開始アドレスが 8 バイトの整数倍である必要があるため、オブジェクトのサイズは 8 バイトの整数倍である必要があります。
オブジェクトのヘッダ部分は8バイトか16バイトになるように設計されていますが、インスタンスデータ部分の長さは保証できませんので、8バイト未満の場合はアライメントパディングで補ってください。
3. オブジェクトアクセスの位置付け
オブジェクトのアクセス配置方法は、スタック上の参照がヒープ上のオブジェクトをどのように指すかを指します。
Javaプログラムでは通过栈上的 reference 数据来操作堆上的具体对象
、この参照型は実装方法を規定せず参照として固定されるだけです。
したがって、仮想マシンはオブジェクトアクセス方法を自由に実装することができる。主流の方法は次の 2 つです。
- ハンドル アクセスを使用します。
- メモリの一部はハンドル プールとして Java ヒープに分割されます
- 参照に格納されているオブジェクトのハンドル アドレス
- ハンドルには、オブジェクトの「インスタンスデータ」と「タイプデータ」の特定のアドレス情報が含まれます。
- 直接ポインター アクセスを使用します。
- オブジェクトの特定のアドレスは参照に直接保存されます。
どちらの方法にも、次のような独自の利点があります。
- ハンドル アクセスの最大の利点は、リファレンスに安定したハンドル アドレスが格納されることであり、オブジェクトが移動されたとき (ガベージ コレクションが発生した場合など)、リファレンスは変更されず、ハンドル内のインスタンス データ ポインタのみが変更されることです。
- 直接ポインター アクセスの利点は、高速であり、ポインターの位置決めのオーバーヘッドを節約できることです。オブジェクト アクセス操作は非常に頻繁に行われます。これは、HotSpot で使用されるソリューションでもあります。
特定のオブジェクト アクセスの位置決め方法は、GC のタイプに関連します。