1. Java仮想マシンのメモリ領域の概要 プログラムを作成するときに、OOM (メモリ不足) やメモリ リークなどの問題に遭遇することがよくあります。これらの問題を回避するには、まず JVM のメモリ分割について具体的に理解する必要があります。 JVM はメモリをメソッド領域、仮想マシン スタック、ローカル メソッド スタック、ヒープ、プログラム カウンターに分割します。 2. Java仮想マシンのランタイムデータ領域 2.1.ランタイムデータ領域の分割 2.1.1 ランタイムデータ領域図 2.1.2 ランタイムデータ領域には
2.2.方法領域 2.2.1 方法領域の概念 メソッド領域は静的領域とも呼ばれ、ロードされたクラスの基本情報、定数、静的変数などが格納されます。スレッド間で共有される領域です。 たとえば、Java コードを記述する場合、各スレッドは同じクラスの静的変数オブジェクトにアクセスできます。リフレクション機構を使用しているため、仮想マシンがどのクラス情報が使用されなくなったかを推測することが難しく、この領域を再利用することが困難です。 2.2.1.1 静的ブロックと非静的ブロックの違いは何ですか?
2.2.2 方法領域の特徴 スレッド間の共有領域 2.2.3 メソッド領域における例外 この領域は主に、一定のプール回復に使用されます。 JDK1.7 では定数プールがヒープへ転送されたことは注目に値します。同様に、メソッド領域がメモリ割り当て要件を満たせない場合は、OutOfMemoryError がスローされます。メソッド領域のメモリオーバーフローが発生します。これは JDK 1.6 以前のバージョンでのみ発生することに注意してください。理由は後ほど説明します。実行前に、仮想マシン パラメータ -XXpermSize および -XX:MaxPermSize を設定して、メソッド領域のサイズを制限できます。 コードリストは次のとおりです。
異常な結果を出力します:
注意: インターネット上で例を実行すると、java.lang.OutOfMemoryError: PermGen スペース例外がスローされます。 2.2.3.1 Stringのintern()関数について intern() の役割: 現在の文字列が定数プールに存在しない場合は、定数プールに配置されます。 上記のコードは定数プールに文字列を継続的に追加するため、最終的にはメモリ不足に陥り、メソッド領域で OOM が発生します。上記のコードを JDK1.6 より前に実行する必要がある理由を説明してください。前述したように、JDK1.7 以降では定数プールがヒープ領域に配置され、intern() 関数の機能が変更されます。コードリストは次のとおりです。
シナリオ jdk6 では、出力は次のようになります。
シナリオ jdk7 では、出力は次のようになります。
なぜ? その理由は、JDK1.6 では、intern() メソッドが *** が遭遇した文字列インスタンスを定数プールにコピーし、定数プール内の文字列への参照を返すためです。 StringBuilder によって作成された文字列インスタンスはヒープ上にあるため、同じ参照ではないはずであり、false を返します。 JDK1.7 では、 intern メソッドはインスタンスをコピーしなくなり、最初のインスタンスへの参照のみが定数プールに保存されます。したがって、intern() によって返される参照は、StringBuilder によって作成された文字列インスタンスと同じです。 str2 の比較で false が返されるのはなぜですか?これは、クラスが JVM 内で内部的にロードされるときに、文字列「java」がすでに存在し、「*** が出現する」原則を満たしていないため、false が返されるためです。 2.2.4 メソッド領域の機能 メソッド領域にはクラス情報、定数、静的変数などが格納される。スレッドごとに共有される領域である。 2.2.5 メソッド領域の使用 仮想マシンパラメータ -XXpermSize と -XX:MaxPermSize を設定してメソッド領域のサイズを制限します。 2.2.6 メソッド領域の使用シナリオ 2.3. VMスタック 2.3.1 仮想マシンスタックの概念 仮想マシン スタックは、Java メソッド実行のメモリ モデルを記述します。 各メソッドが実行されると、同時にスタック フレーム (StackFrame) が作成され、ローカル変数テーブル、操作スタック、動的リンク、メソッド終了などの情報が格納されます。各メソッドが呼び出されてから実行されるまでのプロセスは、スタック フレームが仮想マシン スタックにプッシュされてからポップされるまでのプロセスに対応します。 2.3.1.1 ローカル変数テーブル ローカル変数テーブルには、コンパイラによって制御されるさまざまな基本データ型 (boolean、byte、char、short、int、float、long、double)、オブジェクト参照 (Object reference)、およびバイトコード命令アドレス (returnAddress 型) が格納されます。 2.3.1.1 操作スタック オペランド スタックは、操作スタックとも呼ばれ、後入れ先出し (LIFO) スタックです。ローカル変数テーブルと同様に、オペランド スタックの最大深度も、コンパイル時に Code 属性の max_stacks データ項目に書き込まれます。オペランド スタックの各要素は、long や double を含む任意の Java データ型にすることができます。 32 ビット データ型が占有するスタック容量は 1 で、64 ビット データ型が占有するスタック容量は 2 です。メソッド実行中は、オペランド スタックの深さが max_stacks データ項目に設定された最大値を超えることはありません。 メソッドの実行が開始されると、このメソッドのオペランド スタックは空になります。メソッドの実行中、さまざまなバイトコード命令によってオペランド スタックの内容が書き込まれ、抽出されます。これはプッシュ操作とポップ操作です。たとえば、算術演算を実行する場合はオペランド スタックを介して実行され、他のメソッドを呼び出す場合は、パラメーターがオペランド スタックを介して渡されます。 たとえば、整数加算バイトコード命令 iadd では、オペランド スタックの最上部に最も近い 2 つの要素に 2 つの int 値が格納されている必要があります。この命令が実行されると、2 つの int 値が加算され、結果がスタックにプッシュされます。 オペランド スタック内の要素のデータ型は、バイトコード命令のシーケンスと厳密に一致する必要があります。プログラム コードをコンパイルするときに、コンパイラはこれを厳密に確認する必要があり、クラス検証フェーズのデータ フロー分析で再度検証する必要があります。上記の iadd 命令を例に挙げます。この命令は整数加算に使用されます。実行時に、スタックの最上部に最も近い 2 つの要素のデータ型は int である必要があります。 iadd コマンドを使用して long と float を追加することはできません。 2.3.1.1 動的リンク 各スタック フレームには、ランタイム定数プール内でスタック フレームが属するメソッドへの参照が含まれています。この参照は、メソッド呼び出し中の動的接続をサポートするために保持されます。クラス ファイルの定数プールには多数のシンボリック参照が含まれており、バイトコード内のメソッド呼び出し命令は、定数プール内のメソッドを指すシンボリック参照をパラメーターとして受け取ることがわかります。これらのシンボリック参照の一部は、クラスのロードフェーズ中または初めて使用されるときに直接参照に変換されます。この変換は静的解像度と呼ばれます。他の部分は実行ごとに直接参照に変換されます。これを動的リンクと呼びます。 2.3.2 仮想マシンスタックの特徴
2.3.3 仮想マシンスタックの例外 2.3.3.1 スタックオーバーフローエラー 現在のスレッドによって要求されたスタックの深さが仮想マシンによって許可された深さよりも大きい場合、この例外がスローされます。たとえば、関数が繰り返し再帰的に呼び出されると、最終的にはスタック オーバーフロー エラーが発生します。 コードリストは次のとおりです。
異常な出力結果:
シングルスレッド環境では、スタック フレームが大きすぎるか、仮想マシンのスタック容量が小さすぎるためにメモリを割り当てることができない場合、仮想マシンは StackOverflowError 例外をスローすることに注意してください。 2.3.3.2 1つはOOM例外です 仮想マシン スタックが動的拡張をサポートしている場合、十分なメモリを適用できない場合は OOM 例外がスローされます。コードリストは次のとおりです。
この例は注意して使用してください... この例では、スレッドを継続的に作成することで、メモリ オーバーフロー例外を生成します。ただし、このようにして生成されるメモリ オーバーフロー例外は、スタック スペースが十分に大きいかどうかとは関係ありません。あるいは、より正確に言えば、この場合、各スレッドのスタックに割り当てられるメモリが大きいほど、メモリ オーバーフロー例外が発生しやすくなります。その理由は、オペレーティング システムによって各プロセスに割り当てられるメモリが限られているためです。たとえば、32 ビット Windows では 2 GB に制限されています。 。 2.3.4 仮想マシンスタックの役割 ローカル変数、操作スタック、動的リンク、メソッド終了を格納するために使用されます 2.3.5 仮想マシンスタックの適用 32 ビット jvm の場合、デフォルトのサイズは 256 KB で、64 ビット jvm の場合、デフォルトのサイズは 512 KB です。仮想マシンスタックの最大値は -Xss で設定できます。ただし、設定値が大きすぎると、作成できるスレッドの数に影響します。 2.3.6 仮想マシンスタックの使用シナリオ 2.4.ネイティブメソッドスタック 2.4.1 ネイティブメソッドスタックの概念 ローカル メソッド スタックと仮想マシン スタックが果たす役割は非常に似ています。それらの違いは、仮想マシン スタックは Java コード メソッドを実行するために使用され、ローカル メソッド スタックはネイティブ メソッドに使用されることです。仮想マシン スタックと同様に、ローカル メソッド スタックも StackOverflowError および OutOfMemoryError 例外をスローする可能性があります。 2.4.2 ローカルメソッドスタックの特性
2.4.3 ローカルメソッドスタック内の例外 仮想マシン スタックと同様に、ローカル メソッド スタックも StackOverflowError および OutOfMemoryError 例外をスローする可能性があります。 2.4.4 ローカルメソッドスタックの役割 2.4.4.1 外部Java環境との相互作用 場合によっては、Java アプリケーションは Java 外部の環境と対話する必要があります。これがネイティブ メソッドが存在する主な理由です。Java がオペレーティング システムやハードウェアなどの基盤となるシステムと情報を交換する必要がある状況が考えられます。ローカル メソッドはまさにそのような通信メカニズムです。非常に簡潔なインターフェイスが提供され、Java アプリケーション外部の面倒な詳細を理解する必要がありません。 2.4.4.2 オペレーティングシステムとの相互作用 JVM は Java 言語自体とランタイム ライブラリをサポートします。これは、Java プログラムが依存するプラットフォームです。これは、インタープリタ (バイトコードを解釈する) とネイティブ コードに接続されたいくつかのライブラリで構成されます。しかし、結局のところ完全なシステムではなく、基盤となるシステムのサポートに依存することがよくあります。これらの基盤となるシステムは、多くの場合、強力なオペレーティング システムです。ネイティブ メソッドを使用すると、Java を使用して JRE と基盤となるシステム間の相互作用を実装できます。 JVM の一部も C で書かれています。さらに、Java 言語自体にカプセル化されていないオペレーティング システムの機能を使用する場合は、ネイティブ メソッドも使用する必要があります。 Sun の Java Sun のインタープリタは C で実装されており、通常の C と同様に外部の世界と対話します。JRE は主に Java で実装されており、いくつかのネイティブ メソッドを通じて外部の世界と対話します。例: クラス java.lang.Thread setPriority() メソッドは Java で実装されていますが、クラス内のネイティブ メソッド setPriority0() を呼び出します。このネイティブ メソッドは C で実装され、JVM に埋め込まれています。 Windows 95 では、このネイティブ メソッドは最終的に Win32 SetPriority() API を呼び出します。これは、JVM によって直接提供されるネイティブ メソッドの特定の実装です。多くの場合、ネイティブ メソッドは外部のダイナミック リンク ライブラリによって提供され、JVM によって呼び出されます。 2.4.5 ローカルメソッドスタックの使用 2.4.6 ローカルメソッドスタックの使用シナリオ
2.5. Javaヒープ 2.5.1 Javaヒープの概念 Java ヒープは、仮想マシン内の最後のメモリ部分であると言えます。これはすべてのスレッドで共有されるメモリ領域であり、ほぼすべてのインスタンス オブジェクトがこの領域に格納されます。もちろん、JIT コンパイラの開発により、ヒープ上に割り当てられたすべてのオブジェクトの「絶対性」は徐々に低下しました。 Java ヒープは、ガベージ コレクターによって管理される主要な領域です。現在のコレクターは基本的に世代別コレクション アルゴリズムを使用するため、すべての Java ヒープは新しい世代と古い世代に分けられます。詳細には、新世代は次のように分類されます。
Java 仮想マシン仕様によると: Java ヒープは、ディスク領域と同様に、論理的に連続している限り、物理的に不連続なメモリ領域に配置できます。実装する際は、固定サイズでも拡張可能でも構いませんが、現在主流の仮想マシンはすべて拡張可能な形で実装されています。 2.5.2 Javaヒープの特性 仮想マシンの起動時に作成されるスレッド間の共有領域 これは仮想マシン内で最大のメモリです。ほぼすべてのインスタンス オブジェクトはこの領域に格納されます。 2.5.3 Javaヒープ例外 ヒープを拡張できなくなると、OutOfMemoryError 例外がスローされます。 2.5.4 Javaヒープの役割 唯一の目的はオブジェクトインスタンスを保存することです。ほぼすべてのオブジェクト インスタンスには、Java ヒープ内のメモリが割り当てられます。 2.5.5 Javaヒープの使用 -Xmx と -Xms によって制御されます 2.5.6 Javaヒープ使用シナリオ 2.6.プログラムカウンタレジスタ 2.6.1 プログラム計算機の概念 PC レジスタと同様に、プログラム カウンターはスレッド専用の領域であり、各スレッドには独自のプログラム カウンターがあります。これは、現在のスレッドによって実行されたバイトコードの行番号インジケーターと考えることができます。 2.6.2 プログラム電卓の機能
2.6.3 プログラムされた計算機の異常 このメモリ領域は、Java 仮想マシン仕様で OOM (OutOfMemoryError) 状況が指定されていない唯一の領域です。 2.6.4 プログラム電卓の機能
2.6.5 プログラム電卓の応用 -Xmx と -Xms によって制御されます 2.6.6 プログラム計算機の使用シナリオ 2.7.直接記憶 2.7.1 直接記憶の概念 2.7.1.1 直接記憶と非直接記憶とは何ですか? 公式ドキュメントによると: バイト バッファは直接バッファまたは非直接バッファのいずれかです。直接バイト バッファが指定されると、Java 仮想マシンは、そのバッファ上でネイティブ I/O 操作を直接実行するために最善を尽くします。つまり、基盤となるオペレーティング システムのネイティブ I/O 操作のいずれかを呼び出す前 (または呼び出し後) に、バッファーの内容を中間バッファーに (または中間バッファーから) コピーすることを回避しようとします。 バイト バッファには 2 つのタイプがあります。1 つは直接メモリ (つまり、非ヒープ メモリ) に基づいています。もう 1 つは非直接メモリ (つまり、ヒープ メモリ) です。 ダイレクト メモリは、仮想マシンのランタイム データ領域の一部でも、Java 仮想マシン仕様で定義されているメモリ領域でもありません。ただし、このメモリ部分は頻繁に使用されるため、OutOfMemoryError 例外が発生する可能性もあります。 ダイレクト メモリの場合、JVM はローカル システムの IO 操作に直接作用するため、IO 操作のパフォーマンスが向上します。ヒープ メモリを IO 操作に使用する場合は、まず直接メモリにコピーされ、次にローカル IO を使用して処理されます。 データフローの観点から見ると、非直接メモリのアクション チェーンは次のようになります。 ローカル IO --> 直接メモリ --> 間接メモリ --> 直接メモリ --> ローカル IO 直接記憶の役割連鎖: ローカル IO --> ダイレクト メモリ --> ローカル IO 当然のことながら、ネットワーク経由で大量のデータを送信するなどの IO 処理を行う場合は、ダイレクト メモリの方が効率的です。 このクラスの allocateDirect ファクトリ メソッドを呼び出すことによって、直接バイト バッファを作成できます。このメソッドによって返されるバッファは、通常、非直接バッファよりも割り当ておよび割り当て解除のコストがいくらか高くなります。ダイレクト バッファーの内容は、通常のガベージ コレクションされたヒープの外部に存在する可能性があるため、アプリケーションのメモリ フットプリントへの影響は明らかではない可能性があります。したがって、直接バッファーは、主として、基盤となるシステムのネイティブ I/O 操作の対象となる、大規模で長期間有効なバッファーに割り当てることをお勧めします。一般的に、直接バッファを割り当てるのは、プログラムのパフォーマンスに目に見えるほどの向上が見込まれる場合のみにするのが最善です。 ただし、ダイレクト メモリは allocateDirect を使用して作成されるため、通常のヒープ メモリに適用するよりも高いパフォーマンスを消費します。ただし、アプリケーションのヒープ メモリを占有することはありません。したがって、キャッシュするデータが大量にあり、そのライフ サイクルが比較的長い場合は、ダイレクト メモリを使用するのが適切な選択です。ただし、この選択によってパフォーマンスが大幅に向上しない場合は、ヒープ メモリを使用することをお勧めします。 JDK1.4 の NIO では、ByteBuffer に次のメソッドがあります。
さらに、マシンの合計メモリのサイズ (RAM と SWAP 領域またはページング ファイルを含む) とプロセッサのアドレス空間の制限によって直接制限されます。 サーバー管理者が仮想マシンのパラメータを構成する場合、通常は実際のメモリに基づいて -Xmx などのパラメータ情報を設定しますが、直接メモリを無視することが多く、さまざまなメモリ領域の合計が物理メモリ制限 (物理レベルとオペレーティング システム レベルの制限を含む) を超え、動的拡張中に OutOfMemoryError 例外が発生します。 2.7.2 直接記憶の特性
2.7.3 直接メモリ例外 動的拡張中にOutOfMemoryError例外が発生する 2.7.4 直接記憶の役割 チャネルとバッファの I/O メソッドに基づいて、ネイティブ関数ライブラリを使用してオフヒープ メモリを直接割り当て、Java ヒープに格納されている DirectByteBuffer オブジェクトを介してこのメモリへの参照として操作できます。これにより、Java ヒープとネイティブ ヒープ間でのデータのやり取りが回避され、一部のシナリオでパフォーマンスが大幅に向上します。 2.7.5 直接メモリの使用 XX:最大ダイレクトメモリサイズ=10M 2.7.6 直接メモリ使用シナリオ たとえば、ネットワーク経由で大量のデータを送信するなどの IO 処理では、ダイレクト メモリの方が効率的です。 この記事はjdk1.6、1.7に基づいています |
<<: ハイブリッドの花が満開となり、クラウド コンピューティングは新たな時代を迎えているのでしょうか?
>>: オラクル、ハイアールの海外サービス顧客体験を全面的にアップグレード
創業11年のVPS/サーバー販売業者kvmlaでは、現在、香港VPS、日本VPS、シンガポールVPS...
著者: ラク・シヴァ編集:ノエ現在では、ほぼすべてのアプリケーションをコンテナにパッケージ化して実行...
鶏が先か卵が先かは未解決の問題のままである。ウェブサイトのプロモーション活動では、外部リンクを構築す...
Baidu を使用する際に、Baidu スナップショット後の Baidu 共有データに注目したことが...
Bandwagonhost VPS に関する最新ニュース: 購入した Bandwagonhost V...
2019 年を通じて、テクノロジーは企業やコミュニティに変革的な影響を及ぼし続けました。 5G の最...
Cartika は長い歴史を持つホスティング ビジネスです。Host Cat は少なくとも 2 年間...
SEO業界の競争はますます激しくなっています。SEOを始めるのは難しくありません。ウェブサイト編集者...
最近、Googleの親会社であるAlphabetが初めてGoogleのクラウドコンピューティング事業...
計画からフロントエンドとバックエンドの開発、そしてテストとリリースまで、4か月かかりました。5173...
Zenlayerは、南米ブラジルに独自のブラジルデータセンターを開設しました。データセンターはブラジ...
[51CTO.com からのオリジナル記事] 「Pivotal は非常に控えめな会社であり、真にハー...
脅威アクターは、富士通のSaaS(Software as a Service)プラットフォームを侵害...
LinodeはAkamaiに買収されて以降、インドネシアのクラウドサーバー事業を運営するため、南アジ...
少なくともほとんどの Web サイトについては、これら 3 つの単語の順序も考慮しました。内容は心で...