blog

JVMシリーズ:ヒープ内のJavaオブジェクトオブジェクトの構造を説明する

記事では、アドレスによって占有されるメモリ空間内のJavaクラスまたはJavaインスタンスを解決するために、この魔法のツールをJOLの使用を導入しました。 今日は、さらに一歩踏み込んで、記事では説明し...

Jul 9, 2020 · 5 min. read
シェア

はじめに

この記事では、javaクラスやjavaインスタンスがメモリ上で占有する空間のアドレスを解決する魔法のツール、JOLの使用について説明します。

本日は、さらに一歩踏み込んで、記事では説明しきれなかった深い部分まで解剖します。どうぞお付き合いください。

モノとその隠された秘密

java.lang.Objectはjavaのすべてのオブジェクトの元祖です。

次のステップは、JVMの奥深い秘密を理解するために、このJavaオブジェクトの祖先を詳細に解剖学的に分析することです。

ツールはもちろんJOLを使っています:

@Slf4j
public class JolUsage {
 @Test
 public void useJol(){
 log.info("{}", VM.current().details());
 log.info("{}", ClassLayout.parseClass(Object.class).toPrintable());
 log.info("{}", ClassLayout.parseInstance(new Object()).toPrintable());
 }
}

コードはシンプルで、JVM、Objectクラス、Objectの新しいインスタンスに関する情報を表示します。

出力を見てください:

[main] INFO com.flydean.JolUsage - # Running 64-bit HotSpot VM.
# Using compressed oop with 3-bit shift.
# Using compressed klass with 3-bit shift.
# WARNING | Compressed references base/shifts are guessed by the experiment!
# WARNING | Therefore, computed addresses are just guesses, and ARE NOT RELIABLE.
# WARNING | Make sure to attach Serviceability Agent to get the reliable addresses.
# Objects are 8 bytes aligned.
# Field sizes by type: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
# Array element sizes: 4, 1, 1, 2, 2, 4, 4, 8, 8 [bytes]
10:27:32.311 [main] INFO com.flydean.JolUsage - java.lang.Object object internals:
 OFFSET SIZE TYPE DESCRIPTION VALUE
 0 12 (object header) N/A
 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
10:27:32.312 [main] INFO com.flydean.JolUsage - java.lang.Object object internals:
 OFFSET SIZE TYPE DESCRIPTION VALUE
 0 4 (object header) 05 00 00 00 (00000101 00000000 00000000 00000000) (5)
 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
 8 4 (object header) 86 06 00 00 (10000110 00000110 00000000 00000000) (1670)
 12 4 (loss due to the next object alignment)
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

上記の結果から、64ビットJVMでは、オブジェクト・インスタンスが16バイトを占めていることがわかります。

Objectオブジェクトには他のオブジェクトへの参照がないため、Objectオブジェクトには12バイトのオブジェクト・ヘッダしかないことがわかります。残りの4バイトはパディング・ビットです。

Object

では、この12バイトのオブジェクト・ヘッダは何のためにあるのでしょうか?

この12バイトのオブジェクト・ヘッダについてもっと深く知りたければ、もちろんJVMのソースコード:src/share/vm/oops/markOop.hppを研究する必要があります。

興味のある方はご覧ください。ご興味のない方は、こちらにまとめた表がありますので、ご覧ください:

javaObjectオブジェクトのオブジェクトヘッダーサイズは、32ビット仮想マシンを使用しているか64ビット仮想マシンを使用しているかによって若干異なります。ここでは例として64ビット仮想マシンを使用します。

オブジェクトのオブジェクトヘッダは、2つの部分に分割され、最初の部分は、ハッシュコード、GC世代年齢、ロックステータス、ホールドロック情報、バイアスロックスレッドIDなど、オブジェクトの実行時データを格納するために使用されるマークワードです。

64ビットVMではマーク・ワードは64ビット、32ビットVMではマーク・ワードは32ビットです。

2番目の部分はクラス・ワードで、これはクラスのメタデータへの型ポインターです。

そして遅い!

一部のパートナーは、オブジェクトオブジェクトを解析するためにJOLを使用して、問題を発見した可能性があり、オブジェクトのヘッドサイズは12バイト、つまり、96ビットであり、ここでどのように128ビットに書き込むのですか?

その通り、COOPsがオンになっていなければ128ビットで、COOPsがオンになっていればKlass Wordのサイズは64ビットから32ビットに減少します。

COOPの話を覚えていますか?

COOPsとはCompressed Object Pointersの略で、圧縮されたオブジェクトポインタのことです。

オブジェクト・ポインタはオブジェクトを指し、そのオブジェクトへの参照を表します。通常、64ビット・マシンでは、ポインタは64ビット、つまり8バイトを占有します。32ビット・マシンでは、ポインタは32ビット(4バイト)を占有します。

リアルタイムでは、アプリケーション内にそのようなオブジェクトへのポインタが非常に多く存在するため、同じプログラムが32ビットマシンで実行される場合と64ビットマシンで実行される場合では、使用するメモリ量がまったく異なります。

また、オブジェクト・ポインタの圧縮とは、64ビットのポインタを32ビットに圧縮することです。

64ビットマシンのオブジェクトアドレスは64ビットのままです。圧縮された32ビットメモリは、ヒープのベースアドレスに対して相対的にシフトされるだけです。

64ビットのHAPベースアドレス+32ビットのアドレス変位を使用すると、実際の64ビットのHAPアドレスが得られます。

オブジェクト・ポインタの圧縮は、Java SE 6u23ではデフォルトでオンになっています。ここでは、-XX:+UseCompressedOopsを使用してオンにできます。

配列オブジェクトのヘッダ

Javaには配列と呼ばれる非常に特殊なオブジェクトがあります。配列のオブジェクトヘッダとオブジェクトのヘッダに違いはありますか?

JOLでもう一度見てください:

log.info("{}",ClassLayout.parseClass(byte[].class).toPrintable());
log.info("{}",ClassLayout.parseInstance("www.flydean.com".getBytes()).toPrintable());

上記の例では、バイト配列のクラスとバイト配列のインスタンスをそれぞれ解析しています:

10:27:32.396 [main] INFO com.flydean.JolUsage - [B object internals:
 OFFSET SIZE TYPE DESCRIPTION VALUE
 0 16 (object header) N/A
 16 0 byte [B.<elements> N/A
Instance size: 16 bytes
Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
10:27:32.404 [main] INFO com.flydean.JolUsage - [B object internals:
 OFFSET SIZE TYPE DESCRIPTION VALUE
 0 4 (object header) 01 00 00 00 (00000001 00000000 00000000 00000000) (1)
 4 4 (object header) 00 00 00 00 (00000000 00000000 00000000 00000000) (0)
 8 4 (object header) 22 13 07 00 (00100010 00010011 00000111 00000000) (463650)
 12 4 (object header) 0f 00 00 00 (00001111 00000000 00000000 00000000) (15)
 16 15 byte [B.<elements> N/A
 31 1 (loss due to the next object alignment)
Instance size: 32 bytes
Space losses: 0 bytes internal + 1 bytes external = 1 bytes total

違いがわかりますか?配列のオブジェクトヘッダは16バイトであり、通常のオブジェクトのオブジェクトヘッダよりも4バイト多いことに注目してください。この4バイトが配列の長さです。

オブジェクト全体の構造

さて、ここでまとめて書くと、Javaオブジェクトの構造は、普通のJavaオブジェクトと配列オブジェクトの2種類に分けられます:

配列オブジェクトは、オブジェクトヘッダに4バイトの長さフィールドを追加します。

見ての通り、最後のバイトはパディングパディングバイトです。

JVMの単位は8バイトなので、8バイトの整数倍でない場合は補完が必要です。

この記事へのリンク

最も人気のある説明、最も深く辛口で簡潔なチュートリアル、そしてあなたが知らなかった数々のヒントが、あなたの発見を待っています!

Read next

vueでcdnを使う

コードと説明で、わかりやすく知識を学べます!\ncdnとは?\n比較前後のcdnの使い方\nビュー\nビューエックス\nビュー・ルーター\nエチャート\nアクシオス\n.... など。\n一気に導入、ロードオンデマンドのシナリオなし

Jul 9, 2020 · 8 min read