JVM内部ロックのアップグレードプロセスについて話す

JVM内部ロックのアップグレードプロセスについて話す

[[408735]]

なぜこれについて話すのですか?

AQS をまとめた後、この点について確認してみましょう。この記事は、次のような高頻度の問題から始まります。

  • メモリ内のオブジェクトのメモリレイアウトは何ですか?
  • synchronized と ReentrantLock の基礎となる実装と、再入可能性の基本原理について説明します。
  • AQSについてお話しましょう。 AQS CAS+ の基になるレイヤーが揮発性なのはなぜですか?
  • ロックの 4 つの状態とロックのアップグレード プロセスについて説明してください。
  • Object o = new Object() はメモリ内で何バイトを占有しますか?
  • スピンロックは必ずヘビーウェイトロックよりも効率的ですか?
  • バイアスロックを開くと効率は向上しますか?
  • ヘビーウェイトロックの何がそんなに重要なのでしょうか?
  • 重量級のロックが軽量級のロックよりも効率的なのはどのような場合ですか。またその逆の場合も同様ですか。

2番目のロックはどうなりましたか?

無意識にロックが使用される状況:

  1. //System.out.printlnすべてロックされています
  2. パブリックvoid println(文字列x) {
  3. 同期された(これ){
  4. 印刷(x);
  5. 新しい行();
  6. }
  7. }

シンプルロックはどうなったんですか?

ロック後に何が起こるかを理解するには、オブジェクトが作成された後のメモリ内のレイアウトがどのようになっているかを確認する必要があります。

オブジェクトが作成されると、メモリ内で主に 4 つの部分に分割されます。

  • マークワード部分は実際にはロックの中核であり、GC であるかどうか、Young GC を何回生き残ったかなど、オブジェクトのライフ情報も含まれています。
  • klass ポインターは、オブジェクトへのクラス ファイル ポインターを記録します。
  • インスタンス データは、オブジェクト内の変数データを記録します。
  • パディングは配置に使用されます。 64 ビット サーバー バージョンでは、オブジェクト メモリは 8 バイトで割り切れる必要があります。割り切れない場合は、整列によって埋められます。たとえば、新しいオブジェクトは 18 バイトのメモリしか占有しませんが、8 で割り切れる必要があるため、padding=6 になります。

これら4つの部分がわかったら、最下層を確認してみましょう。サードパーティ パッケージ JOL = Java Object Layout を使用して、Java メモリ レイアウトを確認します。わずか数行のコードでメモリ レイアウト スタイルを確認できます。

  1. パブリッククラス JOLDemo {
  2. プライベート静的オブジェクト o;
  3. 公共 静的void main(String[] args) {
  4. o = 新しいオブジェクト();
  5. 同期(o){
  6. システム。出力.println(ClassLayout.parseInstance(o).toPrintable());
  7. }
  8. }
  9. }

結果を印刷します:

出力結果から:

1) オブジェクト ヘッダーには 3 行に分割された 12 バイトが含まれており、最初の 2 行は実際にはマークワードであり、3 行目はクラス ポインターです。ロックの前後で出力が 001 から 000 に変わることに注目してください。マークワードの使用法: 8 バイト (64 ビット) のヘッダーにはいくつかの情報が記録され、ロックによってマークワードの内容が変更されます。 8 バイト (64 ビット) のヘッダーにはいくつかの情報が記録され、ロックによってマークワードの内容が変更されます。 8 バイト (64 ビット) のヘッダーにはいくつかの情報が記録されます。 001ロック解除状態から00軽量ロック状態に変更しました。

2) 16 バイトを占める新しいオブジェクトを作成します。オブジェクト ヘッダーは 12 バイトを占めます。オブジェクトには追加の変数がないため、instance = 0 です。オブジェクトのメモリ サイズは 8 バイトで割り切れる必要があることを考慮すると、padding = 4 となり、最終的に new Object() のメモリ サイズは 16 バイトになります。

拡張: どのようなオブジェクトが旧世代に入りますか?多くのシナリオでは、たとえば、オブジェクトが大きすぎる場合は直接入ることがありますが、ここでは、Young GC のオブジェクトが最大 15 回の Young GC (年齢は調整可能、デフォルトは 15) を生き延びた場合に、なぜ Old 領域に入るのかについて説明します。上図では、ホットスポットのマークワードで世代年齢を表すために 4 ビットが使用されているため、表現できる最大範囲は 0 ~ 15 です。そのため、新しい世代の年齢は 15 を超えることはできません。職場で -XX:MaxTenuringThreshold を使用して調整できますが、通常は変更しません。

3つのロックのアップグレードプロセス

1 ロックアップグレードの検証

ロックのアップグレードについて説明する前に、実験をしてみましょう。コードは 2 つあり、違いは、1 つは途中で 5 秒間スリープさせ、もう 1 つはスリープさせないことです。それが何か違いをもたらすかどうか見てみましょう。

  1. パブリッククラス JOLDemo {
  2. プライベート静的オブジェクト o;
  3. 公共 静的void main(String[] args) {
  4. o = 新しいオブジェクト();
  5. 同期(o){
  6. システム。出力.println(ClassLayout.parseInstance(o).toPrintable());
  7. }
  8. }
  9. }
  10. --------------------------------------------------------------------------------------------------  
  11. パブリッククラス JOLDemo {
  12. プライベート静的オブジェクト o;
  13. 公共 静的void main(String[] args) {
  14. 試してください { Thread.sleep(5000); } キャッチ (InterruptedException e) { e.printStackTrace(); }
  15. o = 新しいオブジェクト();
  16. 同期(o){
  17. システム。出力.println(ClassLayout.parseInstance(o).toPrintable());
  18. }
  19. }
  20. }

これら 2 つのコードには違いがありますか?実行して結果を確認します。

興味深いのは、メインスレッドを 5 秒間スリープさせた後のメモリレイアウト出力が、メインスレッドをスリープさせない場合の出力結果と異なることです。

Syn ロックがアップグレードされると、jdk1.8 バージョンの基盤となるデフォルト設定では、バイアス ロックが 4 秒後に有効になります。これは、バイアスロックが 4 秒以内にアクティブ化されず、ロックが追加されると、軽量ロックに直接アップグレードされることを意味します。

それで、いくつか質問があります。

  • なぜロックをアップグレードする必要があるのですか?以前は SYN がデフォルトのヘビーウェイト ロックではなかったのですか?それを使わないか、何か他のものを使うことはできないでしょうか?
  • 4 秒以内にロックを追加すると直接軽量化されるため、バイアス ロックを使用することはできませんか?なぜバイアスロックがあるのでしょうか?
  • バイアス ロックを 4 秒後に開始するように設定する必要があるのはなぜですか?

質問 1: ロックをアップグレードする必要があるのはなぜですか?ロックされている場合は、ロックされています。もう一度ロックする必要はないでしょうか?

まず、初期の JDK 1.2 の効率が非常に低いことは明らかです。当時、シンはヘビー級のロックでした。ロックを申請するには、オペレーティング システムのボスであるカーネルを介してシステム コールを実行し、ソート操作のキューに入り、操作が完了したらユーザー状態に戻る必要があります。

カーネル状態: ユーザー状態がハードウェアに直接アクセスする危険な操作を実行したい場合、ハードウェアを強制終了するのは簡単です (フォーマット、ネットワーク カードへのアクセス、メモリへのアクセスなど)。システム セキュリティのため、オペレーティング システムはユーザー状態とカーネル状態の 2 つの層に分かれています。ロック リソースを適用する場合、ユーザー状態をオペレーティング システムのカーネル状態に適用する必要があります。 Jdk1.2 では、ユーザーはカーネル状態からロックを申請する必要があり、その後カーネル状態がそれをユーザー状態に渡します。このプロセスは非常に時間がかかり、初期段階では効率が極めて低くなります。 JVM で処理できる一部のタスクをオペレーティング システムに任せるのはなぜでしょうか?効率を向上させるために、JVM で完了できるロック操作を抽出できますか?そのため、ロックの最適化が提案されます。

質問 2: バイアス ロックはなぜ必要なのでしょうか?

実際、これは本質的に確率の問題です。統計によると、日常的に使用する syn lock プロセスの 70% ~ 80% のケースでは、ロックを取得するスレッドは通常 1 つだけです。例えば、よく使う System.out.println や StringBuffer は最下層に syn ロックがありますが、基本的にマルチスレッド競合はありません。この場合、軽量ロック レベルにアップグレードする必要はありません。バイアスの重要性は、最初のスレッドがロックを取得し、そのロックに独自のスレッド情報をマークするため、次回ロックを取得するときに検証のためにロックを取得する必要がないことです。複数のスレッドがロックを取得しようとすると、バイアス ロックは取り消され、軽量ロックにアップグレードされます。実際、厳密に言えば、バイアス ロックは実際のロックではないと思います。バイアス ロックは、共有リソースにアクセスしようとするスレッドが 1 つだけである場合にのみ発生するためです。

ロックが意図せず使用されるシナリオ:

  1. /***StringBuffer 内部同期***/
  2. パブリック同期int長さ() {
  3. 戻る カウント;
  4. }
  5.  
  6. //System.out.println 無意識のロックの使用
  7. パブリックvoid println(文字列x) {
  8. 同期された(これ){
  9. 印刷(x);
  10. 新しい行();
  11. }
  12. }

質問 3: JDK8 ではなぜ 4 秒後にバイアス ロックを有効にする必要があるのですか?

実際のところ、これは妥協です。コードが最初に実行されるときには、ロックを取得しようとするスレッドが多数あるはずです。バイアスロックをオンにすると、効率が低下します。したがって、上記のプログラムが 5 秒間スリープした後、バイアス ロックがオンになります。バイアスロックを追加すると効率が低下するのはなぜですか?途中にいくつかの余分なプロセスがあるからです。バイアス ロックが設定された後、複数のスレッドが共有リソースを競合する場合、ロックを軽量ロックにアップグレードする必要があります。このプロセスでは、アップグレード前にバイアス ロックを取り消す必要があるため、効率が低下します。なぜ4sなのですか?これは統計的な時間値です。

もちろん、パラメータ -XX:-UseBiasedLocking = false を設定することで、バイアス ロックを無効にすることもできます。 jdk15 以降では、バイアス ロックはデフォルトで無効になっています。この記事では、jdk8 環境でのロックのアップグレードを検証します。

2 ロックのアップグレードプロセス

上記では、オブジェクトが作成後、ロックフリー状態→バイアスロック(有効な場合)→軽量ロックの順にメモリに入るプロセスを検証しました。ロックのアップグレードプロセスが進むにつれて、軽量ロックは重量級ロックになります。まず、軽量ロックとは何かを理解する必要があります。 1 つのスレッドがリソースを取得する (バイアス ロック) 状態から、複数のスレッドがリソースを取得して軽量ロックにアップグレードする状態まで、スレッドの数がそれほど多くない場合、これは実際には CAS (Compare and Swap と呼ばれる、値の比較と交換) として理解できます。並行プログラミングの最も単純な例は、concurrent パッケージのアトミック操作クラス AtomicInteger です。 ++ のような操作を実行する場合、基礎となるレイヤーは実際には CAS ロックです。

  1. パブリッククラス JOLDemo {
  2. プライベート静的オブジェクト o;
  3. 公共 静的void main(String[] args) {
  4. o = 新しいオブジェクト();
  5. 同期(o){
  6. システム。出力.println(ClassLayout.parseInstance(o).toPrintable());
  7. }
  8. }
  9. }
  10. --------------------------------------------------------------------------------------------------  
  11. パブリッククラス JOLDemo {
  12. プライベート静的オブジェクト o;
  13. 公共 静的void main(String[] args) {
  14. 試してください { Thread.sleep(5000); } キャッチ (InterruptedException e) { e.printStackTrace(); }
  15. o = 新しいオブジェクト();
  16. 同期(o){
  17. システム。出力.println(ClassLayout.parseInstance(o).toPrintable());
  18. }
  19. }
  20. }

質問 4: どのような状況で軽量ロックを重量ロックにアップグレードする必要がありますか?

まず考えられるのは、スレッドが複数ある場合は、まず軽量ロックを解除し、それが通せない場合にのみ重量ロックにアップグレードすることです。では、どのような状況では軽量ロックではその役割を果たせないのでしょうか? 1. スレッドが多すぎる場合 (たとえば、最初に 10,000 個)、CAS が値を交換するのにどのくらいの時間がかかりますか?同時に、CPU はこれらの 10,000 個のアクティブ スレッドを切り替えるだけで膨大なリソースを消費します。この場合、当然、重量級のロックにアップグレードされ、キュー管理のためにオペレーティング システムに直接呼び出されます。この場合、たとえ 10,000 個のスレッドがあったとしても、ウェイクアップのためにキューイングされるのを待っている休止状態も処理することになります。 2. CAS が 10 回スピンしてもロックを取得できない場合は、ヘビー級にアップグレードされます。

一般的に、2 つの状況は軽量から重量にアップグレードされます。 10 回スピンしているスレッドや CPU スケジューリングを待機しているスレッドの数が CPU コア数の半分を超えると、自動的に重量ロックにアップグレードされます。サーバーの CPU のコア数を確認するには、top コマンドを入力して 1 を押します。

質問 5: 誰もが syn はヘビー級のロックだと言いますが、その重要性は何ですか?

JVM は遅延型であり、スレッド関連の操作はすべてオペレーティング システムに任せます。たとえば、スケジュール ロックの同期は、実行のためにオペレーティング システムに直接引き渡されます。オペレーティング システムでは、実行するには、まずキューに入る必要があります。さらに、スレッドを開始するときにオペレーティング システムが大量のリソースを消費する必要があり、リソースの消費が比較的多くなります。これが重い点です。

ロックのアップグレードプロセス全体を図に示します。

同期の4つの基本実装

上記のオブジェクトのメモリ レイアウトをある程度理解すると、ロック ステータスは主にマークワードに格納されることがわかります。ここでは、基礎となる実装について見ていきます。

  1. パブリッククラスRnEnterLockDemo {
  2. パブリックvoidメソッド(){
  3. 同期された(これ){
  4. システム。 .println( "start" );を出力します
  5. }
  6. }
  7. }

この単純なコードを分解して何が起こるか見てみましょう。 javap -c RnEnterLockDemo.class

まず、syn にはロック操作が必要であることがわかります。表示される情報には、monitorenter と monitorexit が表示されます。これらはロックとロック解除に関する指示であると主観的に推測できます。興味深いのは、1 monitorenter と 2 monitorexit です。なぜ?通常、ロックとリリースロックが 1 つずつあるはずです。実際、syn と lock の違いもここに反映されています。 syn は JVM レベルのロックです。例外が発生した場合、自分で解放する必要はありません。JVM が自動的に例外の解放を支援します。この手順は追加の monitorexit に依存します。ロック例外は手動でキャプチャして解放する必要があります。

これら 2 つの命令の機能については、JVM 仕様の説明を直接参照します。

モニター入力:

各オブジェクトはモニターに関連付けられています。モニターは、所有者がいる場合にのみロックされます。 monitorenter を実行するスレッドは、次のように、objectref に関連付けられたモニターの所有権を取得しようとします。? objectref に関連付けられたモニターのエントリ数が 0 の場合、スレッドはモニターに入り、そのエントリ数を 1 に設定します。スレッドが既にオブジェクト参照に関連付けられたモニターを所有している場合、スレッドはモニターのエントリ数がゼロになるまでブロックし、その後所有権の取得を再度試みます。

翻訳する:

各オブジェクトにはモニター ロックがあります。モニターが使用されている場合、モニターはロックされた状態になります。スレッドが monitorenter 命令を実行すると、モニターの所有権を取得しようとします。プロセスは次のとおりです。

  • モニターのエントリ数が 0 の場合、スレッドはモニターに入り、エントリ数を 1 に設定し、スレッドがモニターの所有者になります。
  • スレッドがすでにモニターを所有していて、再度入る場合、モニターへのエントリ カウントは 1 増加します。
  • 他のスレッドがすでにモニターを占有している場合、スレッドはモニターのエントリ数が 0 になるまでブロック状態になり、その後モニターの所有権を再度取得しようとします。

モニター終了:

monitorexit を実行するスレッドは、objectref によって参照されるインスタンスに関連付けられたモニターの所有者である必要があります。

翻訳する:

monitorexit を実行するスレッドは、objectref に対応するモニターの所有者である必要があります。命令が実行されると、モニターのエントリ番号が 1 減少します。エントリ番号が 1 減少した後に 0 になった場合、スレッドはモニターを終了し、このモニターの所有者ではなくなります。このモニターによってブロックされた他のスレッドは、このモニターの所有権を取得しようとする可能性があります。

この段落の説明を通じて、Synchronized の実装原理を明確に理解することができます。同期は最下層のモニターオブジェクトを通じて完了します。 wait/notify などのメソッドは、実際にはモニター オブジェクトに依存します。このため、wait/notify などのメソッドは、同期されたブロックまたはメソッド内でのみ呼び出すことができ、それ以外の場合は java.lang.IllegalMonitorStateException がスローされます。

各ロック オブジェクトには、ロック カウンターと、ロックを保持しているスレッドへのポインターがあります。

monitorenter を実行したときに、対象オブジェクトのカウンターがゼロの場合は、他のスレッドによって保持されていないことを意味します。 Java 仮想マシンは、ロック オブジェクトの保持スレッドを現在のスレッドとして設定し、そのカウンターを i だけ増加します。ターゲット ロック オブジェクトのカウンターがゼロでない場合、ロック オブジェクトの保持スレッドが現在のスレッドであれば、Java 仮想マシンはカウンターに 1 を追加できます。それ以外の場合は、保持スレッドがロックを解放するまで待機する必要があります。 monitorexit が実行されると、Java 仮想マシンはロック オブジェクトのカウンターを 1 減らす必要があります。カウンターが 0 の場合、ロックが解放されたことを示します。

要約する

これまでの経験上、シンクロナイズドを使えば、重量級のロックになると考えられます。これは JDK 1.2 より前では当てはまりましたが、後に重すぎてオペレーティング システムのリソースを大量に消費することが判明したため、同期が最適化されました。将来的にはそのまま使用できます。ロックの強度に関しては、基盤となる JVM がすでにそれを実行しているので、それを直接使用することができます。

最後に、最初の質問を見て、すべて理解できたかどうかを確認します。疑問を念頭に置いて調査すると、物事がより明確になることがよくあります。これが皆さんのお役に立てば幸いです。

<<:  ガートナー:Amazon、Microsoft、Alibabaが2020年の世界パブリッククラウドサービスのトップ3にランクイン

>>:  デジタル産業を支援し、インテリジェントな未来をつなぐ――西安航空基地企業「ファーウェイ参入」デジタル変革社長クラス

推薦する

垂直型電子商取引:土地の奪い合いか、それとも集約的な耕作か?

新婚生活がこんなに短いとは思いませんでした。メディアの報道によると、両者が協力を開始してから1年も経...

中小規模のウェブサイトはニッチなトピックに頼って生き残ることができるでしょうか?

中小規模のウェブサイトは、その特性上、適切なトピックを選択することが特に重要です。ウェブサイトが適切...

bluevm-512MメモリKVMとOVZの年間支払額はわずか9.9米ドル

bluevm は、この誕生月にサプライズをプレゼントすると言っています。512M メモリを搭載した ...

A5マーケティング:サードパーティのリソースを通じて企業ウェブサイトの品質を向上させる方法

インターネット データの規模が拡大し続けるにつれて、競争の激しいインターネット上には同質の Web ...

[米国の格安VPSのおすすめ] RackNerd: 年間10ドル以下、AMD Ryzen、高防御、大容量ハードディスク、大容量トラフィック

Racknerd VPS は、米国の格安 VPS の中でも代表的な存在であり、格安 VPS の推奨ブ...

SaaS 時代のデータ セキュリティの再考

今日、SaaS 駆動型のフロントエンド システムと IaaS/PaaS がデータ センター テクノロ...

Xiong Zhanghao コンテンツ ページの関連性を向上させるにはどうすればよいでしょうか?

2018年最もホットなプロジェクト:テレマーケティングロボットがあなたの参加を待っていますコンテンツ...

Baiduアルゴリズムの変更にどう対処するか

ウェブマスターにとって、2012年の百度は間違いなく頭痛の種でした。今年、百度のアルゴリズムは非常に...

ハイブリッドクラウドのセキュリティのベストプラクティス

クラウド環境のセキュリティ保護は困難であり、ハイブリッド クラウド環境のセキュリティ保護はさらに困難...

NBAスターのジェレミー・リンの人気がドメイン名登録ブームを巻き起こす

最近スポーツ界で目覚ましい活躍を見せているのは、NBAのニューヨーク・ニックスの中国系アメリカ人選手...

レンタカー業界でビデオマーケティングを実施する方法

ビデオ マーケティングには長い歴史があります。テレビ コマーシャルからオンライン ビデオまで、すべて...

Chuanyouzhong.com は、年末までに 2,000 人の消費者ユーザーを獲得するという任務を中間管理職に割り当てました。

Chuanyouzhong.comの共同創設者であるSun Tongyu氏は、同社の中間管理職に、年...

vmhaus - Alipay 支払い、$7/KVM/2 コア/2G メモリ/30g SSD/4T トラフィック/ロサンゼルス/ロンドン

vmhausは正式にAlipay決済を受け入れると発表し、後日WeChat決済とUnionPay決済...

自分と敵を知ることは、紙の上で話すだけでは十分ではありません。ウェブサイト分析の正しい方法の解釈

ウェブサイト分析は、すべての SEO 最適化担当者にとって不可欠なスキルです。最適化担当者が最適化が...

リンクは諸刃の剣です。広範囲にわたる多様化は偶然ではありません。

検索エンジンがユーザーエクスペリエンスにますます注意を払う傾向にありますが、これはリンクやオリジナル...