JVM: 自分がどうやって死んだのか知りたいだけ

JVM: 自分がどうやって死んだのか知りたいだけ

[[347298]]

Java プログラムが JVM 上で実行されることは誰もが知っています。 JVM に何らかの障害が発生すると、サービスの安定性が必然的に影響を受けます。運が良ければ、サービスが不安定になり、一部のリクエストが遅延したり異常になったりする可能性があります。運が悪いと、JVM が直接クラッシュし、サービスが完全に中断されることになります。

これは良いことではありません。 JVM とともに、サービスだけでなく私たちの精神も崩壊するでしょう。

いわゆる JVM クラッシュは、一般的にメモリ オーバーフロー、つまり OutOfMemoryError と StackOverflowError を指します。もう 1 つの状況は、オフヒープ メモリの使用量が非常に大きい場合です。これにより、JVM が配置されているマシンのメモリが膨張し、マシンの再起動などの異常な状況が発生します。この状況をメモリ リークと呼びます。

では、どのような状況で JVM がクラッシュするのでしょうか?クラッシュにはどのような種類がありますか?諺にあるように、自分と敵を知ることによってのみ、百戦錬磨の勝者となることができるのです。クラッシュの原因を理解することによってのみ、JVM クラッシュの問題をより適切に解決できます。

まず、JVM メモリ モデル図を示します。 JVM は理解するのが非常に抽象的です。次の図は、JVM メモリ モデルを具体的に理解するのに役立ち、オーバーフローが発生する部分も図で確​​認できます。 JDK 8 では、永続世代は存在しなくなり、メタスペースに置き換えられました。

次に、Hotspot JDK 8 を背景として、JVM メモリ オーバーフローとメモリ リークのいくつかのケースを見ていきます。

まず、ヒープ領域のサイズを制限するために JVM 起動パラメータを設定します。ヒープ領域は、新世代用に 10M、メタスペース用に 10M を含む 20M に設定され、ガベージ コレクション アルゴリズムとして CMS アルゴリズムを使用するように指定します。以降のすべての例では、このパラメータ セットを使用します。

  1. -XX:+ConcMarkSweepGC を使用する
  2. -XX:+CMS 占有開始のみを使用する
  3. -XX:CMS開始占有率=70
  4. -XX:+ExplicitGCInvokesConcurrentAndUnloadsClasses
  5. -XX:+CMSクラスのアンロードが有効
  6. -XX:+並列参照プロセス有効
  7. -XX:+CMSScavengeBeforeRemark
  8. -詳細:gc
  9. -Xms20M
  10. -Xmx20M
  11. -Xmn10M
  12. -XX:+GC詳細を印刷
  13. -XX:生存率=8
  14. -XX:+メモリ不足エラー時のヒープダンプ
  15. -XX:メタスペースサイズ=10M
  16. -XX:最大メタスペースサイズ=10M
  17. -XX:HeapDumpPath=/Users/fengzheng/jvmlog

ヒープオーバーフロー

ヒープ オーバーフローは、おそらく最も一般的なメモリ オーバーフローのシナリオです。 JVM に割り当てられたほとんどのオブジェクト インスタンスと配列はヒープ上に格納されます。さらに、ヒープメモリはガベージコレクターの主戦場でもあります。

Java プログラムが起動すると、ヒープ領域のサイズが指定されます。新しいオブジェクトと配列が作成されると、それらはヒープ上に割り当てられます。新しいオブジェクトがスペースを要求したときに、ヒープ メモリが不足している場合は、ガベージ コレクションが発生します。ほとんどの場合、これはマイナー GC と呼ばれる新しい世代で発生します。新しい世代が収集されてもまだ十分なスペースがない場合は、FullGC が発生します。 FullGC後も領域が不足している場合は、OOMエラー(ヒープオーバーフロー)が発生します。

このシナリオをシミュレートする

  1. プライベートファイナルスタティック 整数_1K = 1024;
  2.  
  3. 公共 静的void main(String[] args){
  4. リスト<byte[]> byteList = 新しいArrayList<>();
  5. 静かにクラッシュヒープを待機します(byteList);
  6. }
  7.  
  8. 公共 静的void quietlyWaitingForCrashHeap(List<byte[]> byteList) {
  9. 試す {
  10. )の間{
  11. byteList.add (新しいバイト[500 * _1K]);
  12. //スレッドスリープ(1000);
  13. スレッド.sleep(100);
  14. }
  15. } キャッチ (InterruptedException e) {
  16.  
  17. }
  18. }

上記の方法は、

以下はプログラムを実行した後の結果です。ガベージ コレクション後も余分なスペースがないため、java.lang.OutOfMemoryError: Java ヒープ スペース例外が発生します。

画像-20201016211017630

ヒープ メモリ オーバーフローの根本的な原因は、使用中のオブジェクトのサイズがヒープ メモリ サイズを超えていることです。

ヒープメモリスペースの設定が小さすぎます。推定される実際のヒープ サイズに基づいて、ヒープ領域を適切に設定する必要があります。

プログラムの脆弱性により、一部の静的変数が増大し続けます。たとえば、キャッシュ データの初期化が不適切だと、キャッシュが際限なく大きくなり、最終的にはヒープ メモリのオーバーフローが発生します。この状況には、適切なテストを実施し、問題が発生した後に適切なログ分析を行う以外に、おそらく適切な解決策はありません。

スタックオーバーフロー

仮想マシン スタックは、ローカル変数テーブル、オペランド スタック、動的リンク、メソッド終了などの情報を格納するために使用されます。Java メソッドが呼び出されるたびに、仮想マシン スタック内にこのメソッドのスタック フレームが生成されます。

スタックには、仮想マシン スタックに加えて、ネイティブ メソッド スタックも含まれます。呼び出されたメソッドがネイティブ メソッド (C 言語で実装されたメソッドなど) の場合、ネイティブ メソッド スタックが使用されます。ただし、HotSpot 仮想マシンでは、仮想マシン スタックとローカル メソッド スタックが 1 つに結合されます。

スタックオーバーフローのシナリオをシミュレートする

  1. 公共 静的void main(String[] args){
  2. スタックオーバーフロー();
  3. }
  4.  
  5. /**
  6. * スタックオーバーフロー
  7. */
  8. 公共 静的voidスタックオーバーフロー() {
  9. スタックオーバーフロー();
  10. }

上記のコードでは、stackOverflow() メソッドの呼び出しは、再帰終了のない無限再帰プロセスです。前述したように、メソッドが呼び出されるたびに、仮想マシン スタックにスタック フレームが生成されます。無限再帰は必然的にスタック フレームの無限生成を引き起こし、最終的にはスタック領域がいっぱいになり、オーバーフローが発生します。

画像-20201019122447325

上記は最も一般的な状況をシミュレートしたものです。この状況の原因はおそらくプログラムのバグによるものです。一般的に言えば、再帰には再帰的な終了が必要です。何らかの理由でプログラムが実行中に終了条件に到達できない場合、この例外が発生します。ループ本体もございます。ループ本体の反復回数が多すぎると、スタック オーバーフローが発生する可能性があります。

スレッドが多すぎるなど、可能性の低い他の理由もあるかもしれません。スレッドを作成するには、仮想マシン スタックにスペースを割り当てる必要があります。作成されるスレッドが多すぎると、OutOfMemoryError 例外が発生する可能性があります。ただし、一般的には手動でスレッドを作成するのではなく、スレッド プール方式を使用するため、このような状況が発生する可能性は低くなります。

メタスペース オーバーフローは、クラス情報、定数、静的変数、ジャストインタイム (JIT) コンパイル コード、および仮想マシンによってロードされたその他のデータを格納するために使用されます。 JDK 8 では、permanent 世代の代わりに metaSpace が使用されています。デフォルトでは、metaSpace のサイズは無制限、つまりサーバーの実際のメモリ サイズになります。ただし、一般的には、メタスペースのサイズを設定するのが最適です。

一般的に、動的に生成されるクラスが大量に生成されると、メタスペースのメモリ オーバーフローが発生する可能性があります。

メタスペースオーバーフローのシミュレーション

  1. 公共 静的void main(String[] args){
  2. リスト<byte[]> byteList = 新しいArrayList<>();
  3. //静かにCrashHeapを待機します(byteList);
  4. // スタックオーバーフロー();
  5. メソッドAreaOverflow();
  6. }
  7.  
  8. 公共 静的voidメソッドAreaOverflow() {
  9. 整数i = 0;
  10. )の間{
  11. エンハンサー enhancer = new Enhancer();
  12. エンハンサー.setUseCache( false );
  13. エンハンサー.setSuperclass(MethodOverflow.class);
  14. エンハンサー.setCallback(新しいMethodInterceptor() {
  15. @オーバーライド
  16. パブリックオブジェクトインターセプト(オブジェクトo、メソッドメソッド、オブジェクト[]オブジェクト、メソッドプロキシメソッドプロキシ)はThrowableをスローします{
  17. メソッドProxy.invokeSuper(o, objects)を返します
  18. }
  19. });
  20. エンハンサーを作成します();
  21. System.out.println (++i) ;
  22. }
  23. }

CGLIB を通じて多くの動的クラスを動的に作成すると、メタスペースに格納されるクラス情報が増え、メタスペース オーバーフローが発生します。

画像-20201019163227576

たとえば、Spring や MyBatis などの技術フレームワークを使用する場合、Bean インスタンス クラスが動的に作成されます。さらに、Spring AOP は動的プロキシ クラスも生成します。

オフヒープメモリオーバーフロー

ほとんどの場合、メモリは JVM ヒープ メモリ内に割り当てられますが、まれにヒープ外部に直接メモリ領域を割り当てる必要がある場合もあります。オフヒープメモリを使用することによる利点はいくつかあります。

  • プロセス間で共有できるため、仮想マシン間のコピーが削減されます。
  • ガベージ コレクションの一時停止の改善: アプリケーションに、YGC または FullGC を頻繁にトリガーする長期間存続する大規模なオブジェクトがある場合は、これらのオブジェクトをヒープ外に配置することを検討できます。ヒープが大きすぎると、Java アプリケーションのパフォーマンスに影響します。オフヒープ メモリが使用される場合、それは仮想マシンではなくオペレーティング システムによって直接管理されます。その結果、ヒープ メモリを小さく保つことができ、ガベージ コレクションがアプリケーションに与える影響を軽減できます。
  • シナリオによっては、プログラムの I/O 操作のパフォーマンスが向上する場合があります。オンヒープメモリからオフヒープメモリにデータをコピーする手順は省略されます。

通常、オフヒープ メモリは、大量の頻繁な IO 操作が必要な場合に使用されます。たとえば、Netty と RocketMQ はオフヒープ メモリを使用してプロセスを高速化します。

したがって、システム メモリの使用量が非常に大きい場合は、スタックをチェックしても結果が出なかった場合、オフヒープ メモリの使用量をチェックして、オフヒープ メモリがオーバーフローしていないかどうかを確認できます。

要約する

事前に設定を行ってください

JVM の問題自体は比較的抽象的で直感的に発見するのが難しいため、プロジェクトがオンラインになる前に、コード ロジックをテストするだけでなく、スタック サイズ、ガベージ コレクターの種類など、アプリケーションのサイズと特性に応じて JVM パラメータを合理的に構成し、適切なパラメータを選択することも必要です。

さらに、ガベージ コレクション ログを保持し、メモリ オーバーフローが発生したときにダンプ ファイルを保存する必要があります。

プロセスを監視する

プログラムがオンラインのときは、Spring Admin などの軽量監視ツールを使用したり、大規模なプロジェクトの場合は Cat や SkyWallking などの分散リンク監視システムを使用したりして、JVM を適切に監視します。

現場での保護とその後の分析を提供する

パラメータ構成と監視プラットフォームがどれほど合理的であっても、例外は必ず発生します。これは正常です。例外がない場合にのみ問題が生じます。例外が発生した後、シーンはタイムリーに保存される必要があります。マルチインスタンス アプリケーションの場合は、例外が発生したインスタンスを一時的にオフラインにして、問題をトラブルシューティングすることができます。単一インスタンスのサービスの場合は、最新のログとダンプが保存されていることを速やかに確認する必要があります。確認後、サービスを再起動するアクションを実行します。

この記事はWeChatの公開アカウント「古代の凧」から転載したものです。下のQRコードからフォローできます。この記事を転載する場合は、Ancient Kite の公開アカウントにご連絡ください。

<<:  専門家の予測:2021年にクラウドコンピューティング分野で出現するトレンド

>>:  VMware が 3 年連続で Gartner Magic Quadrant の WAN エッジ インフラストラクチャのリーダーに選出

推薦する

EasyStack が新世代のプライベート クラウド ECS を発表、クラウド コンピューティングの新たな章を開く

[51CTO.com からのオリジナル記事] 2006 年に Amazon AWS がクラウド サー...

ローカル ウェブサイトの困難な道: ユーザー エクスペリエンスからどこへ向かうか (パート 2)

前回の記事では、ローカル Web サイトの開発の歴史とローカリゼーションの方法について説明しました。...

chicagovps-50% オフ プロモーション/すべてのサイトに有効/VPS は最低 $6/年払い/サーバーは最低 $26

chicagovpsのハロウィンプロモーションが始まってから2、3日経ちましたが、まだ投稿していませ...

ByteDance が大手 Vs を買収、Bilibili はジレンマに陥っているのか?

ビリビリの人気科学分野の二大巨頭の一つ「ウィザードファイナンス」は6月14日、ビリビリからの撤退を発...

仮想化を選択する理由は何ですか?ネットワーク管理業務にどのような効果がありますか?各メーカーの仮想化技術を比較!

仮想化を選択する理由は何ですか? 1. 運用保守担当者が 1 つのエリアのアラーム情報を処理している...

dreamhost - 40% オフ (月額 3.95 ドル) + 2 つの無料ドメイン名

クーポンコード: fallsavings395 10月31日まで有効!このオファーは世界中で利用可能...

cmivps: 米国の 3 ネットワーク Unicom AS4837 ライン VPS、20G 防御、月額 6 ドル、1G メモリ/1 コア/30g NVMe/2T トラフィック/1G 帯域幅

cmivpsは米国南部海岸のシアトルデータセンターにVPSを構え、中国聯通のAS4837回線に接続し...

コンテナセキュリティのベストプラクティスと一般的な脅威

この記事では、コンテナ セキュリティの課題について学習し、イメージやハーバーなどの保護など、コンテナ...

実際のデータは、360度検索が百度よりもユーザー価値が高いことを証明している

ご存知のとおり、360 Search は現在中国で 2 番目に大きな検索エンジンであり、SEO に携...

SEOで見落とされがちな詳細

SEO で見落とされがちな詳細。SEO の最適化は時間がかかり、手間のかかる作業です。作業の効率と方...

ブログマーケティングの特徴と価値

ブログ マーケティングを理解するには、まずブログとは何かを知る必要があります。狭義では、ブログはオン...

Baiduの信頼性を高めるためのいくつかの考察

この記事は、武漢SEOクレイジーが約1か月前にA5チャットに投稿したものです。当時は比較的シンプルな...

Amazon Web Services、AWS Glue サービスのセキュリティ脆弱性を修正

[51CTO.com クイック翻訳] Amazon Web Services は、AWS Glue ...

中国における自律制御型クラウドコンピューティングの開発動向に関する議論

[[405847]]世界各国の技術競争が激化する中、企業のデジタル変革の基盤となるクラウドコンピュー...