ステージ 10: トピックの概要 (第 3 章: 仮想マシン)
第 3 章: 仮想マシン
1.JVMのメモリ構造
必要とする
- マスターJVMメモリ構造部
- 特に、メソッド領域、永続世代、メタスペースの関係を知る必要があります。
Java コードの実行に基づいたメモリの分割を理解する
- javacコマンドを実行してコンパイルするソースコードのためにバイトコード
- Javaコマンドを実行する
- JVM の作成 (Java仮想マシン)、移行クラスローディングサブシステムロードクラス(バイトコードファイル)、クラスの元の情報(クラス名、継承関係、メンバ変数、参照している他のクラス名、クラスのメソッドコード)を格納メソッド領域(バイトコード ファイルをディスクからメモリに読み取ります)
- 作成するメインスレッド、使用されるメモリ領域はJVM仮想マシンスタック、 main メソッドの実行を開始します
- 表示されていないクラスが見つかった場合、クラスのロード プロセスが引き続きトリガーされ、メソッド領域にも格納されます。
- 新しいアクションで作成する必要があるオブジェクトの場合は、次を使用します。ヒープ保存するメモリ
- 使用されなくなったオブジェクトは次のものに置き換えられます。ガベージコレクター(GC) メモリが不足している場合にメモリを再利用します
- メソッドを呼び出すとき、ローカル変数、メソッドパラメータ使用されるのはJVM仮想マシンスタックですスタックメモリ
- メソッドを呼び出すときは、まず次の場所に移動します。メソッド領域次の方法でメソッドのバイトコード命令を取得します。通訳者バイトコード命令をマシンコードに解釈して実行します。
- メソッドを呼び出すときに、実行された命令の行番号を読み取ります。プログラムカウンター, スレッドの切り替えが発生した場合、再開時に中断した位置から続行できます。
- Java 以外で実装されたメソッド呼び出し (ネイティブ メソッド) の場合、メモリを使用するメソッドが呼び出されます。ネイティブメソッドスタック(説明を参照); 通常の Java メソッドは使用します。仮想マシンスタック;
- ホットスポット メソッド呼び出し、または頻繁にループするコードの場合、JITジャストインタイムコンパイラこれらのコードをマシン コード キャッシュにコンパイルして、実行パフォーマンスを向上させます。
説明する
- 太字フォントは JVM 仮想マシン コンポーネントを表します
- Oracle のホットスポット仮想マシンの実装では、仮想マシン スタックとローカル メソッド スタックの間に区別はありません。
メモリオーバーフロー(メモリ枯渇)
- メモリオーバーフローが発生しない領域–プログラムカウンター
- OutOfMemoryError(メモリ不足エラー)が発生する
- ヒープメモリが枯渇した– オブジェクトはますます増えており、常に使用されているため、ガベージ コレクションできません。
- メソッド領域のメモリが枯渇した– ロードされるクラスはますます増え、多くのフレームワークは実行時に新しいクラスを動的に生成します。
- 仮想マシンスタックの蓄積– 各スレッドは最大 1 M のメモリを占有し、スレッドの数は増加し、破壊されることなく長時間実行されます。
- StackOverflowErrorが発生する箇所
- JVM 仮想マシン スタック。その理由は、(スレッド内) メソッドの再帰呼び出しが正しく終了しなかったことです (メソッド呼び出しが多すぎます)、JSONを逆シリアル化するときの循環参照
メソッド領域、永続生成、メタスペース
- メソッド領域:(それは規範であり、定義です) は、JVM 仕様で定義されているメモリ領域です。クラスを保存するために使用されるメタデータ、メソッドのバイトコード、ジャストインタイムコンパイラに必要な情報など。
- 永続的な世代:(さまざまな Java バージョンでのメソッド領域の実装) は、ホットスポット仮想マシンの JVM 仕様 (1.8 より前) の実装です。
- メタスペース:(さまざまな Java バージョンでのメソッド領域の実装) は、ホットスポット仮想マシン (1.8 以降) による JVM 仕様の別の実装です。この情報の保存スペースとしてローカル メモリを使用する
紹介するだけメタスペース
この写真から 3 つのことを学びましょう
- クラスを初めて使用するときは、クラスローダークラスファイルのクラスメタ情報を読み込み、メタ空間に格納する
- X と Y のクラス メタ情報はメタスペースに保存され、直接アクセスすることはできません。
- X.class、Y.class (インスタンス) を使用して、クラスのメタ情報に間接的にアクセスできます。、どちらも Java オブジェクトに属しており、コードで使用できます。
この写真から何がわかるか
- ヒープ メモリ内:クラス ローダー オブジェクトによってロードされたすべてのクラス オブジェクトに対応するすべてのインスタンス オブジェクトが参照されない場合、それらによって占有されていたヒープ メモリは GC 中に解放されます。
- メタスペースの場合: メモリの解放はクラス ローダー単位で行われます。ヒープ内のクラス ローダーのメモリが解放されると、メタスペース内の対応するクラス メタ情報も解放されます。
2.JVMメモリパラメータ
必要とする
- 一般的な JVM パラメータ、特にサイズに関連するパラメータについてよく理解する
ヒープ メモリ、サイズによって設定
説明する:
-Xms
最小ヒープメモリ(新世代・旧世代含む)-Xmx
最大ヒープメモリ(新世代・旧世代含む)- 通常、-Xms と -Xmx を同じサイズに設定することをお勧めします。つまり、メモリを予約する必要がなく、小さいものから大きいものに拡張する必要がないため、パフォーマンスが向上します。
- -XX:
NewSize
および -XX:MaxNewSize
新しい世代の最小値と最大値を設定しますただし、これは JVM 自体によって制御されるため、通常は設定することはお勧めできません。 -Xmn
設定新世代の記憶-XX:NewSize と -XX:MaxNewSize を同時に設定するのと同等等しい値を持っています- 保持とは、最初はそれほど多くのメモリを占有しないことを意味しますが、メモリが使用されるにつれて、予約されたメモリのこの部分が徐々に使用されます。以下同様
ヒープ メモリ、比例的に設定
説明する:
- -XX:
NewRatio
=2:1 を意味します旧世代: 新世代;(デフォルトは 2:1 です) - -XX:
SurvivorRatio
=4:1 (エデンの園:より); 新しい世代が 6 つの部分に分割され、エデンの園が 4 つの部分を占め、からとへのそれぞれが 1 つの部分を占めることを意味します。デフォルトは 8:1 です) from = to
Survivor = from + to
Q:
JVM メモリ設定パラメータについて:-Xmx10240m -Xms10240m -Xmn5120m -XX:SurvivorRatio=3
最小メモリ値と Survivor 領域の合計サイズはそれぞれ
- 最小メモリ値: -Xmn を 5 で割った値。
- Survivor 領域の合計サイズ = + から = -Xmn を 5 で割って 2 を掛けます。
メタスペースメモリ設定
説明する:
class space
ストレージクラスの基本情報。最大値は-XX:CompressedClassSpaceSizeで制御されます。non-class space
クラスの基本情報以外の情報(メソッドのバイトコード、アノテーションなど)を格納します。- クラス空間と非クラス空間の合計サイズは
-XX:MaxMetaspaceSize
によって制御され、デフォルトは 1G です。
知らせ:
- ここで、-XX:CompressedClassSpaceSize、このスペースはポインタ圧縮がオンになっているかどうかにも関係します。ここでは詳細には触れませんが、単にポインタ圧縮がデフォルトでオンになっていると考えることができます。
コードキャッシュメモリの設定
説明する:
- の場合
-XX:ReservedCodeCacheSize < 240m
、すべての最適化されたマシンコードが無差別に一緒に存在します。 - それ以外の場合は、3 つの領域に分かれています (図にタイプミスがあり、mthod のスペルが間違っており、e が 1 つありません)。
non-nmethods
- JVM 自体が使用するコードprofiled nmethods
- 部分的に最適化されたマシンコードnon-profiled nmethods
- 完全に最適化されたマシンコード
スレッド メモリ設定
各スレッド (仮想マシン スタック) が占有するメモリ。
公式参考資料
- https://docs.oracle.com/en/java/javase/11/tools/java.html#GUID-3B1CE181-CD30-4178-9602-230B800D4FAE
3.JVM ガベージ コレクション
必要とする
- ガベージコレクションアルゴリズムをマスターする
- 世代別リサイクルの考え方をマスターする
- 3色マーキングとマーク抜け処理を理解する
- 一般的なガベージ コレクターについて学ぶ
3 つのガベージ コレクション アルゴリズム
マークアンドスイープ方式(現在は使用されていません。断片化に関する多くの問題があります)
説明する:
GC Root 对象
(ルート オブジェクト)を検索します。決してリサイクルされない物体、のように実行メソッドのローカル変数で参照されるオブジェクトと静的変数で参照されるオブジェクト- マークステージ:GC ルート オブジェクトの参照チェーンに沿って、直接または間接的に参照されるオブジェクトをマークします。
- クリアフェーズ: タグなしオブジェクトによって占有されているメモリを解放します。
キーポイント:
- マーキング速度は生存オブジェクトと線形関係があります。
- クリア速度はメモリサイズに直線的に関係します
- 欠点はメモリの断片化を引き起こすことです
マーキング分別方式(旧世代のガベージコレクションに適しています)
説明する:
- 前のマーキング フェーズとクリーニング フェーズは、マーキングとクリーニングの方法と似ています。
- メモリの断片化を避けるために、残ったオブジェクトを一方の端に移動して並べ替える追加の手順があります。
特徴:
- マーキング速度は生存オブジェクトと線形関係があります。
- クリーニングとソートの速度はメモリ サイズに直線的に関係します。
- デメリットは動作が遅いこと
マーク付きコピー方式(新世代のガベージコレクションに適している(生き残るオブジェクトが少ない))
説明する:
- メモリ全体を 2 つの同じサイズの領域 from と to に分割します。to は常に空き領域で、from には新しく作成されたオブジェクトが格納されます。
- マーキングフェーズは前のアルゴリズムと似ています
- 生き残ったオブジェクトが見つかったら、from 領域から to 領域にコピーされ、コピーの過程で自然にデフラグが完了します。
- コピーが完了したら、from と to の位置を入れ替えるだけです。
特徴:
- マーキングとコピーの速度は、生き残ったオブジェクトに直線的に関係します
- デメリットは倍のスペースを取ること
GC と世代別コレクションのアルゴリズム
GC
目的は達成することです不要なオブジェクトメモリの自動解放、メモリの断片化を軽減し、割り当てを高速化します。
GC のキーポイント:
- リサイクルエリアは、ヒープメモリ、仮想マシンスタックを除く
- 役に立たないオブジェクトを判断するには、次を使用します。到達可能性分析アルゴリズム、3色マーキング方式生きているオブジェクトをマークし、マークされていないオブジェクトをリサイクルする
- GC の具体的な実装は次のように呼ばれます。ガベージコレクター
- GC は主に使用します世代を超えたリサイクルのアイデア
- 理論的根拠としては、ほとんどの物体は行き来してすぐに破壊され、使用後すぐにリサイクルできるが、少数の物体には長期間存続し、毎回リサイクルするのが難しいものもあります。
- これら2種類の物の特性に応じて、リサイクルエリアは次のように分類されます。新しい世代(物体が行き来する) そして老齢(サバイバル、毎回リサイクルするのは難しい)、新世代ではマークコピー方式、旧世代ではマークソート方式が一般的です。
- GC の規模に応じて、マイナー GC、混合 GC、フル GCに分類できます。
マイナー GC : 新世代でガベージ コレクションが発生した、小規模なガベージ コレクション
フル GC : 新世代と旧世代の両方でメモリが不足しており、包括的なガベージ コレクションが実行されます。時間と明らかなシステムの一時停止が感じられます;
混合 GC : ガベージ コレクションは新しい世代で発生し、ガベージ コレクションは一部の古い世代でも発生します。これは G1 ガベージ コレクターに特有です。
世代別リサイクル
- すべてのオブジェクトが最初に割り当てられる Eden と、Survivor 領域 (from と to に分割される) を総称して新しい世代と呼びます。
- Eden がメモリ不足の場合、Eden と from の生き残ったオブジェクトにマークを付けます (現在は何もありません)
- コピー アルゴリズムを使用して、生き残ったオブジェクトを にコピーします。コピーが完了すると、エデンとメモリの両方が解放されます。
- からとからの交換
- 一定の時間が経過すると、エデンの記憶は再び不十分になります。
- エデンとそこから生き残ったオブジェクトにマークを付けます
- コピー アルゴリズムを使用して、生き残ったオブジェクトを にコピーします。
- コピー後、エデンとメモリから両方が解放されます
- からとからの交換
- 旧世代。生存領域オブジェクトが数回のリサイクル (最大 15 回) に耐えた場合、古い世代に昇格します (生存領域のメモリ不足またはオブジェクトが大きい場合、早期昇格が発生します)。
GCスケール
Minor GC
ガベージ コレクションは新しい世代で発生し、一時停止時間が短くなりますMixed GC
G1コレクターならではの新世代+旧世代の部分でのガベージコレクションFull GC
新世代 + 旧世代の完全なガベージ コレクション、長い停止時間は可能な限り避けるべきです
トリコロールマーキング
つまり、オブジェクトのマーキング状態を記録するために 3 つの色が使用されます。
- 黒 – マーク付き
- グレー – マーキング
- 白 - まだマークされていません
- 最初の 3 つのオブジェクトはまだ処理されていないため、灰色で表示されます。
- オブジェクトの参照は処理されて黒で表示され、黒で参照されたオブジェクトは灰色に変わります。
- 等々
- 参照チェーンに沿ってマーク
- 最後のマークのない白い物体はゴミです
同時入札欠落の問題
より高度なガベージ コレクターは、同時マーキング をサポートします。つまり、ユーザー スレッドはマーキング プロセス中にも動作できます。しかし、これにより新たな問題が発生し、ユーザー スレッドがオブジェクト参照を変更すると、マークの欠落の問題が発生します。例えば:
-
図のように作業が完了していないことを示すマーク
-
同時にユーザースレッドが動作し、第 1 層の 2 つのオブジェクト 3 と 4 の間の参照を切断しますが、このとき、オブジェクト 3 を処理しているガベージ コレクション スレッドにとって、オブジェクト 4 は白いガベージとして認識されます。
-
しかし、他のユーザー スレッドがオブジェクト 2 と 4 への参照を確立した場合、オブジェクト 2 は黒の処理済みオブジェクトであるため、ガベージ コレクション スレッドは参照関係の変化に気付かず、マークが見逃されてしまいます。
-
ユーザー スレッドが黒いオブジェクトに新しいオブジェクトを参照させる場合、マークの欠落の問題が依然として発生します。
したがって、同時マーキングの場合、マーキングの欠落の問題を解決する必要があります。マーキングプロセス中の変更を記録するには。解決策は 2 つあります。
Incremental Update
増分更新方式、CMS ガベージ コレクターが使用する- アイデアは、各割り当てアクションをインターセプトすることであり、割り当てが発生している限り、割り当てられたオブジェクトは記録され、再マーキング フェーズで再度確認されます。
Snapshot At The Beginning
、SATBオリジナルのスナップショット方式、G1 ガベージ コレクターは使用します- アイデアは各割り当てアクションをインターセプトすることですが、記録されたオブジェクトは異なるため、これらのオブジェクトは再マーキング段階で 2 回処理する必要があります。
- 新しいオブジェクト記録されます
- 参照関係が削除されたオブジェクトも記録されています
ガベージコレクター -Parallel GC
- マイナー GC は、eden がメモリ不足の場合に発生します。マークされたコピー アルゴリズムが使用され、ユーザー スレッドを一時停止する必要があります。
- 旧: メモリ不足によりフル GC が発生します。マーキングおよびソート アルゴリズムが使用され、ユーザー スレッドを一時停止する必要があります。
- スループットに重点を置く
ガベージ コレクター -ConcurrentMarkSweep GC
(同時マーク スキャン、ガベージ フラグメンテーションあり)
-
これは古い時代に動作し、同時マーキングをサポートし、同時クリアアルゴリズムを使用するコレクターです。
- 同時マーキング中にユーザースレッドを一時停止する必要はありません
- 再マークするときにユーザースレッドを一時停止する必要がある
-
同時実行が失敗した場合 (つまり、リサイクル速度が新しいオブジェクトの作成速度に追いつかない場合)、フル GC がトリガーされます。
-
応答時間に重点を置く
ガベージ コレクター -G1 GC
(JDK のデフォルト コレクター)
- 応答時間とスループットのバランスを取る
- 複数のエリアに分かれており、それぞれのエリアはエデン、サバイバー、オールド、ヒューモンガスとして機能し、そのうちヒューモンガスは大型オブジェクト用に特別に用意されています。
- 新世代収集、同時マーキング、混合収集の 3 つの段階に分かれています。
- 同時実行が失敗した場合 (つまり、リサイクル速度が新しいオブジェクトの作成速度に追いつかない場合)、フル GC がトリガーされます。
G1リサイクル段階 - 新世代リサイクル
- 最初はすべてのエリアがアイドル状態です
- いくつかのオブジェクトを作成し、これらのオブジェクトを保存するためにいくつかの空き領域をエデン ガーデンとして選択しました
- Eden でガベージ コレクションが必要な場合は、空き領域を生存領域として選択し、コピー アルゴリズムを使用して生存オブジェクトをコピーします。これには、ユーザー スレッドを一時停止する必要があります。
- コピーが完了し、以前のエデンメモリが解放されます。
- 時間が経つにつれ、エデンの記憶は足りなくなっていきます。
- コピー アルゴリズムを使用して、Eden 内の存続オブジェクトと以前の存続エリアを新しい存続エリアにコピーし、古いオブジェクトは古い世代に昇格します。
- エデンと以前の生存エリアの記憶を解放する
G1 収集フェーズ - 同時マーキングと混合収集
- 古い世代によって占有されているメモリがしきい値を超えると、同時マークがトリガーされ、ユーザー スレッドを一時停止する必要はありません。
- 同時マーキングの後、マーク欠落の問題を解決するために再マーキング フェーズが行われますが、このとき、ユーザー スレッドを一時停止する必要があります。これらがすべて完了すると、どのオブジェクトが古い世代に残っているかがわかります。
(その後、混合収集フェーズに入ります。この時点では、古い世代の領域はすべてリサイクルされませんが、価値の高い (生き残っているオブジェクトが少ない) 領域が、一時停止時間の目標に基づいて最初にリサイクルされます (これは、名前はガベージファースト)。
- 混合コレクション フェーズでは、eden、survivor、old がレプリケーションに関与します。次の図は、Eden と survivor 領域の存続オブジェクトのレプリケーションを示しています。
- 次の図は、古い世代および存続領域でプロモートされた存続オブジェクトのレプリケーションを示しています。
- コピーが完了し、メモリが解放されます。新世代収集、同時マーキング、および混合収集の次のラウンドに入る
4.メモリオーバーフロー
必要とする
- メモリオーバーフローを引き起こす典型的な状況をいくつか挙げることができる
典型的な状況[インタビューの質問: プロジェクトでメモリ オーバーフローが発生する状況とその解決方法]
-
スレッドプールの誤用によるメモリオーバーフロー
- 参考
day03.TestOomThreadPool
- 固定サイズのスレッド プールの悪用: タスクが多すぎると、スレッド プール キューがいっぱいになり、メモリ オーバーフローが発生します。
- バッファリングされたスレッドプールの悪用: スレッド数に上限がないため、スレッド数によってシステム リソースが使い果たされ、メモリ オーバーフローが発生します。
- 解決:固定スレッド プールを使用する
Executors
組み込みメソッド、および組み込みバッファ スレッド プールを使用するメソッドは使用しないでください。キューに使用するキューを制御し、上限を自分で設定する必要があります。new FixedThreadPool()
new CachedThreadPoll()
- 参考
-
大量のデータのクエリによるメモリ オーバーフロー
- 参考
day03.TestOomTooManyObject
- 一度に大量のデータをクエリするとメモリ オーバーフローが発生する: 例えば、電子商取引プラットフォームでは、ユーザーがクエリを実行するたびに 100 万件の商品が存在しますが、総データ量が 363Mb であると、複数のユーザーがクエリを実行するとデータが非常に大きくなり、サーバーのメモリが枯渇してしまいます。 ;
- 解決:すべてをクエリする場合、クエリ条件を追加できます。最も重要なことは、返されるレコードの最大数
limit
を制限する。1 つのクエリで返されるレコードが多すぎないように注意してください。返さないと、サーバーのメモリが枯渇しやすくなります。
- 参考
-
動的に生成されたクラスによって引き起こされるメモリ オーバーフロー
- 参考
day03.TestOomTooManyClass
- クラスを動的に生成するツール、正しい使い方に注意、解放できないクラスが生成されすぎてメタスペースのメモリが枯渇することを避けるため。
- 参考
5.クラスロード
面接の質問:クラスロードプロセス、保護者代表団
必要とする
- マスタークラスロードフェーズ
- マスタークラスローダー
- 理解する親委任メカニズム
クラスロードプロセスの 3 段階
-
負荷
- 似たものになるだろうバイトコード読み込みメソッド領域、作成しますclass.class オブジェクト(クラスオブジェクトはヒープ上にあります)
- このクラスの親クラスがロードされていない場合、最初に親クラスをロードします
- 読み込み中は遅延実行(ロードはクラスが使用されている場合にのみトリガーされます)
-
リンク
- 確認する-確認するクラスがクラス仕様、合法性、セキュリティチェックに準拠しているかどうか
- を準備する静的 (静的) 変数はスペースを割り当て、デフォルト値を設定します
- 解析 –定数プールのシンボル参照を直接参照に解決します。
-
初期化
- 埋め込む静的コード ブロック (静的変数を含む)そして非最終静的変数の割り当て
- 静的
final
に変更された基本类型
変数の割り当てはリンクフェーズ中に完了します - 初期化は遅延実行(使用時のみ初期化されます)
検証手段
- jps を使用してプロセス番号を表示します
- jhsdb を使用してデバッグし、コマンドを実行して
jhsdb.exe hsdb
グラフィカル インターフェイスを開きます。
Class Browser
現在の JVM にどのクラスがロードされているかを確認できます- コンソール
universe
コマンドを使用してヒープ メモリ範囲を表示します。- コンソールコマンドを使用して(領域) の詳細
g1regiondetails
を表示しますregion
scanoops 起始地址 结束地址 对象类型
タイプに応じて、特定の範囲内のオブジェクトのアドレスを見つけることができます- コンソール
inspect 地址
コマンドは、このアドレスに対応するオブジェクトの詳細を表示できます。- javap コマンドを使用してクラスのバイトコードを表示する
コードの説明
day03.loader.TestLazy
- 検証クラスのロードは遅延され、クラスのロードは使用された場合にのみトリガーされます。day03.loader.TestFinal
- Final で変更された変数がクラスのロードをトリガーしないことを確認します
jdk 8のクラスローダー
名前 | どのクラスをロードするか | 説明する |
---|---|---|
ブートストラップクラスローダー(クラスローダーを開始する) | JAVA_HOME/jre/lib | 直接アクセス不可 |
拡張クラスローダー(拡張クラスローダー) | JAVA_HOME/jre/lib/ext | 上位レベルは Bootstrap で、null として表示されます。 |
アプリケーションクラスローダー(アプリケーションクラスローダー) | クラスパス | 上位はエクステンションです |
カスタムクラスローダー | カスタマイズ | 親はアプリケーションです |
親委任メカニズム
いわゆる保護者代表団これは、上位クラス ローダーにロードの優先順位が割り当てられることを意味します。
- このクラスは上位レベルで見つけてロードでき、ロード後は下位レベルのローダーにも表示されます。
- このクラスが見つからない場合は、下位レベルのクラス ローダーがロードを実行する資格を与えられます。
保護者の代表団の目的は 2 つあります。
- 上位クラスローダーのクラスを下位クラスと共有できるようにします(その逆は不可)。これにより、クラスはjdkによって提供されるコアクラスに依存できるようになります。
- クラスのロードを優先し、コア クラスが最初にロードされるようにします。
保護者代表団に関する誤解
次のインタビューの質問に対する答えは間違っています
どうしたの?
-
独自のクラス ローダーを作成して偽の java.lang.System をロードできますか? 答えはノーです。
-
独自のクラスローダーが親の委任を使用していると仮定すると、本物の java.lang.System は起動クラスローダーによって最初にロードされ、偽のクラスローダーは当然ロードされません。
-
独自のクラスローダーが親の委任を使用しないと仮定すると、クラスローダーが偽の java.lang.System をロードするときに、最初に親クラス java.lang.Object をロードする必要がありますが、委任を使用していないため、java が見つかりません.lang.Object なのでロードは失敗します
-
上記は単なる仮定です。実際、カスタム クラス ローダーが java. で始まるクラスをロードすると、セキュリティ例外がスローされることがわかります (セキュリティ チェックがあるため、ロードする順番はありません)。jdk9 以降のバージョンでは、これらの特別なパッケージが名前はモジュールに関連しています。バインド後はコンパイルすることさえできません。
コードの説明
- day03.loader.TestJdk9ClassLoader - クラスローダーとモジュール間のバインディング関係を示します。
6.4種類の参考書
インタビューの質問: オブジェクト参照型の種類は何ですか?
必要とする
- 4種類の引用をマスターしよう
強力な参照
- 通常の変数の代入は、A a = new A(); などの強参照です。
- GC ルートの参照チェーンを通じて、強参照でオブジェクトが見つからない場合、オブジェクトはリサイクルできます。
ソフトリファレンス
- 例えば:
SoftReference a = new SoftReference(new A());
- もしオブジェクトへのソフト参照のみがある場合、最初のガベージ コレクションではオブジェクトはリサイクルされず、メモリがまだ不足している場合は、再度リサイクルされるまでオブジェクトは解放されません。
- ソフト参照自体は参照キューと連動して解放する必要があります。
- 典型的な例は反射データです
弱い参照
- 例:WeakReference a = new WeakReference(new A());
- 弱参照のみがオブジェクトを参照している場合、ガベージ コレクションが発生するたびにオブジェクトは解放されます。
- 弱参照自体は参照キューで解放する必要がある
- 典型的な例は、ThreadLocalMap の Entry オブジェクトです。
ファントムリファレンス
-
例: PhantomReference a = new PhantomReference(new A(),referenceQueue);
-
参照キューと組み合わせて使用する必要があります。仮想参照によって参照されるオブジェクトがリサイクルされると、スレッドは
Reference Handler
仮想参照オブジェクトをキューに入れるため、どのオブジェクトがリサイクルされたかを知り、それらに関連するリソースをさらに処理できます。 -
典型的な例は、DirectByteBuffer に関連付けられたダイレクト メモリを解放する Cleaner です。
コードの説明
- day03.reference.TestPhantomReference - ファントム参照の基本的な使用法を示します。
- day03.reference.TestWeakReference - ThreadLocalMap をシミュレートし、参照キューを使用してエントリ メモリを解放します。
package day03.reference;
// 前面讲的弱、虚引用配合引用队列,目的都是为了找到哪些 java 对象被回收,从而进行对它们关联的资源进行进一步清理
// 为了简化 api 难度,从 java 9 开始引入了 Cleaner 对象
public class TestCleaner1 {
public static void main(String[] args) throws IOException {
Cleaner cleaner = Cleaner.create();
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 1")); //参数1关注的资源;参数2清理动作
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 2"));
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 3"));
MyResource obj = new MyResource(); //局部变量;
//下面的方法执行之前保持了强引用,则4不能被回收掉;
cleaner.register(obj, ()-> LoggerUtils.get().debug("clean 4")); //4的清理动作不能被执行;
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 5"));
cleaner.register(new MyResource(), ()-> LoggerUtils.get().debug("clean 6"));
System.gc();
System.in.read(); //暂停的动作,因为cleaner是首部线程,当没有主线程执行时cleaner会自动结束,肉眼看不到效果;
}
static class MyResource {
}
}
7。完成させる
面接の質問:ファイナライズの
要件を理解する
- ファイナライズの動作原理と欠点をマスターする
ファイナライズ
[面接の質問: ファイナライズについての理解]
- 【一般的な回答】 Object 内のメソッドであり、サブクラスでオーバーライドするとガベージコレクション時にこのメソッドが呼び出され、その中でリソースの解放やクリーンアップを行うことができます。
- [素晴らしい回答]Finalize メソッドにリソースの解放とクリーンアップを入れるのは非常に良くないことであり、パフォーマンスに大きな影響を与えます。深刻な場合には、OOM (メモリ オーバーフロー) を引き起こす可能性もあります。Java9 以降では @Deprecated としてマークされており、使用することはお勧めできません。 。
テストコード:
package day03.reference;
public class TestFinalize {
static class Dog {
private String name;
public Dog(String name) {
this.name = name;
}
@Override
protected void finalize() throws Throwable {
LoggerUtils.get().debug("{}被干掉了?", this.name);
int i = 1 / 0;
}
}
public static void main(String[] args) throws IOException {
new Dog("大傻");
new Dog("二哈");
new Dog("三笨");
System.gc();
System.in.read();
}
/*
第一,从表面上我们能看出来 finalize 方法的调用次序并不能保证(那个对象先回收,就调用那个对象的finalize 方法)
第二,日志中的 Finalizer 表示输出日志的线程名称,从这我们看出是这个叫做 Finalizer 的线程调用的 finalize 方法
第三,你不能注释掉 `System.in.read()`,否则会发现(绝大概率)并不会有任何输出结果了;
(因为finalize线程是一个守护线程,如果主线程没有了,守护线程就不执行行了),
从这我们看出 finalize 中的代码并不能保证被执行
第四,如果将 finalize 中的代码出现异常,会发现根本没有异常输出;(调用了try...catch方法)
第五,还有个疑问,垃圾回收时就会立刻调用 finalize 方法吗?
不会,先把对象加入到ReferenceQueue,从 ReferenceQueue 中逐一取出每个 Finalizer 对象,把它们从链表断开,并真正调用 finallize 方法
*/
}
結果:
原則を確定する
- Finalize メソッドを処理するためのコア ロジックは、unfinalized という名前の静的変数 (二重リンク リスト構造) を含む java.lang.ref.Finalizer クラスにあります。Finalizer は、別の参照オブジェクト (ステータスおよびソフト、弱い) と見なすこともできます。とweakは同等ですが、一般には公開されていないため、直接使用することはできません)
- Finalize メソッドを持つオブジェクトがオーバーライドされると、コンストラクター メソッドが呼び出されるときに、JVM はそれを Finalizer オブジェクトにパッケージ化し、未完了のリンク リストに追加します (このとき、二重リンク リスト内のオブジェクトの Finalize メソッドはまだ呼び出されていません (ガベージ コレクションが発生すると呼び出されます)]
- Finalizer クラスにはもう 1 つの重要な静的変数である ReferenceQueue 参照キューがありますが、これは最初は空です。犬オブジェクトがガベージ コレクションできる場合、これらの犬オブジェクトに対応するファイナライザー オブジェクトがこの参照キューに追加されます。
- ただし、この時点では、Dog オブジェクト(Finalizer に関連付けられたオブジェクト) は、未ファイナライズ -> Finalizer の参照チェーンがまだ参照しているため、すぐにはリサイクルできません。ファイナライズ メソッドの調整が完了するまで待ってから、リサイクル]; リサイクルした場合、これらのオブジェクトのファイナライズ メソッドを呼び出す方法はありません。
- FinalizerThread スレッドは、ReferenceQueue から各 Finalizer オブジェクトを 1 つずつ取り出し、リンクされたリストから切断し、実際に Finalize メソッドを呼び出します。
- Finalizer オブジェクト全体が未完成のリンク リストから切り離されているため、誰もそれと Dog オブジェクトを参照できず、次の gc でリサイクルされます。
ファイナライズのデメリット
[ファイナライズメソッドが非常に悪質でパフォーマンスに影響を及ぼす理由]
ひどい
- リソースの解放は保証できません: FinalizerThread はデーモン スレッドです。コードの実行が完了する前にスレッドが終了する可能性が非常に高く、その結果、リソースが正しく解放されません。
- エラーが発生したかどうかを判断できません※finalizeメソッド実行時は例外(Throwable)が飲み込まれるため、リソース解放時にエラーが発生したかどうか判断できません。
パフォーマンスに影響を与える
- メモリの解放が間に合わない: Finalize メソッドがオーバーライドされたオブジェクトは、初めて gc するときに、占有しているメモリを時間内に解放できません。これは、実際に gc される前に、FinalizerThread が Finalize を呼び出して未ファイナライズ キューから削除するまで待つ必要があるためです。 2 回目はメモリを解放します。
- gc の原因はメモリ不足であり、ファイナライズ呼び出しが非常に遅いことが想像できます (2 つのキューの削除操作は連続して実行され、接続クラスのリソースを解放するのにそれほど時間がかかるはずはありません)。リリースが間に合えば、オブジェクトは解放されます。タイミングが合わないと、徐々に古い世代に移動します。古い世代にゴミが蓄積されすぎると、フル gc が発生します。フル gc 後のリリース速度がまだ維持できない場合は、新しいオブジェクトの作成速度が上がると、OOM が発生します。
質問
- 一部の記事では、[ファイナライザー スレッドはメイン スレッドと競合しますが、優先順位が低く、CPU 時間も少ないため、メイン スレッドに追いつくことはありません] と記載されていますが、これは明らかに間違いであり、FinalizerThread の優先順位の方が高いです。通常のスレッドよりもファイナライズのシリアル実行が遅いなどの理由が考えられます(優先度が低いためではありません)。
コードの説明
- day03.reference.TestFinalize - ファイナライズ用のテストコード