Java Face Classic 03-仮想マシン-jvm メモリ構造とガベージコレクション、メモリオーバーフローとクラスロード、参照と悲観的ロックとハッシュテーブル、参照とファイナライズ

仮想マシン

1. JVMのメモリ構造

必要とする

  • マスターJVMメモリ構造部
  • 特に、メソッド領域、永続世代、メタスペースの関係を理解し​​ておく必要があります。

一部の Java コードの実行と組み合わせてメモリ分割を理解する

ここに画像の説明を挿入


ここに画像の説明を挿入

  • javac コマンドを実行してソースコードをバイトコードにコンパイルします。
  • Javaコマンドを実行する
    1. JVMを作成し、クラスロードサブシステムを呼び出してクラスをロードし、クラス情報をメソッド領域に保存します。
    2. メイン スレッドを作成し、使用されるメモリ領域はJVM 仮想マシン スタックであり、メイン メソッド コードの実行を開始します。
    3. 表示されていないクラスが見つかった場合は、引き続きクラスのロード プロセスがトリガーされ、メソッド領域にも格納されます。
    4. オブジェクトを作成する必要があり、オブジェクトの保存にはヒープメモリが使用されます。
    5. メモリが不足すると、使用されなくなったオブジェクトがガベージ コレクターによって再利用されます。
    6. メソッドを呼び出すとき、メソッド内のローカル変数とメソッド パラメータは、JVM 仮想マシン スタック内のスタック フレーム メモリを使用します。
    7. メソッドを呼び出すときは、まずメソッド領域に移動してメソッドのバイトコード命令を取得する必要があり、インタプリタはバイトコード命令を実行用のマシンコードに解釈します。
    8. メソッドが呼び出されると、実行される命令の行番号がプログラム カウンタに読み込まれます。これにより、スレッドの切り替えが発生した場合、再開時に中断された位置から続行できます。
    9. Java 以外で実装されたメソッド呼び出しの場合、使用されるメモリはローカル メソッド スタックと呼ばれます(説明を参照)
    10. ホット メソッド呼び出しまたは頻繁なループ コードの場合、JIT コンパイラーはこれらのコードをマシン コード キャッシュにコンパイルして実行パフォーマンスを向上させます(そうでないと、同じコードが実行されるたびに、インタープリターはマシン コードの実行がキャッシュと同等であるため、バイトコード命令を繰り返し解釈する必要があります)バイトコード命令)

メソッド領域: クラスの関連情報 (クラス名、継承関係、参照される他のクラスのシンボル、メンバー変数、メソッドのバイトコード、クラス、メソッド、メンバー変数に追加されたアノテーションなど) を格納します。 ヒープ: 新規出力を格納します。 オブジェクト
JVM
仮想マシンスタック: メソッド内のローカル変数と、 Java で実装された通常のメソッド変数のメソッドパラメータを格納します。以前は、OS と対話する必要がある特別なメソッドはローカル メソッド スタック上で実行する必要がありましたが、現在は Oracle のホットスポット仮想マシン実装ではローカル メソッド スタックが使用されなくなったか、2 つのスタックが 1 つに結合され、すべてのメソッドに必要な可変メモリが JVM 仮想マシン スタック内にあります。

説明する

  • 太字フォントは JVM 仮想マシン コンポーネントを表します
  • Oracle のホットスポット仮想マシンの実装では、仮想マシン スタックとネイティブ メソッド スタックは区別されません。

メモリリークが発生する可能性のある領域

メモリ不足: この領域のメモリが枯渇し、エラーが報告されます。

メモリ リーク: ガベージ コレクタはメモリの特定の部分を再利用できません。この現象はメモリ リークと呼ばれます。

上図のプログラム カウンタを除く 5 つのメモリ領域はメモリ オーバーフローを引き起こします。

  • メモリオーバーフローが発生しない領域 – プログラムカウンタ
  • OutOfMemoryErrorの場合
    • ヒープ メモリの枯渇 - 常に使用されているオブジェクトが増えており、ガベージ コレクションできない
    • メソッド領域のメモリが枯渇し、ロードされるクラスが増え、多くのフレームワークが実行時に新しいクラスを動的に生成します。
    • 仮想マシンのスタックの蓄積 – 各スレッドは最大 1 M のメモリを占有します。スレッドの数は増加しており、長時間実行しても破壊されません。
  • StackOverflowErrorが発生する箇所
    • JVM 仮想マシン スタックの場合、メソッドの再帰呼び出しが正しく終了しないことが原因で、json を逆シリアル化する際の循環参照 (スレッド内のメソッドが継続的に呼び出され、各スレッドの 1M メモリが消費され、StackOverflowError が発生します)通報される)

メソッド領域、永続生成、メタスペース

  • メソッド領域は、 JVM仕様で定義されているメモリ領域で、クラスのメタデータメソッドのバイトコードジャストインタイム コンパイラに必要な情報などを格納するために使用されます。
  • 永続的な世代は、ホットスポット仮想マシン (1.8 より前) による JVM 仕様の実装です。
  • メタスペースは、ホットスポット仮想マシン (1.8 以降) による JVM 仕様のもう 1 つの実装であり、これらの情報のストレージ スペースとしてローカル メモリを使用します。

メソッド領域は JVM 仕様の単なる定義です (必ず必要です。実装方法は気にしません)。永続世代
とメタスペースは仕様の物理的な実装です。

ここに画像の説明を挿入

この写真から 3 つのことを学びましょう

クラスメタデータ: クラスを記述するデータ (どのメンバー、どの型、どのくらいの長さ...) がメタスペース (メソッド領域の物理実装) クラス名.クラス バイトコード
オブジェクトに格納されます。これはオブジェクトであるため、自然にヒープに格納される
クラスの元の情報 (クラスのメタデータ) はメタスペースに格納されるため、直接アクセスすることはできません Java オブジェクトを通じてアクセスする必要があります このオブジェクトはバイトコード オブジェクトです

  • クラスが初めて使用されるとき、クラスローダーはクラスファイルのクラスメタ情報を読み取り、メタスペースに格納します。
  • XとYのクラスメタ情報はメタ空間に格納され、直接アクセスすることはできません
  • X.class と Y.class を使用すると、クラス メタ情報に間接的にアクセスできます。どちらも Java オブジェクト (バイトコード オブジェクト) に属し、コードで使用できます。

ここに画像の説明を挿入

この写真から私たちは学ぶことができます

  • ヒープ メモリ内:クラス ローダー オブジェクト、このクラス ローダー オブジェクトによってロードされたすべてのクラス オブジェクト、およびこれらのクラス オブジェクトに対応するすべてのインスタンス オブジェクトが参照されていない場合、それらによって占有されていたヒープ メモリは GC 中に解放されます。
  • メタスペース内:クラスローダ単位でメモリが解放される ヒープ内のクラスローダのメモリが解放されると、メタスペース内の対応するクラスメタ情報も解放される

通常、システム クラス ローダーはリリースされず、カスタム クラス ローダーは使用されなくなったときにリリースされます (何をリリースするか? メタスペース メモリ)

2. JVMメモリパラメータ

必要とする

  • 一般的な JVM パラメータ、特にサイズに関連するパラメータについての知識

質問:ここに画像の説明を挿入

ヒープ メモリ、サイズによって設定

ここに画像の説明を挿入

説明する:

  • -Xms JVM 最小メモリ (新世代と旧世代を含む)
  • -Xmx JVM 最大メモリ (新世代と旧世代を含む)
  • 一般に、-Xms と -Xmx を同じサイズに設定することをお勧めします。つまり、メモリを予約する必要がなく、小さいメモリから大きいメモリへ拡張する必要もないため、パフォーマンスが向上します。
  • -XX:NewSize と -XX:MaxNewSize は新しい世代の最小値と最大値を設定しますが、通常は設定することはお勧めできません。JVM 自体によって制御されます。
  • -Xmn は新しい世代のサイズを設定します。これは、-XX:NewSize と -XX:MaxNewSize を同時に設定するのと同等であり、値は等しいです。
  • 予約とは、最初はそれほど多くのメモリを使用しないことを意味し、メモリが使用されるにつれて、予約されたメモリのこの部分が徐々に使用されます。以下同様

年齢の観点から、JVM はメモリを新世代と旧世代に分割します
。Xmn の n は新しい新世代です。

ヒープ メモリ、比例的に設定

下図の New が新世代で、新世代の記憶はさらに eden と Survivor に分けられ、Survivor は from、to old に細分化でき
、当然旧世代の記憶になります。

ここに画像の説明を挿入

説明する:

  • -XX:NewRatio=2:1 は、古い世代が 2 を占め、新しい世代が 1 を占めることを意味します
  • -XX:SurvivorRatio=4:1 は、新しい世代が 6 つの部分に分割され、エデンが 4 つの部分を占め、from と to がそれぞれ 1 つの部分を占めることを意味します。
  • (注 1: デフォルトの 8:1 は 8:1:1 です) (注 2: from と to は常に等しいため、上記の 4:1 は eden:from=eden:to=4:1 を指します)

メタスペースメモリ設定

ここに画像の説明を挿入

説明する:

  • クラス空間にはクラスの基本情報が格納されます。最大値は -XX:CompressedClassSpaceSize によって制御されます
  • 非クラス空間には、クラスの基本情報以外の情報(メソッドのバイトコードやアノテーションなど)が格納されます。
  • クラス空間と非クラス空間の合計サイズは、-XX:MaxMetaspaceSize によって制御されます。

知らせ:

  • ここで、-XX:CompressedClassSpaceSize スペースのこのセクションは、ポインター圧縮が有効かどうかにも関係します。ここでは詳細には触れませんが、単にポインター圧縮がデフォルトで有効になっていると考えることができます。

コードキャッシュメモリの設定

JIT インスタント コンパイラ。ホット コードをマシン コードにコンパイルしてキャッシュし、CodeCache コード バッファ領域に保存します。

ここに画像の説明を挿入

説明する:

  • -XX:ReservedCodeCacheSize < 240m の場合、すべての最適化されたマシン コードが無差別に一緒に存在します。
  • それ以外の場合は、3 つの領域に分割されます (最適化されたコードは 3 つの部分に再分割されます) (図のタイプミスはスペルミスであり、e が 1 つ抜けています)
    • 非 nmethods - JVM 自体によって使用されるコード (JIT コンパイラー自身のコード)
    • プロファイルされた nmethods - 部分的に最適化されたマシンコード
    • 非プロファイルの nmethods - 完全に最適化されたマシンコード

スレッドメモリ設定

つまり、JVM 仮想マシン スタックのメモリ
- Xss は、各スレッドが占有するメモリを設定します。
設定されていない場合、Linux システムはデフォルトで 1MB に設定され、つまり、各スレッドはデフォルトで 1MB のメモリを占有します。

ここに画像の説明を挿入

公式参考ドキュメント

3. JVM ガベージ コレクション

必要とする

  • ガベージ コレクション アルゴリズムをマスターする
  • 世代別リサイクルの考え方をマスターする
  • 3色マーキングとラベル欠落処理を理解する
  • 一般的なガベージ コレクターについて理解する

例:ヒープメモリ内の一部のオブジェクトは、それらを指すスタックメモリ内の参照を持たなくなり、GC はそれらをリサイクルできます。

3 つのガベージ コレクション アルゴリズム

マークアンドスイープ

ここに画像の説明を挿入

説明する:

  1. GC ルート オブジェクト、つまり、実行メソッド内のローカル変数によって参照されるオブジェクト、静的変数によって参照されるオブジェクトなど、リサイクルしてはいけないオブジェクトを検索します。
  2. マーキング段階: GC ルート オブジェクトの参照チェーンをたどり、直接または間接的に参照されるオブジェクトをマークします。
  3. クリーンアップ フェーズ: マークされていないオブジェクトによって占有されていたメモリを解放します。

ローカル変数によって参照または使用されているオブジェクトはリサイクルできず、ルート オブジェクトとして使用できます。静的
変数は常に存在する必要があり、リサイクルできず、ルート オブジェクトとして使用できます。

要点:

  • マーキング速度と生存オブジェクト間の線形関係
  • クリア速度はメモリサイズに直線的に関係します
  • 欠点は、メモリの断片化が発生することです(マークのないメモリは不連続である可能性が高く、大量のメモリの断片化が発生するため、基本的に放棄されています)

マーキング方法

ここに画像の説明を挿入

説明する:

  1. 前のマーキング段階とクリーニング段階は、マーキングと除去方法に似ています。
  2. 終了アクションのもう 1 つのステップ、生き残ったオブジェクトを一方の端に移動すると、メモリの断片化を回避できます。

特徴:

  • マーキング速度と生存オブジェクト間の線形関係

  • クリアと整理の速度はメモリ サイズに比例します

  • デメリットはパフォーマンスが遅いこと

タグ付きレプリケーション

ここに画像の説明を挿入

説明する:

  1. メモリ全体を同じサイズの 2 つの領域 from と to に分割します。to は常に空き領域で、from には新しく作成されたオブジェクトが格納されます。
  2. マーキングフェーズは前のアルゴリズムと似ています
  3. 生き残ったオブジェクトが見つかった後、それらは from から to の領域にコピーされ、コピープロセス中に自然にデフラグが完了します (コピー後、from の領域はすべてクリアできます)。
  4. コピーが完了したら、from と to の位置を入れ替えるだけです (2 つの領域は交互に使用され、メモリの断片化の問題は発生しません。これは素晴らしいことです)。

特徴:

  • マーキングと複製の速度は、オブジェクトの存続に比例します。
  • デメリットは倍のスペースが必要になること

GC と世代別コレクションのアルゴリズム

GC の目的は、不要なオブジェクト メモリの自動解放を実現し、メモリの断片化を軽減し、割り当てを高速化することです。

GC ポイント:

  • リカバリ領域は、仮想マシン スタック (メソッド呼び出しの終了時にメソッドによって占有されていたメモリが自動的に解放されるメソッド スタック内のメモリ) を除くヒープ メモリです
  • 不要なオブジェクトを特定し、到達可能性分析アルゴリズムを使用し、3 色マーキング方法を使用して生き残ったオブジェクトをマークし、マークされていないオブジェクトをリサイクルします。
  • GC の特定の実装はガベージ コレクターと呼ばれます
  • ほとんどの GC は世代リサイクルの考えを採用しています
    • 理論的根拠は、ほとんどの物体は腐りやすく、使い果たされたらすぐにリサイクルできるが、少数の物体は長期間存続し、毎回リサイクルするのは難しいということです。
    • これら2種類のオブジェクトの特性に応じて、リサイクル領域は新世代旧世代に分けられ新世代はマークコピー方式を採用し旧世代は一般的にマーク仕上げ方式を採用します。
  • GCの規模に応じて、 Minor GCMixed GCFull GCに分けられます。

新世代:ゴミオブジェクト(手法的に新しいことが多い局所的なオブジェクト)が多い
旧世代:生き残っているオブジェクトが多く、リサイクルが難しい、または頻繁にリサイクルする必要がなく、特に分別は行われない時間のかかるもの(例:フレームワーク内の静的オブジェクト、長期間使用されるオブジェクト)(旧世代で生き残っているオブジェクトが多く、マークコピー方式でもメモリを極度に浪費する) 到達可能性解析アルゴリズム:GCルートを見つけ
、マークする (最初にリサイクルされないオブジェクトを見つけてから、その参照チェーンをたどる 検索して、再度マークする)
3 色のマーキング方法: 以下を参照
ガベージ コレクターには多くの種類があります (以下を参照)

マイナー GC : オブジェクトのガベージ コレクション新しい世代、小規模なガベージ コレクション、一時停止時間が短く、システムへの影響が少ない フル
GC : 新世代と古い世代がメモリ不足になり、包括的なガベージ コレクションが来た、一時停止時間が長く、システムへの影響が少ない一般に、彼らはフル GC の混合 GC を見たくありませんでした: これは上記 2 つの間に位置し、次のことを指します:
新しい世代でガベージ コレクションが発生し、一部の古い世代でもガベージ コレクションが発生しました。 G1ガベージコレクター独自のリサイクル手法「混合ガベージコレクション」

GCとヒープメモリに関する概念を個人的に整理:
GCは
ヒープメモリ内の新しいオブジェクトを回収してヒープメモリに置くだけ ヒープ
メモリの分割:
年齢の観点から、JVMはヒープメモリを新世代と旧世代に分割する世代
。エデンとサバイバーに分けられます。サバイバーは次のように細分化できます。
最初の概要には、一般的なフレームワークがあります。次に、以下の詳細なプロセス
ここに画像の説明を挿入
図をゆっくりと見てください。黄色はいアイドル状態マーキングは
1 つの文で要約できます: ルート オブジェクトによって直接参照されているか間接的に参照されているかを確認します。

世代別リサイクル

  1. 最初にここにオブジェクトが配置されているエデンの園と生存エリア(fromとtoに分かれている)を合わせて新世代と呼びます。

ここに画像の説明を挿入

  1. エデンの記憶が不十分な場合は、エデンとエデンからの生き残ったオブジェクトにマークを付けます(この段階ではありません)

ここに画像の説明を挿入

  1. コピー アルゴリズムを使用して、生き残ったオブジェクトを にコピーします。コピーが完了すると、Eden と from の両方のメモリが解放されます。

ここに画像の説明を挿入

  1. からとからの交換

ここに画像の説明を挿入

  1. 時間が経つとまたエデンの記憶が足りなくなる

ここに画像の説明を挿入

  1. エデンとエデンからの生き残ったオブジェクトにマークを付けます (この段階ではありません)

ここに画像の説明を挿入

  1. コピー アルゴリズムを使用して、生き残ったオブジェクトを にコピーします。

ここに画像の説明を挿入

  1. コピー後、eden とメモリから両方が解放されます

ここに画像の説明を挿入

  1. からとからの交換

ここに画像の説明を挿入

  1. 古い世代、Survivor 領域内のオブジェクトが数回のコレクション (最大 15 回) を生き延びた場合、それらは古い世代に昇格されます(Survivor 領域のメモリ不足またはオブジェクトが大きい場合、早期に昇格されます)。

残存領域が不十分: to がコピーするには十分でない場合は、古い時代に移動する必要があります (to が非常に大きいため、不足しているのは以前にリサイクルされたオブジェクトが存在するためです)。また、コピーを促進する方法はありません。大きなオブジェクトを
事前に作成する : 毎回 GC をコピーしてコピーする必要があるため、消費が多すぎるため、事前に古い世代に移行することをお勧めします。

GCスケール

  • 新世代のガベージ コレクションでは、短い一時停止時間を伴うマイナー GC が発生します。

  • G1 コレクター特有の、新世代の混合 GC + 旧世代の一部の領域でのガベージ コレクション

  • フル GC 新しい世代 + 古い世代の完全な (包括的な)ガベージ コレクション、長い一時停止時間は回避する必要があります。

三色のマーク

つまり、対象物のマーキング状態を3色で記録します

  • 黒 - マークあり
  • 灰色 - マーク付き
  • 白 - まだマークされていません

黒 – マーク: ルート オブジェクトの参照チェーンに沿って、このオブジェクトが見つかり、このオブジェクト内の他の参照も処理されました。 灰色 – マーク: ルート オブジェクトの
参照チェーンに沿って、このオブジェクトが見つかりましたが、その他このオブジェクト内の参照はまだ処理されていません
白 - まだマークされていません: これは、マークされている最後に残っているオブジェクトです

  1. 最初の 3 つのオブジェクトはまだ処理されておらず、灰色で示されています

ここに画像の説明を挿入

  1. オブジェクトの参照が処理され、黒で示され、黒で参照されたオブジェクトが灰色に変わります。

直接参照を灰色としてマークすると、その参照処理が完了したとみなされ、直接黒としてマークできます。

ここに画像の説明を挿入

  1. 等々

ここに画像の説明を挿入

  1. 参照チェーンに沿ってマーク

ここに画像の説明を挿入

  1. 最後のマークのない白い物体はゴミです

ここに画像の説明を挿入

ラベル欠落の問題が同時に発生

前の GC は非同時実行です。GC が動作しているとき、ユーザー スレッドは一時停止されているため、ユーザー スレッドは GC スレッドに影響しません。つまり、GC がマークしているとき、ユーザー スレッドは一時停止されており、何も影響しません
。マーキングへの影響 (参照チェーンは変更されません)
非同時 GC は非効率的であり、同時 GC、つまり同時マーキングが必ず必要です。
その場合、GC がマーキングしている間、ユーザー スレッドはまだ動作しています。ユーザースレッドはマーキング処理中に参照関係を変更するため、マークの欠落が発生しやすくなります。

より高度なガベージ コレクターは、同時マーキング をサポートします。つまり、ユーザー スレッドはマーキング プロセス中にも動作できます。しかし、これにより新たな問題が発生し、ユーザー スレッドがオブジェクト参照を変更すると、ラベルが失われるという問題が発生します。例えば:

  1. 図に示すようにマーキング作業はまだ完了していません

ここに画像の説明を挿入

  1. 同時にユーザースレッドが動作し、第 1 層のオブジェクト 3 と 4 の間の参照が切断されます。このとき、オブジェクト 3 を処理しているガベージ コレクション スレッドは、オブジェクト 4 をホワイト ガベージとして扱います。

現時点では 3 をリサイクルするのが実際には合理的です
が、切断された後に他のオブジェクトによって参照される場合 (使用しないわけではありませんが、他のオブジェクトに使用します)、リサイクルすることはできません (下記を参照)。

ここに画像の説明を挿入

  1. ただし、他のユーザー スレッドがオブジェクト 2 と 4 への参照を作成した場合、オブジェクト 2 は黒の処理済みオブジェクトであるため、ガベージ コレクション スレッドは参照関係の変更を認識せずマークが失われます。

ここに画像の説明を挿入

  1. ユーザースレッドが黒いオブジェクトに新しく追加されたオブジェクトを参照させる場合、マーク欠落の問題も発生します。

黒いオブジェクトは処理されており (黒としてマークされ、処理済みとみなされます)、処理されたオブジェクトは再度処理されません (直接の参照を繰り返し見つけて灰色としてマークすることはありません)。

ここに画像の説明を挿入

したがって、同時マーキングの場合は、マーキングの欠落の問題を解決する必要があります。つまり、マーキング プロセスの変更を記録する必要があります。回避策は 2 つあります。

マーク欠落の解決の核心は、マーキングプロセスの変更の記録 + 二次処理です。

  1. Incremental Update増分更新方式。CMS ガベージ コレクターが使用します。
    • アイデアは、各割り当てアクションを傍受することです。割り当てが発生している限り、割り当てられたオブジェクトは記録され、リマーク段階で再度確認されます。
  2. Snapshot At The Beginning、SATB独自のスナップショット方式、G1 ガベージ コレクターが使用
    • アイデアは各割り当てアクションをインターセプトすることですが、記録されたオブジェクトは異なり、これらのオブジェクトはリマーキング段階で2 回処理する必要があります。
    • 新しく追加されたオブジェクトはログに記録されます
    • 参照関係が削除されたオブジェクトもログに記録されます

上図の赤矢印黒→白と黒のオブジェクトが割り当てられたオブジェクトです(白のオブジェクトを黒のオブジェクトに割り当てます)

ここに画像の説明を挿入

ガベージ コレクター - 並列 GC

  • eden のメモリ不足によりマイナー GC が発生し、マーク コピー アルゴリズムが使用されるため、ユーザー スレッドを一時停止する必要があります。

  • メモリ不足により古いフル GC が発生し、マーク ソート アルゴリズムが使用されるため、ユーザー スレッドを一時停止する必要があります。

  • スループットを重視します(応答時間と一時停止時間が遅くても問題ありませんが、全体的に短い一時停止時間は問題ありません)。

パラレル GC: 実際には 2 つのガベージ コレクターで構成され、1 つは若い世代で動作し、もう 1 つは古い世代の
マイナー GC で動作します。
フル GC で新世代のガベージ コレクターのみが動作する場合、新世代と旧世代のガベージの両方が動作します。
マーク コピーとマーク フィニッシング (低速) ではメモリの断片化が発生しません

ガベージ コレクター - ConcurrentMarkSoup GC

  • 古い時代に動作し、同時マーキングをサポートし、同時クリアアルゴリズムを使用するリサイクラーです。

    • 同時マーキング中にユーザー スレッドを一時停止する必要はありません (マーキングが失われる可能性があります)
    • 再マークするときにユーザー スレッドを一時停止する必要があります (ユーザー スレッドはマークの欠落を処理するときに同時実行できなくなるため、一時停止する必要があります。そうしないと無限に実行されます)。
  • 同時実行が失敗した場合 (つまり、リサイクル速度が新しいオブジェクトの作成速度に追いつかない場合)、フル GC がトリガーされます。

  • 応答時間に注意してください(つまり、応答時間が速く、長時間待つ必要がないという利点があります)

ConcurrentMarkSoup GC これは古い時代のガベージ コレクターです。ConcurrentMarkSoup
GC は、CMS ガベージ コレクターと呼ばれます。コンカレントと
Concurrent:并发 Mark:标记 Sweep: 扫描,打扫
は、ユーザー スレッドが GC 中に短時間一時停止し、同時に実行できることを意味しますホワイト ガベージ オブジェクトのクリアとリサイクルを指します。ただし、人々はマークアンドクリア方式を使用しており、メモリの断片化の問題が発生しているため、最新の JDK ではこの方式が廃止されているとマークされています。

STW(ストップ・ザ・ワールド)
ここに画像の説明を挿入

ガベージ コレクター - G1 GC

  • 応答時間とスループットのバランス
  • 複数のエリアに分かれており、各エリアはエデン、サバイバー、オールド、ヒューモンガスとして機能でき、そのうちヒューモンガスは大きなオブジェクト用に特別に用意されています
  • 3 つのフェーズに分かれています: 新世代収集、同時マーキング、混合収集
  • 同時実行が失敗した場合 (つまり、リサイクル速度が新しいオブジェクトの作成速度に追いつかない場合)、フル GC がトリガーされます。

G1 GC 読み取り: G 1 ガベージ コレクター
巨大: 巨大

概要:
ここに画像の説明を挿入

G1 には、保証されたボトム ポリシーもあります: リサイクル速度 < 新しいオブジェクトの作成速度、つまり同時障害: FailBack Full GC は全体としてリサイクルを実行し、一時停止時間が長くなります。

G1 コレクション フェーズ - 新世代コレクション

  1. 最初はすべてのリージョンがアイドル状態です

ここに画像の説明を挿入

  1. いくつかのオブジェクトが作成され、いくつかの空き領域がこれらのオブジェクトを保存するための Eden 領域として選択されます。

ここに画像の説明を挿入

  1. Eden でガベージ コレクションが必要な場合は、空き領域を生存領域として選択し、コピー アルゴリズムを使用して生き残ったオブジェクトをコピーし、ユーザー スレッドを一時停止する必要があります
    (新しい世代ではマーク コピー メソッドが採用されており、コピーは STW である必要があり、非同時)
    (eden エリア内のすべての生存オブジェクトを生存エリアにコピーします (to エリアと to エリアと from エリアのステータスが入れ替わります))

ここに画像の説明を挿入

  1. コピーが完了すると、前のエデンのメモリが解放されます

ここに画像の説明を挿入

  1. 時間が経つと、エデンは再びメモリ不足になります

ここに画像の説明を挿入

  1. コピー アルゴリズムを使用して、エデンの園内の生き残ったオブジェクトと以前の生き残ったエリアを新しい生き残ったエリアにコピーし、古いオブジェクトは古い世代に昇格します。

(eden エリアと surviving from エリア内のすべてのオブジェクトが、新しい surviving エリア (と同様) にコピーされます)

ここに画像の説明を挿入

  1. エデンと以前の生存エリアの記憶を解放する

ここに画像の説明を挿入

G1 収集フェーズ - 同時マークと混合収集

前提として、旧世代のメモリがほとんど不足しているため、旧世代のリサイクルを開始する必要があります。旧世代のマーキング戦略はのとおりです。

  1. 古い世代が占有しているメモリがしきい値を超えると、同時マーキングがトリガーされ、現時点ではユーザー スレッドを一時停止する必要はありません。

ここに画像の説明を挿入

旧世代領域をすべて直接回収するのではなく、回収価値の高い(残存オブジェクトが少ない)いくつかの旧世代領域を選択して最初に再利用します。

  1. 同時マーキングの後、マークの欠落の問題を解決するために再マーキング フェーズが行われ、この時点でユーザー スレッドを一時停止する必要があります。これらがすべて完了すると、残っているオブジェクトが古い世代のものであることがわかり、混合収集段階に入ります。このとき、古い世代の領域がすべてリサイクルされるわけではありませんが、価値の高い(生き残っているオブジェクトが少ない)領域が一時停止時間の目標に従って最初に回復されます(これがガベージ ファーストの名前の由来でもあります)。

ここに画像の説明を挿入

リサイクル価値の高い厳選された旧世代(上図赤)だけでなく、新世代(eden+survivor)も回収する混合回収

  1. 混合コレクション フェーズでは、eden、survivor、および old がレプリケーションに関与します。次の図は、Eden および Survivor 領域の存続オブジェクトのレプリケーションを示しています。

ここに画像の説明を挿入

  1. 以下の図は、旧世代領域と存続領域でプロモートされた存続オブジェクトのレプリケーションを示しています。

ここに画像の説明を挿入

  1. コピーが完了し、メモリが解放されます。新世代コレクション、同時マーク、混合コレクションの次のラウンドに入る

ここに画像の説明を挿入

4. メモリ不足

メモリ不足: この領域のメモリが枯渇し、エラーが報告されます。

必要とする

  • メモリオーバーフローを引き起こす典型的な状況をいくつか挙げることができる

典型的な状況

  • 1) スレッドプールの誤用によるメモリオーバーフロー
    • 参照 day03.TestOomThreadPool
      ここに画像の説明を挿入
      LinkedBlockingQueue は無制限のキューです (整数型はオーバーフローしません、オーバーフローしません)

ここに画像の説明を挿入
上記のコードは継続的に新しいサイトを作成して送信しますが、各スレッドは 30 ミリ秒間ブロックする必要があるため、ブロック キューはますます大きくなり、際限なく増大し、メモリの爆発につながります。

ここに画像の説明を挿入
ここに画像の説明を挿入

  • 2) クエリデータが多すぎることによるメモリオーバーフロー
    • 参考 day03.TestOomTooManyObject

データベース エントリが多すぎます。findAll を実行すると、1 回の検索で 100 万件のアイテムが見つかる可能性があります。これは 100 万件の非常に一般的な Product POJO コレクションであり、363MB のメモリも消費します。サーバー メモリがどれほど大きくても、このよう
作成には耐えられません。
クエリは制限を追加する必要があります (条件がある場合は機能しません。条件は失敗する可能性があります)

これらのエラーはテスト環境では検出できず、数百万のデータが存在する運用環境でのみ問題が明らかになります。

そのため、プロジェクト完了後にはストレステストも行う必要があります。

  • 3) 動的に生成されたクラスによって引き起こされるメモリ オーバーフロー
    • 参考 day03.TestOomTooManyClass
      ここに画像の説明を挿入
      ここに画像の説明を挿入

5. クラスローディング

必要とする

  • クラスローディングフェーズをマスターする
  • クラスローダーをマスターする
  • 親の委任メカニズムを理解する

クラスロードプロセスの 3 つのフェーズ

  1. 負荷

    1. クラスのバイトコードをメソッド領域にロードし、class.class オブジェクトを作成します。
    2. このクラスの親クラスがロードされていない場合は、最初に親クラスをロードします
    3. ロードは遅延して実行されます (このクラスが実際に使用される場合にのみロードされます)

类.class对象その中には一連のリフレクション メソッドがあり、クラスのすべての情報 (どのメンバーが存在するか、どのメソッドが
类.class对象ヒープに保存されているか)を知ることができます。

  1. リンク

    1. 検証 - クラスがクラス仕様、合法性、セキュリティチェックに準拠しているかどうかを検証します。
    2. 準備 – 静的変数にスペースを割り当て、デフォルト値を設定します(ただし、手動で作成した代入ステートメントはこの時点では実行されません。初期化フェーズで実行されます。ここでは実際には静的変数にスペースを割り当てるだけです(最終的な変数は例外、この時点で実行されます) 割り当て))
    3. solve – 定数プールへのシンボリック参照を直接参照に解決します。
  2. 初期化

    1. 静的コード ブロック、静的変更変数割り当て、および静的最終変更参照型変数割り当ては 1 つのメソッドに結合され<cinit>、初期化中に呼び出されます。
    2. static Final によって変更された基本型変数の代入は、リンク段階で完了します。
    3. 初期化は遅延実行です (遅延実行はクラスが実際に使用されるときに初期化され、0 に分割されます、いいですね)

検証手段

  • jps を使用してプロセス番号を表示します
  • jhsdb を使用してデバッグし、コマンドを実行してjhsdb.exe hsdbグラフィカル インターフェイスを開きます。
    • クラス ブラウザでは、現在の JVM にロードされているクラスを表示できます
    • ヒープメモリ範囲を表示するためのコンソールユニバースコマンド
    • コンソールの g1regiondetails コマンドを使用してリージョンの詳細を表示します
    • scanoops 起始地址 结束地址 对象类型タイプに応じた範囲でオブジェクトのアドレスを見つけることができます
    • コンソールのコマンドでinspect 地址、このアドレスに対応するオブジェクトの詳細を表示できます。
  • javap コマンドを使用してクラスのバイトコードを表示します

コードの説明

  • day03.loader.TestLazy - クラスの読み込みが遅延していること、およびクラスの読み込みが使用時にトリガーされることを確認します。
  • day03.loader.TestFinal - 最終変数がクラスの読み込みをトリガーしないことを確認する

バイトコード オブジェクトは実際にはメソッド領域ではなくヒープ領域(eden 領域) にあります。

ここに画像の説明を挿入
ここに画像の説明を挿入

ここに画像の説明を挿入
ここに画像の説明を挿入

  • クラス初期化メソッド (静的メンバー (非最終共通型) および静的コード ブロック)

静的コード ブロック内の静的メンバーとステートメントは、クラスの初期化時に呼び出されるメソッド (cinit メソッド) に統合されます。 注: 最終的な静的非参照型の変数は、クラスのロード時にロードされます (
create bytecode オブジェクト) が初期化されるため、ここで統合する必要はありません

ここに画像の説明を挿入

  • Final で修飾された非参照型変数はクラスのロードをトリガーしません

最初の 2 つの print ステートメントはクラスを使用しているようです。Student.c と Student.m は実際にはクラスを使用していないため、この時点ではクラスはロードされず、メモリ内にもクラスはありません。これは、クラスのロードが遅延ロードであることを完全に証明しています
。この時点でクラスのロードは完了し、クラスのバイトコード情報 (クラス構造: どのメンバー、どのメソッドか) が表示されます。

ここに画像の説明を挿入
クラス A が別のクラス B の最後の静的通常型変数 (実際には定数)を使用する場合、この時点では、クラス A はクラス B の定数を自分のクラスに直接コピーし、実際には別のクラス B をまったく使用しません。 。

定数の値が比較的小さい場合は直接メソッドに記述しますが、値が比較的大きい場合で short の最大範囲 (>32767) を超える場合は定数プールに入れられるので問題ありません。
必要なときに定数プールから取得します。
つまり、値が大きいほど、クラス A の独自の定数プールにコピーされ、各クラスには独自の定数プール (定数リスト、1、2、3、 ... 各定数に番号を付け、その番号を指定し、定数プールに直接アクセスしてその定数の値を取得します)

ここに画像の説明を挿入

分析: シンボル参照 - 「直接参照はコードの実行を継続するプロセスであり、一度に完了することはできません。
クラスの静的メンバー変数への参照はすべて
定数プールに配置されます。直接参照、シンボリック参照のみ (null ポインタは指す型のみを認識しますが、実際のメモリはありません)

jdk 8のクラスローダー

名前 どのクラスをロードするか 説明する
ブートストラップ クラスローダー JAVA_HOME/jre/lib 直接アクセスできない
拡張クラスローダー JAVA_HOME/jre/lib/ext 親はブートストラップであり、null が表示されます
アプリケーションクラスローダー クラスパス 上位はエクステンションです
カスタムクラスローダー カスタマイズ 優れているのはアプリケーションです

String.class、Application、Extension などのクラス ローダーはクラス ローダーに含まれていないためロードできません。このとき、Bootstrap にクラス ローダーを起動してロードするように依頼する必要があります。そうすれば、下位レベルでそれを見ることができます (String 型)は jdk であり、上位レベルはそれを使用する必要があります。すべてが可視で合理的です)
私が書いたクラス Student.class と同様に、これもルールに従い、段階的に上向きに要求します。上位層のローダーにはこのクラスはありません、アプリケーション クラス ローダーは Student.class をロードしてそれをロードする資格があります (上位クラス ローダーは表示されず、表示する必要もありません。この種のシールドは非常に合理的です)。

保護者の委任メカニズム

いわゆる親の委任は、上位レベルのクラスローダーがロードする場合、上位レベルのクラスローダーの優先順位の委任を指します。

  • このクラスは上位レベルで見つけてロードできます。ロード後、このクラスは下位レベルのローダーにも表示されます
  • このクラスが見つからない場合、下位レベルのクラスローダーがロードを実行できます。

保護者代表の目的は 2 つあります

  1. 上位レベルのクラス ローダーのクラスを下位レベルのクラスと共有します (その逆も同様)。つまり、クラスは jdk が提供するコア クラスに依存できます(逆も同様です。jdk は絶対に依存する必要がありません)。自分で作成したクラス)

  2. クラスのロードに優先順位を付けて、コア クラスが最初にロードされるようにします。

上位クラス ローダーのクラスは下位クラスから見えます
が、下位クラス ローダーのクラスは上位クラスからは見えません。

保護者の代表団に関する誤解

次のインタビューの質問に対する答えは間違っています

ここに画像の説明を挿入

どうしたの?

  • 自分でクラスローダーを作成して偽の java.lang.System をロードできますか? 答えはノーです。

  • 独自のクラスローダーが、実際の java.lang.System は最初に起動クラスローダーによってロードされ、偽のクラスローダーは自然にはロードされません。

  • 独自のクラスローダーが親の委任を使用しないと仮定すると、クラスローダーが偽の java.lang.System をロードするときに、最初に親クラス java.lang.Object をロードする必要がありますが、委任を使用していないため、java が見つかりません.lang.Object なのでロードは失敗します

  • 上記は単なる仮定です実際、カスタム クラス ローダーが java. で始まるクラスをロードすると、セキュリティ例外がスローされることがわかります。jdk9 より上のバージョンでは、これらの特殊なパッケージ名はモジュールにバインドされており、コンパイルでさえパスできません (実際の操作では、セキュリティ例外が直接スローされるか、コンパイルが失敗して仮説のステップに到達しない場合、jdk はこれを防ぐためのセキュリティ対策をすでに講じており、java.lang パッケージ名を書き換えることはできません)

コードの説明

  • day03.loader.TestJdk9ClassLoader - クラスローダーとモジュール間のバインディング関係のデモンストレーション = "結論: jdk が既に持っているパッケージ名とクラス名を書き換えないでください

6. 4種類のリファレンス

必要とする

  • 4つの名言をマスターしよう

強力な参照

  1. 通常の変数の代入は、A a = new A(); などの強参照です。

  2. GC ルートの参照チェーンを通じて、オブジェクトを強参照できない場合、オブジェクトを再利用できます。

ここに画像の説明を挿入

ソフトリファレンス (SoftReference)

  1. 例: SoftReference a = new SoftReference(new A()); (途中に SoftReference オブジェクトがあり、a は間接的にオブジェクト new A() に関連付けられます)

  2. オブジェクトへのソフト参照のみがある場合、最初のガベージ コレクションではオブジェクトは再利用されません。それでもメモリが不足している場合は、再度リサイクルされるときにオブジェクトが解放されます (メモリが不足すると GC がトリガーされます)。 1 回目は惜しみますが、2 回目はメモリが不足しています。GC が再度トリガーされ、ソフト参照オブジェクトがリサイクルされます (強参照が指すオブジェクトは GC ではリサイクルできません)。

  3. ソフト参照自体は、参照キューと連動して解放する必要があります (次の図に示すように、オブジェクト a はソフト参照ですが、SoftReference 自体は依然として強参照であり、GC はソフト参照自体をリサイクルできません)。

  4. 典型的な例はリフレクション データです (リフレクションによって取得されたデータはすべてソフト参照データです。たとえば、クラス名.class= "取得されたメンバ変数、メソッド、その他のデータ情報はすべてソフト参照です)

ここに画像の説明を挿入

弱参照 (WeakReference)

  1. 例:WeakReference a = new WeakReference(new A());

  2. 弱参照のみがオブジェクトを参照している場合、ガベージ コレクションが発生するたびにオブジェクトは解放されます。

  3. 弱参照自体は参照キューで解放する必要がある(同上)

  4. 典型的な例は、ThreadLocalMap の Entry オブジェクトです。

ここに画像の説明を挿入

ファントムリファレンス (PhantomReference)

  1. 例: PhantomReference a = new PhantomReference(new A(),referenceQueue);

  2. 参照キューと一緒に使用する必要があります。仮想参照によって参照されるオブジェクトがリサイクルされると、参照ハンドラー スレッドは仮想参照オブジェクトをキューに入れるため、どのオブジェクトがリサイクルされるかを把握し、それらに関連するリソースをさらに処理できます。

  3. 典型的な例は、Cleaner が DirectByteBuffer に関連付けられたダイレクト メモリを解放することです。

ここに画像の説明を挿入

詳細な参照キュー: 図に示すように、仮想参照に関連付けられたオブジェクト a および b が解放された後、仮想参照自体は参照キューに配置され、参照ハンドラー スレッドがそれらを再利用する責任を負います。他のリソースにも関連付けられます (オブジェクトとオブジェクトだけではありません)。

コードの説明

  • day03.reference.TestPhantomReference - ファントム参照の基本的な使用法を示します。
  • day03.reference.TestWeakReference - ThreadLocalMap をシミュレートし、参照キューを使用してエントリ メモリを解放します
String str = new String("hello"); // "hello"在堆内存中 (new出来的都在堆中)
String str = "hello"; // "hello" 在常量池中 

ThreadLocalMap の Entry オブジェクト、キーは弱参照、値は強参照です。上の
ここに画像の説明を挿入
図は典型的なメモリ リークの
解決策です。参照キューを使用してエントリを参照キューに関連付けます。エントリのキーがリサイクルされると、エントリ オブジェクト全体が参照キューに入れられ、その後、すでに参照キューにあるエントリ オブジェクトのマップ参照を直接削除します (または、現在のエントリがマップ内にあるかどうかを確認して、対応する参照を記録します) Map 内の Entry 配列が null に設定されている場合)、それを指す参照はありません。次回リサイクルされるときにリサイクルされます。JDK は
この方法では実装されていないため、コストが比較的高くなります。

★★★キーは ThreadLocal オブジェクトそのものです。スレッドの実行中は他のオブジェクトから強参照される必要があるため、弱参照として設定される心配はありません。スレッドが終了する前に、キー (with他の強力な参照) はリリースされません。ただし、値が弱い参照として設定されると、実際にはこの弱い参照だけが存在するため、スレッドは終了する前に GC によってリサイクルされる可能性が非常に高くなります。★★★

7.ファイナライズ

必要とする

  • ファイナライズの動作原理とデメリットをマスターする

完成させる

  • 一般的な答え: これは Object のメソッドであり、サブクラスがこれをオーバーライドすると、ガベージ コレクション中にこのメソッドが呼び出され、その中でリソースが解放され、クリーンアップされます。
  • 優れた回答: Finalize メソッドにリソースの解放とクリーンアップを入れるのは非常に悪い方法であり、パフォーマンスに大きな影響を与え、深刻な場合には OOM (メモリ不足) を引き起こす可能性があります。Java9 以降 @Deprecated としてマークされており、推奨されません。使用されること。

フォローアップ: 非常に悪く、パフォーマンスに影響を与えるのはなぜですか?
以下の原則を参照してください。

補足:デーモンスレッドは、メインスレッドが終了すると(未実行のコードがあっても)実行されなくなります。

原則を確定する

  1. Finalize メソッドを処理するためのコア ロジックは java.lang.ref.Finalizer クラスにあり、このクラスには unfinalized (二重リンク リスト構造) という名前の静的変数が含まれており、Finalizer は別の参照オブジェクト (ステータスおよびソフト、弱くて想像上のものですが、外部のものではないため、直接使用することはできません)
  2. Finalize メソッドのオブジェクトが書き換えられるとき、構築メソッドが呼び出されるとき、JVM はそれを Finalizer オブジェクトにラップし、未完了リストに追加します (つまり、これらのオブジェクトの Finalize メソッドはまだ呼び出されていません。簡単に解放しないでください (この参照チェーンの機能も))

ここに画像の説明を挿入

  1. Finalizer クラスには、もう 1 つの重要な静的変数、つまり ReferenceQueue参照キューが あります (前の 4 つの参照の参照キューに似ており、参照オブジェクト自体の解放を支援します (他の関連リソースの解放に役立ちます)。関連付けられたオブジェクトは一時的に (最初に Finalize メソッドを呼び出す必要があるため、リサイクルできません)、最初は空です。犬オブジェクトをガベージ コレクションできる場合、これらの犬オブジェクトに対応するファイナライザー オブジェクトがこの参照キューに追加されます。
  2. ただし、現時点では、参照チェーンがファイナライズされていない -> ファイナライザーがまだ参照しているため、Dog オブジェクトをすぐにリサイクルすることはできません。これを行うには、[急いでリサイクルせず、ファイナライズ メソッドの調整が完了するまで待ってからリサイクルする] 必要があります。
  3. FinalizerThread スレッドは、ReferenceQueue から各 Finalizer オブジェクトを 1 つずつ取得し、リンクされたリストから切断し、実際に Finalize メソッドを呼び出します。

ここに画像の説明を挿入

  1. Finalizer オブジェクト全体が未完成のリンク リストから切り離されているため、誰もそれと Dog オブジェクトを参照できず、次の gc でリサイクルされます。

デメリットを確定させる

  • リソースの解放を保証できません: FinalizerThread はデーモン スレッドであるため、コードが時間内に実行されない可能性があり、スレッドは終了します
  • エラーが発生したかどうかを判断できません: Finalize メソッドが実行されると、例外が飲み込まれます (Throwable try-catch は飲み込まれます)。
  • メモリが時間内に解放されない: Finalize メソッドを書き換えるオブジェクトが初めて gc である場合、FinalizerThread が Finalize を呼び出して未ファイナライズから削除するまで待機する必要があるため、オブジェクトが占有しているメモリを時間内に解放できません。キュー、2 回目は gc が実際にメモリを解放できる場合のみ
  • いくつかの記事で言及されていました [ファイナライザー スレッドはメイン スレッドと競合しますが、優先順位が低く、CPU 時間が少ないため、メイン スレッドのペースに追いつくことはありません] これは明らかに間違っており、FinalizerThread の優先順位が高くなります。通常のスレッドよりも (max-2=8、通常のスレッドは 5 つだけ)、その理由は、ファイナライズのシリアル実行の遅さとその他の理由 (1 つをキューに入れ、1 つのファイナライズを呼び出す) の組み合わせであるはずです。

コードの説明

  • day03.reference.TestFinalize - ファイナライズ用のテストコード

ここに画像の説明を挿入

おすすめ

転載: blog.csdn.net/hza419763578/article/details/130630961