この記事では、オブジェクトのメモリ レイアウトとヒープ内のオブジェクトにアクセスする方法を紹介します。
序文
Object object = new Object() この文の理解度を教えてください。一般的に言えば、JDK8 のデフォルトの状況によると、新しいオブジェクトによって占有されるメモリ領域の量。
- オブジェクトの場所がヒープに格納されることは以前に説明しました (従来の状況: エデンの園 → S0/S1 → 古い世代)。
- それでは、レイアウトについて話しましょう。つまり、オブジェクトの構成は何ですか? 頭と体?
オブジェクト メモリ レイアウト
HotSpot 仮想マシンでは、ヒープ メモリ内のオブジェクトのストレージ レイアウトは、オブジェクト ヘッダー (Header)、インスタンス データ (Instance Data)、アライメント パディング (Padding) の 3 つの部分に分割できます。
1) オブジェクトヘッダー
オブジェクト ヘッダーには、データの 2 つの部分が含まれます。
- ランタイム アリティ マーク ワード
- 型ポインタ クラス ポインタ
オブジェクトが配列の場合、配列の長さも記録する必要があります。以下に示すように:
ランタイム メタデータ マーク ワールド
-
HashCode、オブジェクトの経過時間、ロック状態フラグ、スレッドが保持するロックなどの情報を保存します。
-
64 ビット システムでは、マーク ワードが
8
1 バイトを占め、タイプ ポインターが8
1 バイト [ポインター圧縮が有効な場合は 4 バイト]、合計 16 (12) バイトを占めます。 -
新しいオブジェクトにインスタンス データがない場合、それは 16 バイトです [デフォルトでは、ポインターの圧縮が開始されます -XX:+UseCompressedClassPointers、アラインメント パディング 4 バイト]
-
マークワードの格納構造を下図に示します。
型ポインタ クラス ポインタ
- メソッド領域のクラス メタ情報 (オブジェクト テンプレート) を指すと、仮想マシンはこのポインターを使用して、オブジェクトがどのクラスのインスタンスであるかを判断します。
2) インスタンスデータ
- オブジェクトが実際に保持している有効な情報、つまりクラス内で定義されている各種属性(親クラスから継承したものや自身で定義したものも含む)です。
- インスタンス データ ストレージには特定のルールがあります。
- 同じ幅のフィールドは常に一緒に割り当てられます。
- 親クラスで定義された変数は、サブクラスの前に表示されます。
- CompactFields パラメータが true の場合 (デフォルトは true): サブクラスのナロー変数が親クラス変数のギャップに挿入される可能性があります
3) パディングを揃える
- 虚拟机要求对象起始地址必须是8字节的整数倍【具体原因这里就不赘述了,读者可自行查阅相关资料】。
- 填充数据不是必须存在的,仅仅是为了字节对齐这部分内存按8字节补充对齐
4)JOL验证
引入JOL依赖
<dependency>
<groupId>org.openjdk.jol</groupId>
<artifactId>jol-core</artifactId>
<version>0.9</version>
</dependency>
复制代码
使用JOL查看Object对象内部细节
使用JOL查看Customer对象内部细节
public class Customer {
int id;
boolean flag = false;
public static void main(String[] args) {
System.out.println(ClassLayout.parseInstance(new Customer()).toPrintable());
}
}
复制代码
- 7→填充的字节数
- 声明一个Customer的实例,只有一个对象头的实例对象,12字节(开启压缩指针)+ 4字节[int] + 1字节[boolean]=17字节,此时需要对齐填充到24字节
默认开启指针压缩的 -XX:+UseCompressedClassPointers
- 12 + 4(对齐填充) == 一个对象16字节(不算实例数据)
手动关闭指针压缩 -XX:-UseCompressedClassPointers
- 8 + 8 == 16字节(不算实例数据)
图示对象的内存布局
对象的访问定位
创建对象是为了后续使用该对象,那么JVM是如何通过栈帧中的对象引用访问到其内部的对象实例的呢?
- 通过栈上的reference访问
而reference类型只是一个指向对象的引用,并没有定义这个引用应该通过什么方式去定位、访问到堆中对象的具体位置,而JVM中主流的对象访问方式主要有使用句柄和直接指针两种方式。
1)句柄访问
Java堆中可能会划分出一块内存来作为句柄池,而reference中存储的就是对象的句柄地址,而句柄中包含了对象实例数据与类型数据各自具体的地址信息。
- 优点:reference中存储稳定句柄地址,对象被移动(垃圾收集时候移动对象很普遍)时会改变句柄中的实例数据指针即可,reference本身不需要被修改
- 缺点:需要多占用一些空间
2)直接指针
- HotSpot使用该方式。
直接ポインタ アクセスを使用する場合、Java ヒープ内のオブジェクトのメモリ レイアウトは、アクセス タイプ データの関連情報を配置する方法を考慮する必要があります。オブジェクト アドレスは参照に直接格納されます。オブジェクト自体にのみアクセスする場合は、そこにオブジェクト アドレスが格納されます。追加の間接アクセス オーバーヘッドは必要ありません。