[jvm series-06] オブジェクトのインスタンス化、メモリ レイアウト、およびアクセス ポジショニングに関する深い理解

JVMシリーズ総合コラム


コンテンツ リンクアドレス
[1] 仮想マシンと Java 仮想マシンを理解する https://blog.csdn.net/zhenghuishengq/article/details/129544460
[2] jvmのクラスローディングサブシステムとjclasslibの基本的な使い方 https://blog.csdn.net/zhenghuishengq/article/details/129610963
[3] 実行時のプライベート領域の仮想マシンスタック、プログラムカウンター、ローカルメソッドスタック https://blog.csdn.net/zhenghuishengq/article/details/129684076
[4]実行時のデータ領域の共有領域のヒープとエスケープ解析 https://blog.csdn.net/zhenghuishengq/article/details/129796509
[5] ランタイムデータ領域共有領域のメソッド領域と定数プール https://blog.csdn.net/zhenghuishengq/article/details/129958466
[6] オブジェクトのインスタンス化、メモリ レイアウト、およびアクセス ポジショニング https://blog.csdn.net/zhenghuishengq/article/details/130057210

1. オブジェクトのインスタンス化、メモリ レイアウト、およびアクセス ポジショニング

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

オブジェクトを作成するには、主に次の方法とオブジェクトを作成する手順があります。
ここに画像の説明を挿入

1.1、オブジェクトを作成するいくつかの方法

日常の開発では、主に次の方法でオブジェクトを作成します。

  • 最も一般的な方法:新しいプラス コンストラクター。コンストラクターがプライベートの場合、シングルトン モードなどの静的にアクセスするか、ファクトリを介してロードできます。
//new 构造器 创建对象
Object object = new Object();
//构造器静态私有,如典型的单例模式
Object object = Object.getObject()//工厂加载,SpringBean,SqlSessionBean
Object object = ObjectFactory.getObject();
  • リフレクションメソッド: クラスの newInstance またはコンストラクターの newInstance
public class Invoke {
    
    
    public static void main(String[] args) {
    
    
        try {
    
    
            Class<?> clazz1 = Class.forName("com.tky.jvm.Invoke");
            //通过类构造器获取对象
            Constructor<?> constructor = clazz1.getConstructor();
            Invoke invoke1 = (Invoke)constructor.newInstance();
            //通过类名获取
            Class<Invoke> clazz2 = Invoke.class;
            Invoke invoke2 = clazz2.newInstance();
            //通过对象获取
            Invoke in = new Invoke();
            Class<? extends Invoke> clazz3 = in.getClass();
            Invoke invoke3 = clazz3.newInstance();
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}
  • クローン作成メソッド: コンストラクターを呼び出さないクローン メソッド。現在のクラスは Cloneable インターフェイスとクローン メソッドを実装する必要があります。
/**
 * @author zhenghuisheng
 * @date : 2023/4/10
 */
@Data
public class Clone implements Cloneable {
    
    
    private Long id;
    private String username;
    private String password;

    @Override
    protected Clone clone() throws CloneNotSupportedException {
    
    
        return (Clone)super.clone();
    }
}

class TestClone{
    
    
    public static void main(String[] args) {
    
    
        Clone clone1 = new Clone();
        clone1.setId(1L);
        clone1.setUsername("zhenghuisheng");
        clone1.setUsername("123456");
        try {
    
    
            Clone clone2 = clone1.clone();
            System.out.println(clone2.getId());
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }
}
  • 逆シリアル化方法: ファイルまたはネットワークからバイナリ ストリームを取得し、バイナリ ストリームをオブジェクトに変換します。
//对象序列化
Student s = new Student("1","zhenghuisheng","18");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("d:/a.txt"));
objectOutputStream.writeObject(s);
objectOutputStream.close();

//对象反序列化
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("d:/a.txt"));
Student student = (Student) inputStream.readObject();
  • サードパーティ ライブラリ オブジェクト
//构建 Objenesis 对象  Objenesis需要对应的pom依赖对象
Objenesis objenesis = new ObjenesisStd();
ObjectInstantiator<Student> instantiator = objenesis.getInstantiatorOf(Student.class);
Student student = instantiator.newInstance();

1.2、オブジェクト作成の手順

ここでは、主に実行の観点からオブジェクト作成の手順を分析します. 上図に示すように、オブジェクト作成は主に6つのステップに分けられます.
ここに画像の説明を挿入

1.2.1、オブジェクトに対応するクラスがロード、検証、準備、解析、および初期化されているかどうかを判断します

仮想マシンが新しい命令に遭遇すると、最初に、この命令のパラメーターがメタスペースの定数プールでクラス シンボルを見つけられるかどうかを確認し、クラスがロード、検証、準備、および解析を経て、これを初期化するかどうかを確認します。いくつかのステップ。そうでない場合、クラス ローダーは引き続き親の委任モードにあり、現在のクラス ローダーのキーを使用してClassLoader + package + class、対応する .class ファイルを検索します。対応するファイルが見つからない場合は、ClassNotFoundException例外がスローされます。見つかった場合、 Class のロード、および対応する Class オブジェクトの生成

1.2.2、オブジェクト用のスペースを開き、メモリを割り当てる

最初に、オブジェクトが占有する領域のサイズを計算する必要があります, 次に、新しいオブジェクトのヒープ内のメモリを分割します. インスタンスメンバー変数が参照変数である場合は、参照変数領域のみを割り当てます.サイズは 4 バイトです。たとえば、さまざまな基本データ型が占めるバイト数に応じて、各変数が占めるスペースの量を知ることができ、最終的にこれらの変数が必要とするすべてのスペースを合計して、この合計スペースのバイト数を取得できます。

また、メモリが通常の場合、仮想マシンはポインタオブジェクトにメモリを割り当てます。下図のように片側に使用済みメモリ、反対側に空きメモリが配置されており、真ん中に分割点の指標があり、ポインタを空き側に移動させるのがメモリ割り当てです。 . 移動距離はオブジェクトが必要とするものです. サイズ, およびポインタの衝突方法は、仮想マシンのガベージ コレクション アルゴリズムに圧縮機能があるかどうかによって異なります。

[外部リンクの画像の転送に失敗しました。ソース サイトにリーチング防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-mPmoI7G5-1681101135139)(img/1680846919641.png)]

内部メモリが一定でない場合、使用済みメモリと未使用メモリを管理するためのリストを仮想マシン内に保持する必要があり、これをフリー下図に示すように、仮想マシン内にテーブルが保持され、どのメモリが使用可能で、どのメモリが使用可能でないかを記録し、割り当て時に、リストからオブジェクト インスタンスに割り当てるのに十分な大きさのスペースを見つけ、テーブルの内容を更新します。

[外部リンクの画像転送に失敗しました。ソース サイトには盗難防止リンク機構がある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-jaJCEOZ8-1681101135140)(img/1680847513344.png)]

1.2.3、並行性の問題への対処

オブジェクトはヒープ上に作成され、ヒープは共有領域であるため、この並行性の問題は避けられません. ヒープ内では、インスタンスのセキュリティを確保するために主に2つの方法が使用されます.

1 つはCAS 比較交換方式を使用し、失敗した場合は再試行し、領域をロックして更新の原子性を確保する方法、もう 1 つはスレッドごとにTLAB を事前に割り当てる方法です並行性セキュリティの問題を解決するには、主にこれら 2 つの方法を使用します。

1.2.4、オブジェクトの初期割り当て

デフォルトの初期化はここで実行されますこのように、すべてのプロパティにはデフォルト値があり、オブジェクト インスタンス フィールドが割り当てられていない場合にそれらを使用できるようになっています。したがって、メソッド内では、静的変数は準備段階で最初に割り当てられ、インスタンス変数もスペースが割り当てられるときに最初に割り当てられるため、これら2つの変数を直接使用できます.他の変数が明示的に初期化されていない場合、それらは直接コンパイルの失敗が発生します。

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

オブジェクトのオブジェクトヘッダーには、オブジェクトが属するクラス、オブジェクトのhashCode、GC情報、年齢、ロック情報などを格納します。

1.2.6、init メソッドを実行して初期化する

ここでディスプレイの初期化を行い、正式に初期化作業に入ります。メンバー変数を初期化し、インスタンス化コード ブロックを実行し、クラスのコンストラクターを呼び出し、ヒープ オブジェクトの最初のアドレスを参照オブジェクトに割り当てます。したがって、一般的に言えば、真に使用可能なオブジェクトが完全に作成されるように、新しい命令の後に実行メソッドが続き、プログラマーの希望に従ってオブジェクトを初期化します。

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

オブジェクトのメモリ レイアウトには、主にオブジェクト ヘッダー、インスタンス データ、およびその埋め込みが含まれます。

[外部リンクの画像の転送に失敗しました。ソース サイトにはリーチング防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-ehB4t1wc-1681101135141)(img/1680853091624.png)]

2.1、オブジェクト ヘッダー (ヘッダー)

オブジェクト ヘッダーでは、2 つの部分に分けることができます。一方の部分はランタイム メタデータで、もう一方の部分は型ポインタです。

ランタイム メタデータには、ハッシュ コード、GC エイジ生成、スレッドが保持するロック、ロック保持フラグ、スレッド ID、およびスレッド タイムスタンプが含まれます。

下の図からわかるように、オブジェクトの年齢は 4 ビットに分割されているため、最大は 1111 の 15 であり、0 から始まるため最大年齢は 15 であり、この年齢を設定すると、より小さい値にのみ設定できますロックのフラグ ビットに対応するバイト コードは 01、10、および 11 で表され、その値はそれぞれ 1、2、および 3 に対応し、ロックのアップグレード プロセスは元に戻すことができません。

[外部リンクの画像の転送に失敗しました。ソース サイトにリーチング防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-LgRgzEzW-1681101135141)(img/1680854392412.png)]

型ポインターは、オブジェクトの型を決定するメタデータ InstanceKlass を指します。配列の場合は、配列の長さも記録する必要があります

2.2、インスタンス データ (インスタンス データ)

オブジェクトによって実際に格納される有効な情報には、コードで定義されたさまざまなタイプのフィールドと、親クラスから継承され、それ自体が所有するフィールドが含まれます。And in these objects, the variables defined by the parent class will appear before the subclass, and the same fields will always be allocated together. CompactFields パラメータが true の場合、サブクラスの狭い変数が親のギャップに挿入される可能性があります。クラス変数。

2.3、コード例

次に、次のコードを分析します

/**
 * @author zhenghuisheng
 * @date : 2023/4/7
 */
public class Customer {
    
    
    Integer id = 1001;
    String name = "zhenghuisheng";
    public Customer(){
    
    
        Account account = new Account();
    }
}
public class Test{
    
    
    public static void main(String[] args){
    
    
        Customer cust = new Customer(); 
    }
}

次に対応するメモリ構造を下図に示します. この main メソッドでは, static メソッドであるため, ローカル変数テーブルの最初のスロットは this ではなく, ローカル変数テーブルの 2 番目の cust は new を参照しています. heap. Customer() のインスタンス アドレス、インスタンス オブジェクトは、主に上記のランタイム メタデータ、型ポインタ、およびその埋め込みで構成されます。ランタイム データ領域には、一意のアドレスのハッシュ値、複数回の GC 後の結果の経過時間、ロックを取得するかどうかなどが含まれ、型ポインタは Customer の Klass クラス メタ情報に対応し、インスタンス データには独自の属性と親クラスの属性

[外部リンクの画像の転送に失敗しました。ソース サイトにリーチング防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-wdzf03vJ-1681101135142)(img/1680855082757.png)]

3. オブジェクトのアクセス場所

オブジェクトを作成する主な目的は、それをより有効に使用することです. JVM 内で内部オブジェクトへのオブジェクト参照アクセスを実現するには、主に 2 つの方法があり、1 つはダイレクト ポインター で、もう 1 つはハンドルアクセスです

次のコードに示すように、オブジェクトの作成にはヒープ、スタック、およびメソッド領域が必要になるため、オブジェクトの場所とアクセスもこれら 3 つの場所を設計する必要があります。

//第一个User存在方法区,主要是存储类信息和运行时常量池
//第二个user在栈中,作为变量存储
//最后的 new User存储在堆中
User user = new User();

ハンドルアクセスの方法は以下の通りで、Javaヒープにハンドルプールがあり、ハンドルプールはインスタンスを指すアドレスをヒープに格納し、クラス情報を指すアドレスをメソッド領域に格納し、ハンドル プールのアドレスをスタックに保存します。

[外部リンクの画像の転送に失敗しました。ソース サイトにリーチング防止メカニズムがある可能性があります。画像を保存して直接アップロードすることをお勧めします (img-TRULI2d8-1681101135142)(img/1680857098011.png)]

直接ポインタとは、ハンドル プールを使用する必要がないことを意味し、ヒープ内のインスタンスのアドレスはスタックのローカル変数テーブルに直接保存され、ヒープ内のアドレスを指すポインタが存在します。メソッド領域の予約済みクラス情報。Hotspot 仮想マシンでは、この方法が主に使用されます。

ここに画像の説明を挿入

ハンドル ポインタは、ハンドル プールを格納するためにヒープ領域にスペースを開く必要があるため、ある程度のスペースの無駄が生じ、効率は比較的低くなりますが、ガベージ コレクションなど、オブジェクトの場所が変わると、または、マークアップ アルゴリズムを使用する場合、このスタックのヒープ内のハンドル プールへのポインターを変更する必要はありません。ハンドル プールのインスタンス データとメソッド領域へのポインターのみを変更する必要があります。この直接ポインタの長所と短所は、ハンドル ポインタの反対です。

4.直接記憶の初体験(理解)

In JDK8, the specific implementation of the method area has changed to the metaspace, and the metaspace uses local memory, also known as direct memory. この部分は、ランタイム データ領域の一部ではなく、ランタイム データ領域の一部でもありません。 Java 仮想マシン仕様。「で定義されたメモリ領域

ここに画像の説明を挿入

Java コードでは、 ByteBuffer.allocateDirect()thisつまり、この NIO を介して直接操作できます。通常、ダイレクト メモリの速度は Java ヒープの速度よりも直接優れており、読み取りと書き込みのパフォーマンスは比較的高くなります。したがって、パフォーマンスを考慮して、頻繁な読み取りと書き込みにはダイレクト メモリを使用できます。また、Java の NIO ライブラリでは、Java プログラムでダイレクト メモリを使用することもできます。

OutOfMemoryError例外も発生する可能性があります。ダイレクト メモリは Java ヒープの外側にあるため、そのサイズは -Xmx で指定された最大ヒープ サイズによって制限されませんが、システムのメモリは常に制限されているため、ヒープとダイレクト メモリの合計は残りオペレーティング システムによって指定される最大メモリによって制限されますが、ダイレクト メモリには特定の欠点もあります。割り当てと回復のコストが高く、JVM メモリ回復によって管理されません

したがって、ダイレクト メモリのサイズMaxDirectMemorySizeは で。指定しない場合、デフォルト-Xmx値はヒープの最大パラメータ値と一致します。

ByteBuffer byteBuffer = ByteBuffer.allocateDirect(100 * 1024);

おすすめ

転載: blog.csdn.net/zhenghuishengq/article/details/130057210