Java JVM の秘密を解明

Java JVM の秘密を解明

この記事では、JVM メモリ モデル、クラス ローダー、GC 回復アルゴリズム、GC コレクターなど、JVM の分析に焦点を当て、全体的に理論に焦点を当てます。

この記事は初心者向けではなく、開発経験が 3 年以上ある技術者向けです。皆様のコミュニケーションと共有を歓迎します。記事に欠点があれば、読者はぜひ指摘してください。よろしくお願いします。

[[335548]]

1. jdk、jre、jvmの関係を明確にする

次の図は、公式サイトの jdk、jre、jvm のアーキテクチャ図です。このアーキテクチャ図から、次の 3 つの関係を簡単に確認できます。

(1)JDKにはJREが含まれ、JREにはJVMが含まれる

(2)JDKは主に開発環境で使用され、JREは主にリリース環境で使用されます。もちろん、リリース環境で JDK を使用しても問題はありませんが、パフォーマンスに若干影響が出る可能性があります。 JDK と JRE の関係は、プログラムのデバッグ バージョンとリリース バージョンの関係に少し似ています。

(3)ファイルサイズに関しては、JDKはJREよりも大きいです。図からわかるように、jdk には、よく使用される javac、java コマンドなど、jre よりも 1 つ多くのツールキットがあります。

クラス2ローダー

jvm クラスローダーに関しては、次のように要約できます。

1. クラスローダーが必要なのはなぜですか?

(1)バイトコードファイルをランタイムデータ領域にロードする。 .java ソースコードが Javac コマンドによってコンパイルされた後に生成されたバイトコードファイル (.class) は、クラスローダーを介して jvm にロードされます。

(2)実行時にバイトコードファイルのデータ領域の一意性を判断する。同じバイトコード ファイルは、異なるクラス ローダーを通じて異なるファイルを形成します。したがって、実行時のバイトコード ファイルのデータ領域の一意性は、バイトコード ファイルとそれをロードするクラス ローダーによって決まります。

2. クラスローダーの種類

クラスローダーは 4 つのカテゴリに分類できます。

(1)ブートストラップクラスローダー:このクラスローダーはクラスローダーの最上位レベルにあり、主に/jre/lib/rt.jarなどのJREコア関連のjarパッケージをロードします。

(2)拡張クラスローダー:このクラスローダーはクラスローダー階層の2番目のレベルにあり、主に/jre/lib/ext/*.jarなどのJRE拡張関連のjarパッケージをロードします。

(3) アプリケーションクラスローダーアプリ: このクラスローダーはクラスローダーの第3層に位置し、主にクラスパス(classpath)の下にある関連するjarパッケージをロードします。

(4)ユーザークラスローダー:このクラスローダーは、主にユーザーが指定したパス内の関連するjarパッケージをロードするユーザー定義のクラスローダーです。

3. クラスローダーメカニズム(親委任)

バイトコードのロードの場合、クラスのロード メカニズムは親の委任です。親の委任とは何ですか?

クラス ローダーはバイトコード ファイルを取得した後、それを直接ロードするのではなく、バイトコード ファイルを直接の親クラス ローダーに渡し、その直接の親クラス ローダーはそれをさらに直接の親クラス ローダーに渡し、これをルート親クラス ローダーに渡します。ルート親ローダーの場合

ロードできる場合はロードされます。それ以外の場合は、ロードのために直接の子ローダーに渡されます。直接の子ローダーがロードできる場合は、ロードされます。そうでない場合は、その直接の子クラスローダーが順番にプッシュされます。いずれもロードできない場合は、最終的にはユーザー定義のクラスローダーによってロードされます。

4.JDK 1.8 でクラスローダーを実装するにはどうすればよいですか?

以下は再帰を使用したJDK 1.8クラスローダーの実装です。

  1. 保護されたクラス<?> loadClass(文字列、ブール値解決)
  2. ClassNotFoundException をスローします
  3. {
  4. 同期化 (getClassLoadingLock(名前)) {
  5. //まずクラスがすでにロードされているかどうかを確認します
  6. クラス<?> c = findLoadedClass(名前);
  7. c == nullの場合{
  8. 長いt0 = System.nanoTime();
  9. 試す {
  10. 親がnull場合
  11. c = parent.loadClass(名前 false );
  12. }それ以外{
  13. c = findBootstrapClassOrNull(名前);
  14. }
  15. } キャッチ (ClassNotFoundException e) {
  16. // クラスが見つからない場合は ClassNotFoundException がスローされます
  17. // nullでない親クラスローダーから
  18. }
  19.  
  20. c == nullの場合{
  21. //それでも見つからない場合は findClassを呼び出します 注文 
  22. //クラスを見つけます
  23. 長い t1 = System.nanoTime();
  24. c = findClass(名前);
  25.  
  26. // これは定義クラスローダーです統計を記録する
  27. sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
  28. sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
  29. sun.misc.PerfCounter.getFindClasses().increment();
  30. }
  31. }
  32. if (解決) {
  33. クラスを解決します(c);
  34. }
  35. cを返します
  36. }
  37. }

5. 親の委任モデルの破壊

場合によっては、ロード スコープの制限により、親クラス ローダーが必要なファイルをロードできないため、親クラス ローダーは対応するバイトコード ファイルをロードするために子クラス ローダーに委任する必要があります。

たとえば、データベース ドライバー インターフェイス Driver は JDK で定義されていますが、このインターフェイスの実装はさまざまなデータベース ベンダーによって実装されているため、次の問題が発生します。

実行されたDriverManagerは、統一された管理を実現するためにDriverインターフェースを実装する関連実装クラスをロードする必要がありますが、Bootstrap ClassLoaderはjre/libの下にある対応するファイルのみをロードでき、

さまざまなメーカーによって実装された Dirver インターフェース実装クラス (Dirver 実装クラスは、Application ClassLoader によってロードされます)。この時点で、Bootstrap ClassLoader は、サブクラス ローダーにドライバーをロードするよう委任する必要があります。

これを達成するために、親の委任モデルを破壊します。

3種類のライフサイクル

JVM における Java クラスのライフ サイクルは、おおよそ 5 つの段階に分けられます。

1. ロードフェーズ: バイトコードバイナリストリームを取得し、静的ストレージ構造をメソッド領域のランタイムデータ構造に変換し、クラスのデータアクセスエントリとしてメソッド領域に対応するクラスオブジェクト (java.lang.Class オブジェクト) を生成します。

2. 接続フェーズ: このフェーズは、検証、準備、解析の3つのサブフェーズで構成されています。

(1)検証:バイトコードファイルが、メタデータ検証、ファイル形式の検証、バイトコード検証、シンボル検証などの仮想マシン仕様の要件に準拠していることを確認します。

(2)準備:静的テーブルにメモリを割り当て、JVMのデフォルト値を設定します。非静的変数の場合、この段階でメモリを割り当てる必要はありません。

(3)分析:定数プール内のシンボリック参照を直接参照に変換する

3. 初期化フェーズ: クラスオブジェクトが使用される前に必要な初期化作業

以下はブロガーからの引用ですが、非常に良い説明だと思います。

Java コードでは、静的フィールドを初期化する場合、宣言時に値を直接割り当てることも、静的コード ブロック内で値を割り当てることもできます。

final static によって変更された定数を除き、直接代入操作と静的コード ブロック内のすべてのコードは、Java コンパイラによって同じメソッド内に配置され、<clinit> という名前が付けられます。初期化の目的は、

定数値のフィールド割り当てと <clinit> メソッドの実行。 Java 仮想マシンはロックを使用して、クラスの <clinit> メソッドが 1 回だけ実行されるようにします。

どのような条件でクラスの初期化が行われますでしょうか?

(1)仮想マシン起動時に、ユーザが指定したメインクラス(メイン関数)を初期化する。

(2)新しいターゲットクラスのインスタンスを作成するための新しい命令に遭遇したとき、新しい命令のターゲットクラスを初期化する。

(3)静的メソッドを呼び出す命令に遭遇したときは、静的メソッドが配置されているクラスを初期化する。

(4)サブクラスの初期化は親クラスの初期化をトリガーする。

(5)インターフェースがデフォルトメソッドを定義している場合、そのインターフェースを直接または間接的に実装するクラスの初期化は、インターフェースの初期化をトリガーする。

(6)リフレクションAPIを使用してクラスをリフレクションする場合は、クラスを初期化します。

(7)MethodHandleインスタンスが初めて呼び出されると、MethodHandleが指すメソッドのクラスが初期化される。

4. 使用フェーズ: JVM でのオブジェクトの使用

5. アンロード フェーズ: オブジェクトを JVM からアンロードします。 JVM がクラスをアンロードする条件は何ですか?

(1)クラスをロードしたクラスローダーがリサイクルされた

(2)このクラスのインスタンスはすべてリサイクルされている

(3)このクラスに対応するjava.lang.Classオブジェクトはどこからも参照されていない

4つのJVMメモリモデル

1. JVM メモリ モデルとは何ですか?

以下は、JVM メモリ モデル アーキテクチャの図です。これまでの記事でも取り上げてきたので、ここでは一つ一つ説明せず、主にヒープ領域について解説します。

JDK 1.8 より前では、ヒープ領域は主に新しい世代、古い世代、永続的な世代に分かれていました。 JDK 1.8 以降では、永続世代が削除され、MetaSpace 領域が追加されました。ここでは、主に jdk 1.8 を共有します。

JDK 1.8 によれば、ヒープ領域のロジックは次の 3 つの部分に抽象化されます。

(1)新世代:エデン領域、S0領域(フロム領域とも呼ばれる)、S21(TO領域とも呼ばれる)を含む

(2)旧世代

(3)メタスペース

2. 新世代と旧世代のメモリサイズはどれくらいですか?

公式の推奨によれば、新しい世代が 3 分の 1 (Eden:S0:S1=8:1:1) を占め、古い世代が 3 分の 2 を占めるため、メモリ割り当て図は次のようになります。

3.GCリサイクルはどのように機能しますか?

オブジェクトはまずエデンエリア内を走ります。 Eden メモリがいっぱいになると、Eden は未使用のオブジェクトを再利用し、再利用されていないオブジェクトを s0 領域に配置するという 2 つの操作を実行します。このとき、s0 領域と s1 領域の名前が交換されます。つまり、s0->s1、s1->s0 となります。エデンエリアでオブジェクトをリサイクルすると、スペースが解放されます。次回 Eden がいっぱいになると、同じ手順がループで実行されます。 Eden 領域がリサイクルされると、残りのオブジェクトが s0 の容量を超えると、マイナー GC がトリガーされます。このとき、再利用されなかったオブジェクトは古い領域に配置され、ループで実行されます。 Eden 領域がマイナー GC をトリガーする場合、残りのオブジェクト容量が古い領域の残りの容量より大きい場合、古い領域はメジャー GC をトリガーし、この時点でフル GC がトリガーされます。通常、メジャー GC にはフル GC が伴うことに注意してください。フル GC はパフォーマンスに非常に悪影響を与えるため、JVM を調整するときは注意してください。

以下は、監視ツール VisualVM を使用して実稼働環境で取得した GC グラフです。

4. ガベージコレクションアルゴリズムとは何ですか?

(1)マークスイープアルゴリズム

アルゴリズムは、マーキング フェーズとクリア フェーズの 2 つのフェーズに分かれています。まず、リサイクル対象となるすべてのオブジェクトにマークを付け、次にマークされたオブジェクトをリサイクルします。このアルゴリズムは非効率であり、メモリの断片化が発生しやすくなります。

a.効率が低い: メモリを2回トラバースする必要がある。1回目はマークするため、2回目はマークされたオブジェクトをリサイクルするため。

b.非連続なメモリセグメントであるため、断片化が発生しやすくなります。オブジェクトが大きすぎる場合、Full GC が発生する可能性が高くなります。

次の図は、リサイクル前とリサイクル後のマークスイープアルゴリズムの比較図です。

(2)マークコピーアルゴリズム

このアルゴリズムは、「マーク アンド スイープ」アルゴリズムの効率の低さとメモリの断片化の問題を解決します。メモリを同じサイズの 2 つのブロックに分割し、一度にそのうちの 1 つだけを使用します。ブロックの 1 つをリサイクルする必要がある場合、そのブロック内の残存オブジェクトを別のブロックにコピーし、そのメモリ ブロックを一度にクリーンアップするだけで、このサイクルを何度も繰り返すことができます。

次の図はリサイクル前とリサイクル後のマークコピーアルゴリズムの簡単な図です。

ただし、若い世代のほとんどのオブジェクトの滞留時間は非常に短く、オブジェクトの 98% はすぐにリサイクルされ、生き残るオブジェクトは非常に少ないため、メモリを 1:1 に分割する必要はなく、8:1:1 に分割する必要があります。

生き残った 2% のオブジェクトを s0 (ゾーンから) に配置するだけです。

以下は、Eden:s0:s1 =8:1:1によるパーティションの概略図です。

(3)マーククリアアルゴリズム

アルゴリズムは、マーキングとソートの 2 つの段階に分かれています。まず、生き残ったすべてのオブジェクトがマークされ、これらのオブジェクトは一方の端に移動され、次に終了境界の外側のメモリが直接クリーンアップされます。古い世代のオブジェクトはより長く存続するため、このアルゴリズムはそれらに適しています。

マーキング プロセスは依然として「マーク アンド スイープ」プロセスと一致していますが、後続の手順では、リサイクル可能なオブジェクトを直接クリーンアップするのではなく、残っているすべてのオブジェクトを一方の端に移動し、終了境界の外側のメモリを直接クリーンアップします。

以下は、「マークソートアルゴリズム」の回収期間と回収後の概略図である。

(4)世代別コレクションアルゴリズム

このアルゴリズムは、世代の考え方を採用した現在の JVM アルゴリズムです。モデルは次のとおりです。

5.一般的な GC コレクターは何ですか?

(1)シリアルGC

SerialGC はシリアル コレクターとも呼ばれ、最も基本的な GC コレクターです。主にシングルコアCPUに適しています。新しい世代ではコピー アルゴリズムが使用され、古い世代ではマーク圧縮アルゴリズムが使用されます。操作中にアプリケーションを一時停止する必要があります。

これにより STW 問題が発生し、JVM アノテーション パラメータは -XX:+UseSerialGC になります。

(2)並列GC

ParallelGC は SerialGC に基づいています。主に SerialGC のシリアル問題を解決し、それを並列問題に変更し、マルチスレッド問題を解決しますが、STW 問題も発生します。 JVM の主なパラメータは次のとおりです。

a.-XX:+UseParNewGC、これは新しい世代が並列(コピーアルゴリズム)であり、古い世代がシリアル(マーク圧縮)であることを意味します。

b.XX:+UseParallelOldGC、旧世代も並列です

(3)CMSGC

CMSGC は、「マークスイープ アルゴリズム」を使用する旧世代のコレクターであり、STW の問題は発生しません。 JVM のパラメータ設定は次のとおりです。

-XX:+UseConcMarkSweepGC、古い世代がCMSコレクターを使用することを示します

(4)まずゴミから

Garbage First は、短い一時停止の要件を満たしながら高いスループットを実現する JVM ガベージ コレクターです。マルチコア CPU と大容量メモリを搭載したサーバーに適しており、JDK9 のデフォルトのガベージ コレクターでもあります。

5. 結論

JVM メモリ モデルは、jdk、jre、jvm の関係、jvm クラス ローダー、jvm ヒープ メモリ分割、GC コレクター、GC リサイクル アルゴリズムなどに重点を置いて深く分析されています。全体的なアプローチは理論に偏っています。スペースの制限により、この記事ではこれらのテクノロジが実際の JVM チューニングでどのように使用されるかについては分析しませんが、これについては次の記事で紹介します。

<<:  CIOがマネージドクラウドサービスプロバイダーの新たなベンチマークを設定

>>:  「数秒で理解できます!」 JVM 仮想マシンの詳細なグラフィックとテキストの説明!全然難しくない

推薦する

ウェブサイトのコンバージョン率を向上させるには、まず何から始めるべきか - A5 Webmaster Network

トラフィックの守護者になるか、それともコンバージョン率の改革者になるか? トラフィックは、Web サ...

Weiboマーケティングは時代遅れですか?

ショートビデオ、セルフメディア、インフルエンサーのためのワンストップサービス業界ではよく知られている...

Phoenix.com を例にとると、情報サイトはどのようにしてユーザーをより効果的に維持できるでしょうか?

検索エンジンのアルゴリズムが継続的にアップグレードされるにつれて、アルゴリズムはオンサイト最適化にさ...

aliendata: 月額 2.5 ドル、KVM 仮想 VPS、1G メモリ/1 コア (ryzen 3900X)/20g SSD/1Gbps 無制限トラフィック

ヒューストンの北部で事業を展開している Data Ideas LLC は、正式に事業を開始して 1 ...

2020年、わが国のクラウドコンピューティング市場の規模は1,781億元に達し、成長率は33.6%でした。

中国インターネット協会主催の2021年(第20回)中国インターネット会議が北京国家会議センターで開幕...

強化された制御、統合、スケーラビリティ、コンテナサポートを備えた Red Hat Ansible Tower 3.3 がリリースされました

オープンソース ソリューションの大手グローバル プロバイダーである Red Hat, Inc. (N...

ジャック・マーが起業家精神について語る: 地に足のついたことをして、ベンチャーキャピタルにウェブサイトを見つけてもらう

アリババは、他のインターネット企業がとってきた、資金を調達し、人材を採用し、物事を実行するという古い...

Kuaiboの王欣とMomoが地図ソーシャルネットワーキング市場に参入、新たなトレンドか?

MaToilet MTの失敗後も、王欣はソーシャルネットワーキングの探求をやめなかった。 Tech ...

企業に必要なのはウェブサイトの最適化だけではありません

ウェブサイトの最適化が企業にもたらす利益がますます大きくなるにつれて、ウェブサイトの最適化の役割を認...

ユーザーの2大収益モデルとSEO最適化の徹底分析

優れたウェブサイト運営モデルは、ウェブサイトの発展方向と将来の収益空間を促進するため、ウェブサイト構...

電子商取引マーケティングは何に依存しているのでしょうか?

長い間売上に圧倒されてきた企業のマーケティング担当者は、次第に数字しか見なくなります。最大限のパフォ...

中国オープンソースクラウドコンピューティングユーザーカンファレンス:オープンソースクラウドアーキテクチャがトレンドに

[51CTO.com からのオリジナル記事] 長年の開発を経て、オープンソース ソフトウェアはソフト...

Kubernetes を採用しますか?避けるべき落とし穴をいくつか紹介します

ツールの使い方を知ることは、それをうまく使うための鍵であり、この概念は週末の趣味のプロジェクトだけに...

クラウドのために生まれた「クラウド ネイティブ」が、なぜますます重要になっているのでしょうか?

[[343634]] IT の歴史には古典的な物語があります。 1943 年、IBM の会長であるト...