blog

JVM-ランタイムのデータ領域-オブジェクトのメモリレイアウトとアクセス位置のインスタンス化

VMは新しい命令に出会うと、まず命令の引数がメタスペースの定数プール内のクラスへのシンボリック参照を見つけられるかどうか、そしてシンボリック参照で表されるクラスがロード、解決、初期化されているかどうか...

Apr 22, 2020 · 5 min. read
シェア

オブジェクトのインスタンス化

オブジェクトの作成

  • new:最も一般的な方法

    変形1:Xxxの静的メソッド

    変換2:XxBuilder/XxoxFactoryの静的メソッド

  • クラスの newInstance(): 反射的な方法で、空のパラメータでコンストラクタを呼び出すことができます。

  • コンストラクタのnewInstance:java.lang.reflect.Constructorにある反射的な方法で、空のパラメータでコンストラクタを呼び出すことができます。

  • クローンの使用

  • サードパーティライブラリ Objenesis

オブジェクトを作成する手順

オブジェクトに対応するクラスがロードされ、リンクされ、初期化されているかどうかを判断します。

仮想マシンが新しい命令に遭遇すると、まず命令の引数がメタスペースの定数プール内のクラスへのシンボリック参照を見つけられるかどうかをチェックし、シンボリック参照で表されるクラスがロード、解析、初期化されているかどうかをチェックします。

もし見つからない場合、2親委譲モードでは、ClassLoader+Package+ClassNameをKeyとして、対応する.classファイルを見つけるために現在のクラス・ローダが使用されます。ファイルが見つからない場合は ClassNotFoundException がスローされ、見つかった場合はクラスがロードされ、対応する Class オブジェクトが生成されます。

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

まず、オブジェクトが占有する領域のサイズを計算し、新しいオブジェクトのためにヒープ内のメモリブロックを分割します。 インスタンス・メンバ変数が参照変数である場合、参照変数だけの領域、つまり4バイトの大きさを確保すれば十分です。

  • メモリが規則的ならポインタ衝突を使用

    メモリに規則性がある場合、仮想マシンはポインタ衝突方式でオブジェクトにメモリを割り当てます。つまり、すべての使用済みメモリが片側に、空きメモリがもう片側にあり、真ん中にあるポインタを分割点の指標とします。 メモリの割り当ては、オブジェクトのサイズに等しい距離だけポインタを空き側に移動させるだけです。コンパクション・アルゴリズムに基づくゴミ収集器が Serial または ParNew の場合、仮想マシンはこの割り当て方法を使用します。そのため、COMPACT プロシージャを持つコレクタを使用する場合は、一般的にポインタの衝突が使用されます。
  • メモリが規則的でない場合、VMはリストを維持し、フリー・リスト割り当てを使用する必要があります。

    メモリが規則的でなく、使用済みメモリと未使用メモリがインターリーブされている場合、仮想マシンはオブジェクト用のメモリを確保するためにフリー・リスト方式を使用します。つまり、仮想マシンは利用可能なメモリ・ブロックのリストを保持し、割り当て時にリストからオブジェクト・インスタンスに割り当てるのに十分な大きさのブロックを見つけてリストを更新します。この割り当てを「空きリスト」と呼びます。

注:割り当て方法の選択は、Javaのヒープが規則的かどうかによって決まり、Javaのヒープが規則的かどうかは、使用するゴミ収集器が圧縮および照合機能を備えているかどうかによって決まります。

同時実行セキュリティの処理

オブジェクトの作成は非常に頻繁に行われるため、VMは並行性の問題に対処する必要があります。VMが並行性の問題を解決する方法は2つあります:

  • CAS失敗リトライ、領域ロック:ポインタ更新操作のアトミック性を確保;
  • すなわち、各スレッドには、ローカル・スレッド・アロケーション・バッファ(LTAB)と呼ばれるJavaヒープ内の小さなメモリ・ブロックが事前に割り当てられ、仮想マシンによって使用されるかどうかが決まります。
割り当て空間の初期化

Javaでは、以下の4つの操作でオブジェクトのプロパティに値を割り当てます:

  • 明示的な初期化
  • コードブロックでの初期化
  • コンストラクタでの初期化
  • コンストラクタでの初期化

メモリ割り当ての最後に、仮想マシンは割り当てられたすべてのメモリ空間をゼロ値に初期化します。このステップによって、オブジェクトのインスタンス・フィールドが初期値を割り当てることなくJavaコードで直接使用できるようになり、プログラムがこれらのフィールドのデータ型に対応するゼロ値にアクセスできるようになります。

オブジェクトのヘッダーを設定

オブジェクトが属するクラス、オブジェクトのHashCode、オブジェクトのGC情報、ロック情報などのデータは、オブジェクトのオブジェクトヘッダに格納されます。この処理がセットアップされる正確な方法は、JVMの実装に依存します。

初期化のためのinitメソッドの実行

Javaプログラムから見ると、初期化が正式に始まるのはこれからです。メンバ変数の初期化、インスタンス化ブロックの実行、クラス・コンストラクタ・メソッドの呼び出し、ヒープ上のオブジェクトの最初のアドレスを参照変数に代入します。 このように、一般的に、新しい命令の後には、プログラマーが望むようにオブジェクトを初期化するメソッドが実行され、本当に使えるオブジェクトが作成されます。

オブジェクトのメモリレイアウト

オブジェクトのヘッダー

2部構成

  • ランタイムメタデータ
    • GC生成年代
    • ロック・ステータス・フラグ
    • スレッドが保持するロック
    • バイアススレッドID
    • タイムスタンプの偏り
    • 偏ったタイムスタンプ
  • 型ポインタ:オブジェクトが属する型を特定するクラスメタデータを指す InstanceKlass

注:配列の場合は、配列の長さも記録してください。

インスタンス・データ

説明:オブジェクトが実際に格納する有効な情報であり、プログラムコードで定義された様々なタイプのフィールドのルールを含みます:

  • 同じ幅のフィールドは常に一緒に割り当てられます。
  • 親クラスで定義された変数が子クラスに現れます。
  • CompactFieldsパラメータがtrueの場合、サブクラスの狭い変数が親クラスの変数の隙間に挿入される可能性があります。

アラインメント・フィリング

必須ではありませんし、特別な意味もありません。

概要

public class CustomerTest {
 public static void main(String[] args) {
 Customer cust = new Customer();
 }
}

ハンドルアクセス方式

ハンドル位置決め方法の利点と欠点:

  • 欠点:スペースを取る、間接的にオブジェクトのインスタンスを指すこと。
  • 利点:安定した位置決め:オブジェクトインスタンスが移動しても、ハンドルへの参照の位置決めアドレスを修正する必要はなく、ハンドル内の間接アドレスを修正してオブジェクトインスタンスに再位置決めするだけです。

ダイレクト・ポインタ方式

ホットスポットVMはダイレクト・ポインタ方式を採用しています。

Read next

配列のmapメソッドに関するシナリオと古典的な質問

mapが行う最大のことは、配列から新しい配列を取得することであり、その結果、配列の各要素は、提供された関数への単一の呼び出しの戻り値です。 前の文章が核心です。 ほとんどの場合、mapで関数に渡す引数は1つで十分です。 つまり、配列の各項目はオブジェクトであり、それを操作します。あるいは、オブジェクトの値のひとつに変更を加えます。 mapがトラバースするとき、もし現在の値がundefi...

Apr 21, 2020 · 3 min read