ホームページ  >  記事  >  Java  >  Java メモリ領域とメモリ オーバーフロー例外の詳細な紹介

Java メモリ領域とメモリ オーバーフロー例外の詳細な紹介

黄舟
黄舟オリジナル
2017-03-17 10:18:411324ブラウズ

この記事では、主に Java メモリ領域とメモリ オーバーフロー例外に関する関連情報を紹介します。必要な方は、

Java メモリ領域とメモリ オーバーフロー例外

概要

C および C++ プログラムを参照してください。 , メモリ管理の分野では、プログラマはメモリを使用する絶対的な権利を持っていますが、メモリを正しく使用してクリーニングする必要もあり、プログラマにはより高いレベルが求められます。 Java プログラマにとって、仮想マシンの自動メモリ管理メカニズムのおかげで、新しい操作のたびに削除/解放のペアのコードを記述する必要がなく、メモリ リークやメモリ オーバーフローの問題が発生する可能性が低くなります。メモリを管理する仮想マシンには問題がないように見えます。ただし、Java プログラマが Java 仮想マシンにメモリ制御の権限を与えているため、メモリ リークやオーバーフローの問題が発生すると、仮想マシンがメモリをどのように使用するかを理解していないと、トラブルシューティングが非常に困難になります。仕事。

Java ランタイム データ領域

一般に、JVM はヒープとスタックの 2 つの部分だけで構成されていると考えられていますが、実際の Java 仮想マシンは、実行中に管理するメモリをいくつかの部分に分割します。 Java プログラム。これらの領域には独自の目的があり、作成および破棄のタイミングも異なります。仮想マシン プロセスの起動とともに存在する領域もあれば、ユーザー スレッドの開始と終了に応じて作成および破棄される領域もあります。以下に示すように:

プログラムカウンター

コンピューターの構成原理を学習したことがある方なら、JVM にも独自の CPU があるため、プログラムカウンターが ID カードに相当することが非常に明確になるはずです。マルチスレッド プログラムを実行する場合、タイム スライス ローテーション方式により、プログラム カウンターに基づいてスレッドの実行がスケジュールされます。

プログラム カウンター レジスタは小さなメモリ空間であり、その機能は現在のスレッドによって実行されるバイトコードの行番号インジケーターとして見ることができます。仮想マシンの概念モデル (単なる概念モデルであり、さまざまな仮想マシンをより効率的な方法で実装できます) では、バイトコード インタープリターは、このカウンターの値を変更して、実行する必要がある次のステップを選択することによって機能します。 、分岐、ループ、ジャンプ、例外処理、スレッド回復、その他の基本的な機能はすべて、このカウンターに依存して完了する必要があります。

Java 仮想マシンのマルチスレッドは、スレッドを順番に切り替えてプロセッサの実行時間を割り当てることで実装されるため、ある時点では、1 つのプロセッサ (マルチコア プロセッサの場合は 1 コア) がスレッド内の命令のみを実行します。 。したがって、スレッド切り替え後に正しい実行位置に戻るためには、各スレッド間のカウンタが互いに影響を及ぼさずに独立して格納される、独立したプログラム カウンタが必要です。このようなメモリ領域を「スレッド プライベート」と呼びます。 "。 "メモリ。 スレッドが Java メソッドを実行している場合、このカウンタは実行中の仮想マシンのバイトコード命令のアドレスを記録します。スレッドが Natvie メソッドを実行している場合、カウンタ値は空 (未定義) です。このメモリ領域は、Java 仮想マシン仕様で OutOfMemoryError 条件が指定されていない唯一の領域です。

Java 仮想マシン スタック

プログラム カウンタと同様に、Java 仮想マシン スタック (Java 仮想マシン スタック) もスレッドプライベートであり、その

ライフサイクル

はスレッドと同じです。

仮想マシン スタックは、Java メソッド実行のメモリ モデルを記述します。各メソッドが実行されると、ローカル

変数テーブル、操作スタック、ダイナミック リンク、およびメソッド出口およびその他の情報。各メソッドの呼び出しから実行が完了するまでの過程は、仮想マシンスタックにおいてスタックフレームがスタックからプッシュされてからスタックからポップアウトされるまでの過程に相当します。
よく Java メモリをヒープ メモリ (Heap) とスタック メモリ (Stack) に分ける人がいますが、この分割は実際にはこれよりもはるかに複雑です。この分割方法の人気は、ほとんどのプログラマが最も注目し、オブジェクト メモリの割り当てに最も密接に関係しているメモリ領域がこれら 2 つの領域であることを示しているだけです。参照される「ヒープ」については後で詳しく説明します。参照される「スタック」とは、今話している仮想マシン スタック、または仮想マシン スタックのローカル変数テーブル部分です。

ローカル変数テーブルには、既知のさまざまな基本的な データ型 (boolean、byte、char、short、int、float、long、double) とオブジェクト参照 (オブジェクト自体と同等ではない参照型) が格納されます。コンパイル時、仮想マシンの実装に応じて、オブジェクトの開始アドレスを指す参照ポインタである場合もあれば、オブジェクトを表すハンドルまたはこのオブジェクトに関連する他の場所を指す場合もあります。バイトコード命令のアドレスに))。

64 ビットの long 型および double 型のデータは 2 つのローカル変数スペース (スロット) を占有しますが、他のデータ型は 1 つだけを占有します。ローカル変数テーブルに必要なメモリ空間は、メソッドの開始時に、フレーム内でどのくらいのローカル変数空間を割り当てる必要があるか完全に決定されます。ローカル変数テーブルのサイズは、実行中に変更されません。方法の。

Java 仮想マシンの仕様では、この領域に対して 2 つの例外条件が指定されています。スレッドによって要求されたスタックの深さが仮想マシンによって許可される深さよりも大きい場合、仮想マシンのスタックが許可されている場合は StackOverflowError 例外がスローされます。動的に拡張できます (現在、ほとんどのすべての Java 仮想マシンは動的に拡張できますが、Java 仮想マシンの仕様では、固定長の仮想マシン スタックも許可されています)。十分なメモリを拡張できない場合は、OutOfMemoryError 例外がスローされます。

ネイティブ メソッド スタック

ネイティブ メソッド スタックと仮想マシン スタックによって実行される機能は非常に似ています。唯一の違いは、仮想マシン スタックが仮想マシン サービスの Java メソッド (つまり、バイトコード) を実行することです。ローカル メソッド スタックは、仮想マシンで使用されるネイティブ メソッドを提供します。仮想マシンの仕様では、ローカル メソッド スタック内のメソッドの言語、使用法、およびデータ構造が必須ではないため、特定の仮想マシンが自由に実装できます。一部の仮想マシン (Sun HotSpot 仮想マシンなど) では、ローカル メソッド スタックと仮想マシン スタックを 1 つに直接結合することもあります。仮想マシン スタックと同様に、ローカル メソッド スタック領域も StackOverflowError 例外と OutOfMemoryError 例外をスローします。

Java ヒープ

ほとんどのアプリケーションにとって、Java ヒープ (Java ヒープ) は、Java 仮想マシンによって管理される最大のメモリ部分です。 Java ヒープはすべてのスレッドによって共有されるメモリ領域であり、仮想マシンの起動時に作成されます。このメモリ領域の唯一の目的はオブジェクト インスタンスを保存することであり、ほとんどすべてのオブジェクト インスタンスがここにメモリを割り当てます。これは Java 仮想マシンの仕様で次のように説明されています。すべてのオブジェクト インスタンスと配列はヒープ上に配置する必要がありますが、JIT コンパイラの開発とエスケープ解析技術の段階的な成熟により、スタック上での配置とスカラー置換最適化技術が必要になります。これにより、いくつかの微妙な変更が発生し、ヒープ上に割り当てられたすべてのオブジェクトは徐々に「絶対的」ではなくなります。

ava ava ヒープは、ガベージ コレクターによって管理される主要な領域であるため、「GC ヒープ」 (ガベージ コレクション ヒープ、幸いなことに中国では「ガベージ ヒープ」と訳されません) と呼ばれることがよくあります。メモリのリサイクルの観点から見ると、カレント コレクタは基本的に世代別コレクション アルゴリズムを使用するため、Java ヒープは次のように細分化することもできます。新世代と旧世代には、Eden 空間、From Survivor 空間、To Survivor 空間などが含まれます。メモリ割り当ての観点から、スレッド共有 Java ヒープは、複数のスレッドプライベート割り当てバッファ (スレッド ローカル割り当てバッファ、TLAB) に分割される場合があります。ただし、分割方法に関係なく、どの領域にオブジェクト インスタンスが格納されても、メモリをより適切に再利用したり、メモリをより速く割り当てたりすることが目的です。この章では、メモリ領域の役割についてのみ説明します。Java ヒープ内の上記領域の割り当てと再利用の詳細については、次の章で説明します。

Java 仮想マシンの仕様によれば、Java ヒープは、ディスク領域と同様に、論理的に連続している限り、物理的に不連続なメモリ領域に存在することができます。実装する場合、固定サイズまたはスケーラブルとして実装できますが、現在の主流の仮想マシンはすべてスケーラブルとして実装されます (-Xmx および -Xms によって制御されます)。インスタンスの割り当てを完了するためのメモリがヒープ内になく、ヒープを拡張できなくなった場合、OutOfMemoryError 例外がスローされます。

メソッド領域

メソッド領域は、Javaヒープと同様に、各スレッドによって共有されるメモリ領域であり、クラス情報、定数静的変数、および、によってロードされた瞬間変数を格納するために使用されます。コンパイラによってコンパイルされたコードとその他のデータ。 Java 仮想マシンの仕様ではメソッド領域をヒープの論理部分として記述していますが、これには Non-Heap (非ヒープ) という別名があり、Java ヒープと区別するためのはずです。

Java 仮想マシンの仕様では、この領域に関して非常に緩やかな制限があり、Java ヒープのような連続したメモリを必要とせず、固定サイズまたは拡張性のオプションがあることに加えて、ガベージ コレクションを実装しないことも選択できます。この領域ではガベージコレクション動作は比較的まれですが、永続世代の名前のようにデータがメソッド領域に入って「永続的に」存在するわけではありません。この領域のメモリ リサイクルの対象は、主に定数プールのリサイクルと型のアンロードです。一般的に、この領域のリサイクルの「スコア」は比較的満足のいくものではなく、特に型のアンロードに関してはかなり厳しい条件です。リサイクルは確かに必要です。

Java 仮想マシンの仕様によれば、メソッド領域がメモリ割り当て要件を満たせない場合、OutOfMemoryError 例外がスローされます。

ランタイム定数プール

ランタイム定数プールはメソッド領域の一部です。

クラス クラスのバージョン、フィールド、メソッド、インターフェイス、その他の説明などの情報に加えて、コンパイル中に生成されるさまざまなリテラルやシンボル参照を保存するために使用される定数プール (定数プール テーブル) もあります。コンテンツは、クラスがロードされた後、メソッド領域のランタイム定数プールに保存されます。

Java仮想マシンでは、クラスファイル(当然定数プールも含む)の各部分のフォーマットに厳しい規定があり、各バイトがどのようなデータを格納するかは仕様要件を満たしている必要があります。仮想マシンによって承認、ロード、実行されます。ただし、ランタイム定数プールについては、Java 仮想マシンの仕様に詳細な要件はありません。さまざまなプロバイダーによって実装された仮想マシンが、独自のニーズに応じてこのメモリ領域を実装できます。ただし、一般に、クラス ファイルに記述されているシンボル参照の保存に加えて、変換された直接参照も実行時定数プールに保存されます。

クラス ファイル定数プールと比較したランタイム定数プールのもう 1 つの重要な特徴は、Java 言語では、定数、つまりクラス内の定数プールの内容のみが生成される必要がないことです。ファイルが事前設定されていないため、メソッド領域にランタイム定数プールを入力すると、ランタイム中に新しい定数をプールに入れることもできます。この機能は、

String クラスの intern() メソッドです。

ランタイム定数プールはメソッド領域の一部であるため、当然、メソッド領域のメモリによって制限され、定数プールがメモリに適用できなくなると、OutOfMemoryError 例外がスローされます。

ダイレクト メモリ

ダイレクト メモリ (ダイレクト メモリ) は、仮想マシンのランタイム データ領域の一部ではなく、Java 仮想マシン仕様で定義されているメモリ領域でもありませんが、メモリのこの部分も頻繁に使用されます。 OutOfMemoryError 例外が発生する可能性があります。

NIO (新しい入力/出力) クラスが JDK 1.4 に追加され、チャネルとバッファーに基づく I/O メソッドが導入されました。これにより、ネイティブ関数ライブラリを使用してオフヒープ メモリを直接割り当てることができます。このメモリへの参照として Java ヒープに格納されている DirectByteBuffer オブジェクトを通じて動作します。これにより、Java ヒープとネイティブ ヒープの間でのデータのコピーが回避されるため、一部のシナリオではパフォーマンスが大幅に向上します。

明らかに、ローカル ダイレクト メモリの割り当ては Java ヒープ サイズによって制限されませんが、メモリであるため、ローカル メモリの合計 (RAM と SWAP 領域またはページング ファイルを含む) のサイズに確実に影響されます。プロセッサのアドレス空間の制限。サーバー管理者が仮想マシンのパラメータを構成する場合、通常は実際のメモリに基づいて -Xmx およびその他のパラメータ情報を設定しますが、直接メモリは無視されることが多く、各メモリ領域の合計が物理メモリの制限 (物理メモリおよびオペレーティング システム レベルを含む) を超えます。制限))、動的拡張中に OutOfMemoryError 例外が発生します。

以上がJava メモリ領域とメモリ オーバーフロー例外の詳細な紹介の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。