オブジェクトのインスタンス化メモリのレイアウトとアクセス場所
面接の質問
- オブジェクトはJVMにどのように格納されますか?
- オブジェクトヘッダー情報には何が含まれていますか?
- Javaオブジェクトヘッダーには何が含まれていますか?
オブジェクト作成の方法と手順から始めます。
オブジェクトの作成方法
- new:シングルトンクラスでgetInstanceの静的クラスメソッドを呼び出す最も一般的な方法であるXXXFactoryの静的メソッド。
- クラスのnewInstanceメソッド:空のパラメーターコンストラクターしか呼び出せないため、JDK9で廃止としてマークされたメソッド。
- コンストラクターのnewInstance(XXX):リフレクションメソッド。空またはパラメーターを使用してコンストラクターを呼び出すことができます。
- clone()を使用する:コンストラクターを呼び出さず、現在のクラスがCloneableインターフェースにcloneインターフェースを実装する必要があります。
- シリアル化を使用する:シリアル化は通常、ソケットネットワーク送信に使用されます。
- サードパーティのライブラリであるObjenesis。
オブジェクト作成手順
バイトコードからのオブジェクトの作成プロセスを見てください。
サンプルコード:
public class ObjectTest {
public static void main(String[] args) {
Object obj = new Object();
}
}
バイトコード:
public static void main(java.lang.String[]);
descriptor: ([Ljava/lang/String;)V
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=2, args_size=1
0: new #2 // class java/lang/Object
3: dup
4: invokespecial #1 // Method java/lang/Object."<init>":()V
7: astore_1
8: return
LineNumberTable:
line 9: 0
line 10: 8
LocalVariableTable:
Start Length Slot Name Signature
0 9 0 args [Ljava/lang/String;
8 1 1 obj Ljava/lang/Object;
}
1.オブジェクトに対応するクラスがロード、リンク、および初期化されているかどうかを判別します
- 仮想マシンが新しい命令を検出したら、最初に、この命令のパラメーターがメタスペース(メソッド領域)の定数プール内のクラスのシンボリック参照を見つけることができるかどうかを確認し、このシンボリック参照によって表されるクラスがロードされているかどうかを確認します。解析および初期化されます。(つまり、メタ情報が存在するかどうかを判断するため)。
- クラスがロードされていない場合は、親委任モードで、現在のクラスローダーを使用して、ClassLoader +パッケージ名+クラス名キーを持つ対応する.classファイルを検索します。ファイルが見つからない場合は、ClassNotFoundExceptionがスローされます。見つかった場合、クラスがロードされ、対応するClassオブジェクトが生成されます。
2.オブジェクトにメモリを割り当てます
- 最初にオブジェクトが占めるスペースのサイズを計算し、次に新しいオブジェクトのヒープ内のメモリブロックを分割します。インスタンスメンバー変数が参照変数の場合は、サイズが4バイトの参照変数スペースを割り当てるだけです。
- メモリが通常の場合:ポインタの衝突を使用してメモリを割り当てます。
- メモリが通常の場合、仮想マシンはBump ThePointメソッドを使用してオブジェクトにメモリを割り当てます。これは、使用済みメモリがすべて片側にあり、空きメモリが反対側に保存され、分割点のインジケータとしてポインタが中央に配置されていることを意味します。メモリを割り当てると、ポインタが空きメモリに移動するだけです。オブジェクトのサイズに等しい距離。
- ガベージコレクターが圧縮アルゴリズムに基づくSerial、ParNewを選択した場合、仮想マシンはこの割り当て方法を使用します。通常、コンパクトプロセスでコレクターを使用する場合は、ポインターの衝突が使用されます。
- マーク圧縮(デフラグ)アルゴリズムはメモリをデフラグします。ヒープメモリの一方の側はオブジェクトを格納し、もう一方の側は空き領域です。
- メモリが定期的でない場合:空きリスト。
- メモリが規則的でない場合、使用済みメモリと未使用メモリが相互にインターリーブされ、仮想マシンは空きリストを使用してオブジェクトにメモリを割り当てます。これは、仮想マシンが使用可能なメモリブロックを記録するためのリストを維持することを意味します。再配布するときは、オブジェクトインスタンスを分割し、リストのコンテンツを更新するのに十分な大きさのスペースをリストから見つけます。この配分方法が「フリーリスト(フリーリスト)」になりました。
- 割り当て方法の選択は、Javaヒープが通常であるかどうかによって決まり、Javaヒープが通常であるかどうかは、使用するガベージコレクターに圧縮機能があるかどうかによって決まります。
- マークスイープアルゴリズムがヒープメモリをクリーンアップした後、多くのメモリフラグメントが存在します。
3.並行性の問題に対処する
- CAS +失敗の再試行を使用して、更新のアトミック性を確認します。
- -XX:+ UseTLABパラメーター(エリアロックメカニズム)を設定して、スレッドセットごとにTLABを事前に割り当てます。
- エデンエリアの各スレッドにエリアを割り当てます。
4.割り当てられたスペースを初期化します
- すべてのプロパティはデフォルト値に設定されており、値を割り当てずにオブジェクトインスタンスフィールドを直接使用できるようになっています。
- オブジェクトのプロパティに値を割り当てる順序:
- プロパティのデフォルト値の初期化
- 表示の初期化/コードブロックの初期化(並列関係、誰が最初で、誰がコードの記述順序を確認するか)
- イニシャライザー
5.オブジェクトのオブジェクトヘッダーを設定します
- オブジェクトのクラス(つまり、クラスのメタデータ情報)、オブジェクトのHashCode、オブジェクトのGC
情報、ロック情報、およびその他のデータをオブジェクトのオブジェクトヘッダーに格納します。このプロセスの具体的な設定は、JVMの実装によって異なります。
6.initメソッドを実行して初期化します
- Javaプログラムの観点からは、初期化は正式に始まったばかりです。メンバー変数を初期化し、インスタンス化されたコードブロックを実行し、クラスのコンストラクターメソッドを呼び出し、ヒープ内のオブジェクトの最初のアドレスを参照変数に割り当てます。
- したがって、一般に(バイトコードのinvokespecial命令によって決定されます)、initメソッドは新しい命令の後に実行され、プログラマーの希望に従ってオブジェクトを初期化します。これにより、真に使用可能なオブジェクトが作成されたと見なされます。
バイトコードの観点からinitメソッドを見てください
/**
* 测试对象实例化的过程
* ① 加载类元信息 - ② 为对象分配内存 - ③ 处理并发问题 - ④ 属性的默认初始化(零值初始化)
* - ⑤ 设置对象头的信息 - ⑥ 属性的显式初始化、代码块中初始化、构造器中初始化
*
*
* 给对象的属性赋值的操作:
* ① 属性的默认初始化 - ② 显式初始化 / ③ 代码块中初始化 - ④ 构造器中初始化
*/
public class Customer{
int id = 1001;
String name;
Account acct;
{
name = "匿名客户";
}
public Customer(){
acct = new Account();
}
}
class Account{
}
バイトコード情報:
0 aload_0
1 invokespecial #1 <java/lang/Object.<init>>
4 aload_0
5 sipush 1001
8 putfield #2 <com/atguigu/java/Customer.id>
11 aload_0
12 ldc #3 <匿名客户>
14 putfield #4 <com/atguigu/java/Customer.name>
17 aload_0
18 new #5 <com/atguigu/java/Account>
21 dup
22 invokespecial #6 <com/atguigu/java/Account.<init>>
25 putfield #7 <com/atguigu/java/Customer.acct>
28 return
init()メソッドのバイトコード命令:
- 属性のデフォルト値が初期化されます:id = 1001;
- 表示の初期化/コードブロックの初期化:name = "Anonymous Client";
- コンストラクターの初期化:acct = new Account();
オブジェクトのメモリレイアウト
注:タイプポインタが指すのは、実際にはメソッド領域に格納されているメタ情報です。
メモリレイアウトの概要
オブジェクトアクセス場所
JVMは、スタックフレーム内のオブジェクト参照を介して内部オブジェクトインスタンスにどのようにアクセスしますか?
スタック上の参照によってアクセスされるポジショニング
オブジェクトにアクセスする2つの方法:ハンドルアクセスと直接ポインタ:
1.アクセスを処理する
- 短所:ヒープスペースにハンドルプールとしてスペースが開かれ、ハンドルプール自体もスペースを占有します。ヒープ内のオブジェクトには、2回のポインターアクセスでしかアクセスできないため、非効率的です。
- 利点:参照は安定したハンドルアドレスを格納します。オブジェクトが移動されると(ガベージコレクション中にオブジェクトを移動するのが一般的です)、ハンドル内のインスタンスデータポインターのみを変更でき、参照自体を変更する必要はありません。 。
2.ダイレクトポインタ(HotSpotで採用)
- 特徴:直接ポインターは、ヒープ内のインスタンスを直接指すローカル変数テーブル内の参照であり、メソッド領域内のオブジェクト型データを指す型ポインターがオブジェクトインスタンスにあります。
- 短所:オブジェクトを移動するときに参照の値を変更する必要があります(ガベージコレクション中にオブジェクトを移動するのが一般的です)。