オブジェクトの作成プロセス
クラス搭載チェック
仮想マシンが新しい命令に遭遇すると、まずその命令の引数が定数プール内のクラスへのシンボリック参照を指定しているかどうか、そしてそのシンボリック参照で表されるクラスがロード、解析、初期化されているかどうかをチェックします。そうでない場合は、適切なクラス・ロード処理を最初に実行する必要があります。
メモリの割り当て
クラスのロード・チェックが通過すると、仮想マシンは次に新生オブジェクトのメモリを割り当てます。オブジェクトに必要なメモリのサイズは、クラスがロードされた時点で決定され、オブジェクトのための領域を割り当てる作業は、Javaヒープからメモリの明確なチャンクを分割することに相当します。
割り当て
割り当て方法には「ポインタ衝突」と「フリーリスト」の2つがあり、Javaのヒープが規則的かどうかでどちらを選ぶかが決まり、Javaのヒープが規則的かどうかは、使用するゴミ収集器がコンパクション機能を持っているかどうかで決まります。
ポインタの衝突:ヒープメモリの規則性、使用されるメモリは、一方の側に統合され、未使用のメモリは、反対側に統合され、中央には境界ポインタであり、ちょうどポインタがオブジェクトのメモリサイズを移動するには、未使用のメモリの方向にさせる割り当て時。
空きリスト:ヒープメモリは規則的ではありません。仮想マシンは、オブジェクトのためのメモリの十分な大きさの部分を見つけるために割り当てる、どのような領域が空き、レコードを行うためのリストのリストを維持します。
割り当て方法の選択は、Javaヒープの規則性に依存し、それは使用されるごみコレクタの圧縮によって決定されます。したがって
ポインタの衝突は、 マークアップ照合 アルゴリズムに基づくごみコレクタを使用するときに使用されます。
マークアンドクリアアルゴリズムに 基づくごみコレクタを使用する場合、フリーリストが使用されます。たとえば、CMSのゴミ収集器は
メモリ割り当ての並行性問題
オブジェクトの作成には、スレッドの安全性という非常に重要な問題があります。実際の開発プロセスでは、オブジェクトの作成は非常に頻繁に行われるため、仮想マシンとして、スレッドの安全性を確保する必要があります:
CAS+失敗リトライ:CASは楽観的ロックの実装です。楽観的ロッキングとは、ロックが追加されずに操作が完了するたびに、競合がないと見なされ、競合が原因で失敗した場合は成功するまで再試行されることを意味します。VMは更新操作のアトミック性を保証するために、失敗の再試行を伴うCASを使用します。
TLAB:各スレッドのためにEdenにメモリブロックを事前に割り当てます。もちろん、この領域を申請するプロセスはスレッド同期です。JVMがスレッドのオブジェクトのためにメモリを割り当てるとき、まずTLABにメモリを割り当てます。オブジェクトがTLABの残りのメモリより大きくなったとき、あるいはTLABのメモリが使い果たされたとき、再びEdenにTLABを申請します。新しいTLABが収まらない場合、上記のようにCASを使ってメモリを割り当てます。それでも新しい TLAB が大きすぎて入らない場合は、上記の CAS を使ってメモリを割り当てます。
ゼロ値の初期化
メモリ割り当てが完了したら、仮想マシンは割り当てられたすべてのメモリ空間をゼロ値に初期化する必要があります。 このステップによって、オブジェクトのインスタンス・フィールドを初期値なしでJavaコードで直接使用できるようになり、プログラムがこれらのフィールドのデータ型に対応するゼロ値にアクセスできるようになります。
仮想マシンから見れば、新しいオブジェクトが作成されたことになりますが、Javaプログラムから見れば、オブジェクトの作成は始まったばかりです!
オブジェクトヘッダの設定
ゼロ値の初期化が完了すると、仮想マシンは、このオブジェクトがそのクラスのインスタンスであるという情報、クラスのメタデータ情報を見つける方法、オブジェクトのハッシュコード、オブジェクトのGCステージングの年齢など、必要な設定をオブジェクトに設定する必要があります。 これらの情報はオブジェクトのヘッダーに格納されます。 さらに、バイアス・ロックが有効になっているかどうかなど、仮想マシンの現在の実行状態に応じて、オブジェクト・ヘッダはさまざまな方法で設定されます。
initメソッドの実行
上記の作業がすべて完了すると、仮想マシンから見れば新しいオブジェクトが作成されたことになりますが、Javaプログラムから見れば、オブジェクトの作成は始まったばかりで、
initクリニット
コンストラクタはjavacコマンドで生成され、コンストラクタは基本的にjavaコードで記述さ れます コンストラクタ・メソッド
インスタンスブロック {} はコンストラクタの代入動作よりも優先されます。複数の {} は、最終的に優先順位の高い順に
変数の初期化とステートメントブロックの順序は、定義の順序と同じです。
以下のセクションが含まれます。
- 非静的変数の初期化
- ステートメントブロック
- コンストラクタ
- 親
以下のセクションが含まれます。
- 静的変数の初期化
- 静的ステートメントブロック
親クラスと子クラスの初期化順序
親クラスを持つサブクラスは、ロード順にオブジェクトをNEWします:
クラスを初期化する際に、親クラスがまだ初期化されていないことが判明した場合は、親クラスの初期化を最初に行う必要があります。
- 親クラスの
メソッド - サブクラスの
メソッド
クラスを初期化する際に、親クラスがまだ初期化されていないことが判明した場合は、まず親クラスの初期化をトリガーする必要があります。
- 親クラスの
メソッド - サブクラスの
メソッド
親クラスのコンストラクタを呼び出すサブクラス
スーパークラスのコンストラクタの呼び出しは、super を介して実装できます。
super でコンストラクタを呼び出すステートメントは、サブクラスのコンストラクタの最初のステートメントでなければなりません。
サブクラスのコンストラクタが明示的にスーパークラスのコンストラクタを呼び出さない場合、スーパークラスのデフォルトの(引数なしの)コンストラクタが自動的に呼び出されます。
スーパークラスにパラメータなしのコンストラクタがなく、サブクラスのコンストラクタでスーパークラスの他のコンストラクタが明示的に呼び出されない場合、Javaコンパイラはエラーを報告します。
メモリ内のオブジェクトのレイアウト
Hotspot VMでは、メモリ上のオブジェクトのレイアウトは、オブジェクトヘッダ、インスタンスデータ、アラインメントパディングの3つの領域に分けることができます。
オブジェクトヘッダ
Hotspot VMのオブジェクトヘッダには、次の2つの情報が含まれています。
オブジェクト自身に関する実行時データを格納するためのものです。
タイプ・ポインタとは、オブジェクトからそのクラスのメタデータへのポインタのことで、これによって仮想マシンはオブジェクトがそのクラスのインスタンスであると判断します;
仮想マシンは通常のJavaオブジェクトのメタデータ情報からJavaオブジェクトのサイズを判断できますが、配列の長さが不定であれば、メタデータの情報から配列のサイズを推測することができないからです。
一般人口
|--------------------------------------------------------------|
| Object Header (64 bits) |
|------------------------------------|-------------------------|
| Mark Word (32 bits) | Klass Word (32 bits) |
|------------------------------------|-------------------------|
配列オブジェクト
|---------------------------------------------------------------------------------|
| Object Header (96 bits) |
|--------------------------------|-----------------------|------------------------|
| Mark Word(32bits) | Klass Word(32bits) | array length(32bits) |
|--------------------------------|-----------------------|------------------------|
オブジェクトヘッダの詳細:
データ例
インスタンス・データ部分は、オブジェクトが実際に格納する有効な情報であり、プログラムで定義された様々なタイプのフィールドの内容です。
アライメントフィル
整列されたフィラーセクションは特別な意味はなく、単にプレースホルダーとして機能します。
これは、Hotspot VMの自動メモリ管理システムが、オブジェクトの開始アドレスがバイトの整数倍であることを要求しているためで、言い換えれば、オブジェクトのサイズはバイトの整数倍でなければなりません。
オブジェクト・ヘッダ部分は正確に1バイトの倍数なので、オブジェクト・インスタンスのデータ部分がアラインメントされていない場合、アラインメント・パディングで補う必要があります。
オブジェクトアクセスロケータ
オブジェクトは使用するために生成され、Javaプログラムはスタック上の参照データを通じてヒープ上の特定のオブジェクトを操作します。オブジェクトへのアクセス方法は仮想マシンの実装によって決定され、現在主流のアクセス方法はハンドルと直接ポインタです。
REFERENCEには以下の2つの条件があります:
- このリファレンスは、直接的または間接的に、Javaヒープ内のオブジェクトのデータ・ストレージの開始アドレスのインデックスを検索します。
- この参照は、直接または間接的に、オブジェクトが属するデータ型のメソッド領域に格納されている型情報を見つけます。
ハンドル
ハンドルを使用する場合、Javaヒープはハンドルのプールとしてメモリの一部に分割され、参照はオブジェクトのハンドルアドレスに格納され、ハンドルにはオブジェクトのインスタンスデータと型データの特定のアドレスに関する情報が含まれます;
ダイレクトポインタ
直接ポインタ・アクセスを使用する場合、Javaヒープ・オブジェクトのレイアウトは、アクセスされるデータのタイプに関する情報をどのように配置するかを考慮する必要があり、参照はオブジェクトのアドレスに直接格納されます。
つのアプローチの比較
これら2つのオブジェクトアクセス方法には、それぞれ利点があります。
アクセスにハンドルを使用する最大の利点は、参照に安定したハンドルアドレスが格納されるため、オブジェクトが移動してもハンドル内のインスタンスデータポインタだけが変更され、参照自体を変更する必要がないことです。
ダイレクト・ポインタ・アクセスを使用する最大のメリットは速度で、ポインタを一度探す時間的オーバーヘッドを節約できます。 Javaではオブジェクトへのアクセスが非常に頻繁に行われるため、この種のオーバーヘッドも、ごく少量が大量になった後には大きなコストとなります。