Javaオブジェクトと同期ロックプロセスの詳細な説明

1つは、Javaオブジェクトコンポーネントです。

Javaオブジェクトはメモリに保存され、次の3つの部分に分けられます。

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

2)、インスタンスデータ

3)、パディングバイトを揃えます

2.オブジェクトヘッダー

Javaオブジェクトヘッダーは、次の3つの部分で構成されています。

1)、マークワード

2)、クラスメタデータアドレス(クラスへのポインタ)

3)、配列の長さ(配列の長さ、配列オブジェクトのみ)

JVMのオブジェクトヘッダーには2つの方法があります(例として32ビットJVMを取り上げます)。

1.1。通常のオブジェクト:

|--------------------------------------------------------------|
|                     Object Header (64 bits)                  |
|------------------------------------|-------------------------|
|        Mark Word (32 bits)         |    Klass Word (32 bits) |
|------------------------------------|-------------------------|

1.2。配列オブジェクト:

|---------------------------------------------------------------------------------|
|                                 Object Header (96 bits)                         |
|--------------------------------|-----------------------|------------------------|
|        Mark Word(32bits)       |    Klass Word(32bits) |  array length(32bits)  |
|--------------------------------|-----------------------|------------------------|
 

2.1、マークワード

Mark Wordは主に、ハッシュコード、gc生成年齢、オブジェクトロック、GC、その他の関連情報など、オブジェクト自体のランタイムデータを格納するために使用されます。オブジェクトがsynchronizedキーワードによって同期ロックとして使用される場合、一連の操作ロックの周りはマークワードに関連しています。32ビットJVMのMarkWordの長さは32ビットで、64ビットJVMの長さは64ビットです。

2.1.1、マークワード(32ビット)

|-------------------------------------------------------|--------------------|
|                  Mark Word (32 bits)                  |       State        |
|-------------------------------------------------------|--------------------|
| identity_hashcode:25 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|-------------------------------------------------------|--------------------|
|  thread:23 | epoch:2 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|-------------------------------------------------------|--------------------|
|               ptr_to_lock_record:30          | lock:2 | Lightweight Locked |
|-------------------------------------------------------|--------------------|
|               ptr_to_heavyweight_monitor:30  | lock:2 | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                              | lock:2 |    Marked for GC   |
|-------------------------------------------------------|--------------------|

2.1.2、マークワード(64ビット)

|------------------------------------------------------------------------------|--------------------|
|                                  Mark Word (64 bits)                         |       State        |
|------------------------------------------------------------------------------|--------------------|
| unused:25 | identity_hashcode:31 | unused:1 | age:4 | biased_lock:1 | lock:2 |       Normal       |
|------------------------------------------------------------------------------|--------------------|
| thread:54 |       epoch:2        | unused:1 | age:4 | biased_lock:1 | lock:2 |       Biased       |
|------------------------------------------------------------------------------|--------------------|
|                       ptr_to_lock_record:62                         | lock:2 | Lightweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                     ptr_to_heavyweight_monitor:62                   | lock:2 | Heavyweight Locked |
|------------------------------------------------------------------------------|--------------------|
|                                                                     | lock:2 |    Marked for GC   |
|------------------------------------------------------------------------------|--------------------|

ビット数の違いを除いて、32ビットと64ビットの各部分の意味は同じであり、各部分の意味は次のとおりです。
ロック: 2ビットのロックステータスフラグ。できるだけ多くの情報を表すためにできるだけ少ないバイナリビットを使用するため、ロックフラグが設定されます。以下に示すように、マークの値が異なり、マークワード全体の意味が異なります。

biased_lock ロック 状態
0 01 ロックなし
1 01 バイアスロック
0 00 軽量ロック
0 10 ヘビーウェイトロック
0 11 GCマーク

biased_lock:オブジェクトがバイアスロックフラグを有効にするかどうかにかかわらず、バイアスロックはデフォルトで有効になり、1バイナリビットのみを占有します。1の場合、オブジェクトにバイアスロックがあることを意味します。0の場合、オブジェクトにバイアスロックがないことを意味します。
age:4桁のJavaオブジェクトの年齢。GCでは、オブジェクトがサバイバーエリアに1回コピーされると、年齢が1増加します。被験者が設定されたしきい値に達すると、老年期に昇格します。デフォルトでは、並列GCの年齢しきい値は15で、並行GCの年齢しきい値は6です。年齢はわずか4ビットを有しているので、最大値が理由である、15で -XX:MaxTenuringThreshold オプションの最大値は15である
identity_hashcode遅延ロード技術を使用して、25ビットオブジェクトの識別ハッシュコード:。メソッドSystem.identityHashCode() 計算を呼び出し 、結果をオブジェクトヘッダーに書き込みます。オブジェクトがロックされると、値はモニターモニター
スレッドに移動されます:バイアスロック
エポックを保持するスレッドID:バイアスタイムスタンプ
ptr_to_lock_record:スタック内のロックレコードへのポインター
ptr_to_heavyweight_monitor:モニターモニターへのポインター

以下では、同期同期キーワードロックの実装プロセスに焦点を当てます。次に、Mark Word(32ビット)を例として取り上げます。MarkWordは、以下に示すように、さまざまなロック状態でさまざまなコンテンツを格納します。

ロック状態

25ビット

4ビット

1ビット

2ビット

23ビット

2ビット

ロックに偏っていますか

ロックフラグ

ロックなし

オブジェクトのHashCode

世代の年齢

0

01

バイアスロック

スレッドID

時代

世代の年齢

1

01

軽量ロック

スタック内のロックレコードへのポインタ

00

ヘビーウェイトロック

ヘビーウェイトロックへのポインタ

10

GCマーク

空気

11

JDK1.6以降のバージョンでは、同期ロックを処理する際のロックアップグレードの概念が追加されています。JVMの同期ロックの処理はバイアスロックから始まります。競争がますます激しくなるにつれて、処理方法はバイアスロックから軽量ロックにアップグレードされます。 、そしてついにヘビーウェイトロックにアップグレード

同期プロセス

JVM同期ロックは、次のプロセスを使用してMark Wordを変更します。ロックプロセス全体は、次のように要約できます。

2.1.3、バイアスロックはデフォルトでオンになっています

1)以下に示すように、オブジェクトがロックされていない場合、つまり通常のオブジェクトの場合、バイアスロック状態は1、ロックフラグは01、スレッドIDは0です。

2)オブジェクトがロックされ、スレッドAがロックを取得すると、バイアスロックステータスビットとロックフラグビットは変更されませんが、ロックを取得した最初の23ビットレコードのスレッドIDがスレッドIDに記録されます。オブジェクトがバイアスに入るロックステータスは次のとおりです。

オブジェクトのロックを競合する他のスレッドがない場合、スレッドAがロックを解放した後、オブジェクトはバイアスされたロック状態、つまりステップ2の状態を維持し続けます。スレッドIDは引き続きスレッドのIDを維持します。 A

3)スレッドAが再びロックを取得すると、JVMは、オブジェクトがバイアスされたロック状態にあることを検出します。つまり、ロックフラグは01、バイアスロックフラグも1であり、同じスレッドであるため、必要なのはロック取得の数に1を追加するにははい、ロックを再取得する必要はありません。コードロジックを直接実行します。

4)スレッドBがロックを取得すると、JVMはオブジェクトがバイアスされたロック状態にあり、スレッドIDレコードがスレッドBでないことを検出し、スレッドBはCASスピンを介してロックを取得します。Bがロックを正常に取得した場合、オブジェクトがバイアスされたロック状態にある場合は、スレッドIDをスレッドBのIDに変更します。それ以外の場合は、手順5に進みます。

5)Bはロックを取得していないため、ロック競争が激しいことを示しており、この時点でバイアスロックは軽量ロックにアップグレードされます。JVMは、現在のスレッドのスレッドスタックに別のスペースを開き、オブジェクトロックMark Wordへのポインターを保存すると同時に、このスペースへのポインターをオブジェクトロックMarkWordに保存します。操作はCAS操作です。保存が成功すると、ロックが正常に取得され、ロックフラグが00に変更されます。このとき、オブジェクトは次のように軽量ロック状態になります。

6)Bがロックを取得できなかった場合は、ロックが軽量ロックにアップグレードできなかったことを意味します。このとき、ロックは重量ロックに拡張され、ロックフラグは10に変更され、すべてのスレッドがロックを取得していないものはブロック状態になります。、ロックを取得するために回転しません。JVMは、個別のObjectMonitorオブジェクトモニターを作成します。MarkWordポインターはモニターを指します。以下に示すように、ブロックされたすべてのスレッドがリンクリストに追加され、ObjectMonitorオブジェクトロック(つまり、monitorenter、monitorexitロジック)を取得するためにキューに入れられます。

オブジェクトロックがヘビーウェイトロックに展開されると、すべてのスレッドがロックを解放した後でも、オブジェクトロックはロックフリーまたはバイアスロック状態に復元されません。その後、すべてのスレッドロック操作はヘビーウェイトロックになります。

2.1.4。バイアスロックを無効にします(-XX:-UseBiasedLocking

1)オブジェクトがロックされていない場合、つまり通常のオブジェクトの場合、バイアスされたロック状態は0、ロックフラグは01、オブジェクトのhashCode値は次のとおりです。

2)オブジェクトがロックされ、スレッドAがロックを取得すると、JVMは現在のスレッドのスレッドスタックに別のスペースを開きます。これにより、オブジェクトロックMark Wordへのポインタが保存され、同時にオブジェクトロックマークワードこのスペースへのポインタを保存します。上記の2つの保存操作はCAS操作です。保存が成功すると、ロックが正常に取得され、ロックフラグビットが00に変更されます。このとき、オブジェクトが軽量ロック状態になります

オブジェクトのロックを競合する他のスレッドがない場合、スレッドAがロックを解放した後、オブジェクトはステップ1の状態であるロックフリー状態に戻ります。

3)スレッドAが再びロックを取得すると、JVMは、オブジェクトがロックフリー状態の場合は手順2に従って実行し、それ以外の場合(再突入ロック)はロックの取得回数に1を追加するだけでよいことを検出します。 、ロックを再取得せずに、コードロジックを直接実行します

4)スレッドBがロックを取得すると、JVMはオブジェクトが軽量ロック状態にあることを検出します。JVMは現在のスレッドのスレッドスタックに別のスペースを開き、オブジェクトロックへのポインタを保存します。同時にオブジェクトをロックしますこのスペースへのポインタはマークワードに保存されます上記の2つの保存操作はCAS操作です保存が成功した場合はロックが正常に取得されたことを意味しますロックフラグはに変更されます00.この時点で、以下に示すように、オブジェクトは軽量ロック状態になります。

5)Bがロックの取得に失敗した場合、ロックはヘビーウェイトロックに拡張され、ロックフラグは10に変更されます。その後、ロックを取得していないすべてのスレッドはブロック状態になります。JVMは、個別のObjectMonitorオブジェクトモニターを作成します。MarkWordポインターはモニターを指します。以下に示すように、ブロックされたすべてのスレッドがリンクリストに追加され、ObjectMonitorオブジェクトロック(つまり、monitorenter、monitorexitロジック)を取得するためにキューに入れられます。

オブジェクトロックがヘビーウェイトロックに展開されると、すべてのスレッドがロックを解放した後でも、オブジェクトロックはロックフリーまたはバイアスロック状態に復元されません。その後、すべてのスレッドロック操作はヘビーウェイトロックになります。

2.1.5Java出力オブジェクト情報

1)、mavenは、以下に示すように、jol-corejarパッケージを導入します。

        <dependency>
            <groupId>org.openjdk.jol</groupId>
            <artifactId>jol-core</artifactId>
            <version>0.13</version>
        </dependency>

2)、サンプルデモ:

/**
 * synchronized 测试
 *
 * -XX:-UseBiasedLocking 禁止偏向锁,默认偏向锁是开启的
 *
 * @author supu
 * @since 2020-08-31 11:00
 **/
public class SynchronizedDemo {

    public static void main(String[] args) {
        System.out.println(VM.current().details());

        A a = new A();
        a.setAge(10);
        a.setName("zs");

        ClassLayout c2 = ClassLayout.parseInstance(a);

        System.out.println(c2.toPrintable());

        new Thread(() -> {
            synchronized (a){
                System.out.println("*********** a 第一次被锁定后的对象头 markword ***********");
                System.out.println(c2.toPrintable());


                /*synchronized (a){
                    System.out.println("*********** a 第二次被锁定后的对象头 markword ***********");
                    System.out.println(c2.toPrintable());
                }*/

                try {
                    TimeUnit.MILLISECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        try {
            TimeUnit.MILLISECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 当多个线程竞争锁时,偏向锁升级为轻量级锁还是重量级锁的条件时,后者获取锁的线程是否能在指定时间内自旋CAS获取到锁,
        // 如果获取到锁了则升级为轻量级锁,否则升级为重量级锁

        synchronized (a){
            System.out.println("*********** a 第二次被锁定后的对象头 markword ***********");
            System.out.println(c2.toPrintable());
        }

        System.out.println("************ a 释放锁定后的对象头 markword **********");
        System.out.println(c2.toPrintable());

    }

    static class A {
        private int age;
        private String name;

        public int getAge() {
            return age;
        }

        public void setAge(int age) {
            this.age = age;
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (o == null || getClass() != o.getClass()) return false;
            A a = (A) o;
            return age == a.age &&
                    Objects.equals(name, a.name);
        }

        @Override
        public int hashCode() {
            return Objects.hash(age, name);
        }
    }
}

2.2、クラスメタデータアドレス(クラスへのポインタ)

クラスのポインタはオブジェクトの型ポインタを格納するために使用され、ポインタはそのクラスメタデータ情報を指し、JVMはこのポインタを使用してオブジェクトがどのクラスインスタンスであるかを判別します。ポインタのビット長はJVMの1ワードサイズです。つまり、32ビットJVMは32ビットで、64ビットJVMは64ビットです。
アプリケーションのオブジェクトが多すぎる場合、64ビットポインタを使用すると大量のメモリが浪費されます。統計的に言えば、64ビットJVMは32ビットJVMよりも50%多くのメモリを消費します。メモリを節約するために、オプションを使用し+UseCompressedOops てポインタ圧縮オンにすることができ ます。ここで、oopは通常のオブジェクトポインタです。このオプションをオンにすると、次のポインタが32ビットに圧縮されます。

1)、各クラスの静的変数属性ポインター(つまり静的変数)

2)、各オブジェクトのメンバー変数属性ポインター(つまりメンバー変数)

3)、通常のオブジェクト配列の各要素ポインタ

もちろん、すべてのポインターが圧縮されるわけではありません。PermGenへのクラスオブジェクトポインター(JDK8のメタスペースへのクラスオブジェクトポインター)、ローカル変数、スタック要素、入力など、一部の特殊なタイプのポインターはJVMによって最適化されません。パラメータ、戻り値、NULLポインタなど。

2.3。配列の長さ(配列の長さ、配列オブジェクトの場合のみ)

オブジェクトが配列の場合、オブジェクトヘッダーには、配列の長さを格納するための追加のスペースが必要です。データのこの部分の長さも、JVMアーキテクチャによって異なります。32ビットJVMでは、長さは32ビットです。 64ビットJVMは64ビットです。64ビットJVM+UseCompressedOops オプションがオンになっている 場合、領域の長さも64ビットから32ビットに圧縮されます

3つ、インスタンスデータ

オブジェクトのインスタンスデータは、Javaコードで確認できる属性とその値です

第四に、パディングバイトを揃えます

JVMではJavaオブジェクトのメモリサイズを8ビットの倍数にする必要があるため、オブジェクトのサイズを8ビットの倍数に入力するのに数バイト遅れており、それ以外の特別な機能はありません。

5、参照

https://wiki.openjdk.java.net/display/HotSpot/Synchronization

https://www.jianshu.com/p/3d38cba67f8b

おすすめ

転載: blog.csdn.net/ywlmsm1224811/article/details/108322059
おすすめ