blog

7.ランタイム・データ領域 [オブジェクトのインスタンス化メモリ・レイアウトとアクセス位置、ダイレクト・メモリ]について

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

Aug 8, 2020 · 8 min. read
シェア

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

オブジェクトの作成方法

  • 新しい
    • 最も一般的な方法
    • 変換1:Xxxの静的メソッド
    • 変換2:XxBuilder/XxoxFactoryの静的メソッド
  • クラスの newInstance(): 反射的な方法で、空のパラメータでコンストラクタを呼び出すことができます。
  • コンストラクタのnewInstance: 反射的な方法で、空のパラメータでコンストラクタを呼び出すことができます。
  • クローンの使用
  • デシリアライズの使用:ファイルやネットワークからオブジェクトのバイナリ・ストリームを取得します。
  • サードパーティ・ライブラリ Objenesis

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

  • 1.オブジェクトに対応するクラスがロードされ、リンクされ、初期化されているかどうかを判断します。
  • 2.オブジェクトのメモリ確保
    • 2.1 メモリに規則性がある場合 - ポインタの衝突
    • 2.2 メモリが規則的でない場合:VMは空きリストの割り当てリストを保持する必要があります。
  • 3.同時実行セキュリティの処理
    • 3.1 更新のアトミック性を確保するために失敗リトライを伴うCASを使用
    • 3.2 スレッドごとにTLABを事前割り当て
  • 4.割り当てられた空間の初期化 オブジェクトのインスタンス・フィールドが値を割り当てずに直接使用できるように、すべての属性にデフォルト値が設定されます。
  • 5.オブジェクトのヘッダーを設定
  • 6.初期化のためのinitメソッドの実行

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

仮想マシンが新しい命令に遭遇すると、まず命令の引数がメタスペースの定数プールでクラスへのシンボリック参照を見つけられるかどうかをチェックし、このシンボリック参照で表されるクラスがすでにロード、解析、初期化されているかどうかをチェックします。もしそうでなければ、現在のクラス・ローダーを使用して、2つの親デリゲート・モードでClassLoader+PackageName+ClassName Keyを使用して対応する.classファイルを検索します。ファイルが見つからない場合は ClassNotFoundException がスローされ、見つかった場合はクラスがロードされ、対応する Class オブジェクトが生成されます。

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

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

メモリの規則性1ポインタ衝突の場合

メモリが規則的であれば、仮想マシンはポインタの衝突メソッドを使用してオブジェクトのメモリを割り当てます。つまり、すべての使用済みメモリが片側に、空きメモリがもう片側にあり、真ん中にあるポインタが分割点の指標となり、メモリの割り当ては、オブジェクトのサイズに等しい距離だけポインタを空き側に移動させるだけです。ゴミコレクタに Serial または ParNew を選択した場合、仮想マシンはこの割り当て方法を使用します。一般に、コンパクトなプロシージャを持つコレクタを使用する場合、ポインタの衝突が使用されます。

メモリは規則的ではありません

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

  • 注意:割り当て方法の選択は、Javaのヒープが規則的かどうかによって決まり、Javaのヒープが規則的かどうかは、使用するごみ収集器にコンパクション機能があるかどうかによって決まります。
  • オブジェクトのプロパティに値を代入する: ①プロパティのデフォルト初期化 ②明示的初期化 ③コードブロックでの初期化 ④コンストラクタでの初期化

同時実行セキュリティ問題への対応

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

  • 実際、VMは更新操作のアトミック性を確保するために、失敗リトライを伴うCASを採用しています。
  • つまり、各スレッドには、ローカル・スレッド・アロケーション・バッファ(LTAB)と呼ばれるJavaヒープ内の小さなメモリ・ブロックが事前に割り当てられます。 VMがTLABを使用するかどうかは、XX:+/UseTLABパラメータで設定できます。

割り当て空間の初期化

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

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

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

initメソッド実行による初期化

上記の作業が完了した後、仮想マシンから見ると新しいオブジェクトが生成されたことになりますが、Javaプログラムから見ると、オブジェクトの生成は始まったばかりで、オブジェクトの構築メソッドはまだ実行されておらず、すべてのフィールドはゼロ値に初期化されています。新しい命令の後に、プログラマの希望に従ってinitメソッドが実行され、オブジェクトが初期化されることで、本当に使えるオブジェクトが完全に構築されたと考えられます!このようにして、本当に使えるオブジェクトが構築されるのです。

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

オブジェクト・ヘッダ

  • この部分のデータの長さは、32ビットVMでは32ビット、64ビットVMでは64ビットで、正式にはマーク・ワードのランタイム・メタデータとして知られています。
    • ハッシュ値
    • GC世代
    • ロック・ステータス・フラグ
    • スレッドが保持するロック
    • バイアススレッドID
    • バイアス・タイムスタンプ
  • 型ポインタ:オブジェクトの型のメタデータへのポインタ。
  • 注:配列の場合は、配列の長さも記録してください。

インスタンス・データ

プログラム・コードで定義されたさまざまなタイプのフィールドのルールを含め、オブジェクトが実際に保存する有効な情報です:

  • 同じ幅のフィールドは常に一緒に割り当てられます。
  • 親クラスで定義された変数が子クラスに現れます。
  • HotSpot VMの+XX:CompactFieldsパラメータの値がtrueの場合、サブクラス内の狭い変数も、スペースを少し節約するために親の変数の隙間に挿入することが許可されます。

整列パディング

特別な意味はありません。

説明: HotSpot VMの自動メモリ管理システムのため、オブジェクトの開始アドレスは8バイトの整数倍でなければなりません。

オブジェクトヘッダは、8バイトの整数倍、1xまたは2xになるように注意深く設計されています。

オブジェクト・インスタンスのデータ部分がアライメントされていない場合は、パディングして埋める必要があります。

コード解析

/**
 * テスト・オブジェクトのインスタンス化のプロセス
 * 
    1.  クラスのメタ情報をロードする。 
    2.  オブジェクトにメモリを割り当てる 
    3.  属性のデフォルト初期化。
 * - オブジェクトヘッダ内の情報を設定する - ⑥ プロパティの明示的初期化、コードブロック内の初期化、コンストラクタ内の初期化
 *
 * オブジェクトの属性に値を割り当てる操作:
 * 
    1.  属性のデフォルト初期化 
    2.  を明示的に初期化する。 
    3.  コードブロックでの初期化 - ④ コンストラクタでの初期化
 * 
 */
public class Customer{
 int id = 1001;
 String name;
 Account acct;
 {
 name = "匿名クライアント";
 }
 public Customer(){
 acct = new Account();
 }
}
class Account{
}
public class CustomerTest { public static void main(String[] args) { Customer cust = new Customer(); } }

オブジェクト・アクセスの局所性

オブジェクトを作成する目的は、それを使用することです JVMはスタック・フレーム内のオブジェクト参照を通じて、どのように内部のオブジェクト・インスタンスにアクセスするのでしょうか?-> オブジェクトにアクセスするには、主に次の2つの方法があります。

  • デメリット:スペースを取る、非効率 メリット:オブジェクトの移動時にスタックフレームを修正する必要なし
  • ダイレクト・ポインタ メリット:場所を取らない、速い デメリット:オブジェクトの移動時にスタック・フレームを修正する必要がある

ダイレクト・メモリ

  • は、仮想マシンのランタイム・データ領域の一部ではなく、Java仮想マシン仕様で定義されているメモリ領域でもありません。
  • ダイレクト・メモリーとは、システムから直接要求されるJavaヒープ外のメモリー間隔のことです。
/**
 * IO NIO (New IO / Non-Blocking IO)
 * byte[] / char[] Buffer
 * Stream Channel
 *
 * ダイレクト・メモリの占有と解放を表示する
 */
public class BufferTest {
 private static final int BUFFER = 1024 * 1024 * 1024;//1GB
 public static void main(String[] args){
 //ローカル・メモリ空間を直接割り当てる
 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
 System.out.println("ダイレクト・メモリの割り当てが完了した。);
 Scanner scanner = new Scanner(System.in);
 scanner.next();
 System.out.println("ダイレクト・メモリが解放され始める);
 byteBuffer = null;
 System.gc();
 scanner.next();
 }
}

  • 通常、ダイレクト・メモリへのアクセスはJavaヒープよりも優れています。つまり、読み取りと書き込みの性能が高い

    • したがって、性能上の理由から、読み書きが頻繁に行われる状況では、ダイレクト・メモリの使用が考慮されるかもしれません。
    • JavaのNIOライブラリは、Javaプログラムがデータ・バッファにダイレクト・メモリを使用することを可能にします。
  • OutOfMemoryError例外が発生することもあります。:OutOfMemoryError: Direct buffer memory

/**
 * ローカル・メモリのOOM: OutOfMemoryError: Direct buffer memory
 */
public class BufferTest2 {
 private static final int BUFFER = 1024 * 1024 * 20;//20MB
 public static void main(String[] args) {
 ArrayList<ByteBuffer> list = new ArrayList<>();
 int count = 0;
 try {
 while(true){
 ByteBuffer byteBuffer = ByteBuffer.allocateDirect(BUFFER);
 list.add(byteBuffer);
 count++;
 try {
 Thread.sleep(100);
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 }
 } finally {
 System.out.println(count);
 }
 }
}
  • ダイレクト・メモリはJavaヒープの外側にあるため、そのサイズはXmxで指定された最大ヒープ・サイズに直接制限されませんが、システム・メモリは有限であり、Javaヒープとダイレクト・メモリの合計は、オペレーティング・システムが与えることができる最大メモリに制限されたままです。
  • デメリット
    • 割り当て再利用コストが高い
    • JVMのメモリ再生成によって管理されない
  • ダイレクトメモリのサイズは MaxDirectMemorySize で設定できます。
  • 指定されない場合、デフォルトはヒープのmax-Xmxパラメータ値と同じです。



























Read next

JavaScriptの誕生

1.イントロダクションは、高水準のインタプリタ型プログラミング言語です。Brendan Eichによって考案され、ECMAによって標準化されました。 2.誕生 1994年、ネットスケープ(Net...

Aug 8, 2020 · 2 min read