JVMランタイムデータ領域、メソッド領域、永続的世代、メタスペースについては本書では明確に説明されていない

JVMランタイムデータ領域、メソッド領域、永続的世代、メタスペースについては本書では明確に説明されていない

この記事はWeChatの公開アカウント「Flying Veal」から転載したもので、著者はVealです。記事を転載する場合は飛天牛肉公式アカウントまでご連絡ください。

データベースシリーズの執筆はほぼ終わりました。冬休み中に JVM についての読み終えて、学校が始まったらフレームワークについて読み、秋の採用に備えて一般的な知識を覚えるつもりです。さっそく、JVM の最初の知識ポイントは、Java プログラムの実行時のデータ領域の分割に当てられる必要があります。

いつも通り、朗読バージョンは記事の最後にあります。クリックすると原文を読むことができ、私がまとめた大手企業の面接質問に直接移動します

JVM ランタイム データ領域の概要

Java プログラムの実行中 (ランタイムと呼ばれる)、JVM は管理するメモリを複数の異なるデータ領域に分割します。これらのエリアには独自の用途があり、作成および破壊のタイミングも異なります。一部の領域は仮想マシン プロセスが開始されている限り存在しますが、一部の領域はユーザー スレッドの開始と終了に応じて作成および破棄されます。

Java 仮想マシン仕様によれば、Java 仮想マシンによって管理されるメモリには、次の図に示すように、次のランタイム データ領域が含まれます。

図からわかるように、スレッド間で共有される領域はメソッド領域とヒープであり、スレッド間で分離される領域(スレッドプライベート)は仮想マシンスタック、ローカルメソッドスタック、プログラムカウンタです。

スレッド共有とスレッドプライベートの意味を簡単に説明します。

  • いわゆるスレッド プライバシーとは、簡単に言えば、各スレッドが独自のスペースを作成し、各スレッド間のプライベート スペースは互いに影響を及ぼさず、独立して保存されることを意味します。たとえば、プログラム カウンターはスレッド専用です。各スレッドには独自のプログラム カウンターがあり、互いに干渉しません。
  • スレッド共有については特に言うことはありません。簡単に言えば、誰でもアクセスでき、すべてのスレッドが保存されたデータにアクセスできるパブリックな場所として理解できます。

これらのデータ領域を個別に説明しましょう。

スレッドプライベート: プログラムカウンタレジスタ

プログラム カウンターは、現在のスレッドによって実行されたバイトコードの行番号インジケーターと考えることができる小さなメモリです。バイトコード インタープリタが動作する場合、このカウンタの値を変更して、次に実行するバイトコード命令を選択します。分岐、ループ、ジャンプ、例外処理、スレッド回復などの基本機能はすべて、このカウンターに依存して完了します。

Java 仮想マシンのマルチスレッドは、スレッドを順番に切り替えてプロセッサ実行時間を割り当てることによって実装されるため、特定の瞬間にプロセッサ (マルチコア プロセッサの場合はコア) は 1 つのスレッド内の命令のみを実行します。

そのため、スレッド切り替え後に正しい実行位置に戻すためには、各スレッドが独立したプログラム カウンターを持つ必要があり、スレッド間のカウンターは互いに影響を及ぼしません。

では、プログラム カウンターには何が保存されるのでしょうか?

  • スレッドが Java メソッドを実行している場合、このカウンターは実行中の仮想マシン バイトコード命令のアドレスを記録します。
  • ネイティブ メソッドが実行されている場合、カウンター値は空 (未定義) になります。ネイティブ メソッドとは何かについては、ネイティブ メソッド スタックのセクションで詳しく説明します。

注記!このメモリ領域は、Java 仮想マシン仕様で OutOfMemoryError 状況が指定されていない唯一の領域です。この質問は比較的よく聞かれる面接の質問です。

スレッドプライベート: Java 仮想マシンスタック

仮想マシン スタックは、実際にはスタック フレームが 1 つずつ構成されています。スタック フレームは、Java メソッド実行のメモリ モデルを記述します。つまり、各メソッドは実行時にスタック フレームを同期的に作成し、ローカル変数テーブル、オペランド スタック、動的リンク、メソッドの戻りアドレスなどの情報を格納するために使用されます。

各メソッドが呼び出されてから実行されるまでのプロセスは、スタック フレームが仮想マシン スタックにプッシュされてからスタックからポップされるまでのプロセスに対応します。

このうち、ローカル変数テーブルには次の 3 種類のデータが格納されます。

  • コンパイル時に認識される Java 仮想マシンのさまざまな基本データ型: boolean、byte、char、short、int、float、long、double
  • オブジェクト参照、参照型: オブジェクト自体と同等ではありません。これは、オブジェクトの開始アドレスを指す参照ポインターである場合もあれば、オブジェクトまたはこのオブジェクトに関連する他の場所を表すハンドルを指す場合もあります。 (参照型に関しては、ハンドルを使ってアクセスする方法と、直接ポインタを使ってアクセスする方法の2つの方法があります。これについては、以降の記事で詳しく説明します)
  • returnAddress型: バイトコード命令のアドレスを指す

ローカル変数テーブル内のこれらのデータ型の記憶領域は、ローカル変数スロットによって表されます。つまり、ローカル変数テーブルの基本記憶単位はスロットです。 JVM は各スロットにアクセス インデックスを割り当て、このインデックスを通じてローカル変数テーブルに格納された値に正常にアクセスできます。

このうち、64 ビットの long および double データ型は 2 つのスロットを占有し、残りのデータ型は 32 ビットで 1 つのスロットのみを占有します。

Java 仮想マシン仕様では、仮想マシン スタック メモリ領域に対して 2 種類の例外が指定されています。

  • スレッドが要求したスタックの深さが仮想マシンで許可されている深さより大きい場合、StackOverflowError例外がスローされます(スタックオーバーフロー)
  • 使用されている JVM が仮想マシン スタック容量の動的な拡張をサポートしている場合、スタック拡張中にメモリ不足が要求されると、OutOfMemoryError 例外がスローされます。

スレッドプライベート: ネイティブメソッドスタック

ローカル メソッド スタックは、基本的に前述の仮想マシン スタックと同じ機能を持ちます。唯一の違いは、ローカル メソッド スタックは仮想マシンが使用するネイティブ メソッドを提供するのに対し、仮想マシン スタックは仮想マシンが Java メソッド (つまり、バイトコード) を実行するために提供されることです。

ここではネイティブ方式の概念について説明します。実際、この概念は Java だけでなく、多くの言語にも存在します。

「ネイティブ メソッドとは、Java 以外のコードによって実装が提供される Java メソッドです。」

つまり、ネイティブ メソッドは実際にはインターフェイスですが、その特定の実装は C や C++ などの Java 以外の言語で外部的に記述されます。 Java は JNI を介してネイティブ メソッドを呼び出し、ネイティブ メソッドはライブラリ ファイル (WINDOWS プラットフォームでは DLL ファイル、UNIX マシンでは SO ファイル) の形式で保存されます。

したがって、同じネイティブ メソッドが異なる仮想マシンによって呼び出された場合、結果と実行効率が異なる場合があります。これは、仮想マシンによって、Object クラスの hashCode メソッドなどの特定のネイティブ メソッドの独自の実装があるためです。

では、なぜネイティブ メソッドが必要なのでしょうか?

主な理由は、Java は使いやすいものの、一部のレベルのタスクは Java で実装するのが容易ではない、またはプログラム効率に対する要件が比較的高い場合、Java 言語が最適な選択ではない可能性があることです。したがって、ネイティブ メソッドにより、Java プログラムは Java ランタイムの境界を超越し、JVM を効果的に拡張できるようになります。

仮想マシン スタックと同様に、ローカル メソッド スタックでも、スタック深度がオーバーフローしたり、スタック拡張に失敗したりすると、StackOverflowError および OutOfMemoryError 例外がスローされます。

スレッド共有: ヒープ

Java ヒープは、仮想マシンによって管理されるメモリの最大の領域です。ヒープはすべてのスレッドで共有されるメモリ領域であり、仮想マシンの起動時に作成されます。このメモリ領域の唯一の目的はオブジェクト インスタンスを格納することであり、「ほぼ」すべてのオブジェクト インスタンスがここにメモリを割り当てます。

注記!ここでは「ほぼ」という言葉を使います。技術の発展により、エスケープ技術など、すべてのオブジェクト インスタンスが実際にヒープに割り当てられるわけではありません。これについては次の記事で説明しますね〜

ヒープはガベージコレクターによって管理されるメモリ領域であるため、一部の資料では「GC ヒープ」(Garbage Collected Heap) とも呼ばれます。

新世代、旧世代、永久世代、エデン空間、From Survivor 空間、To Survivor 空間など、さまざまな用語を聞いたことがあるかもしれません。これらの領域区分は、一部のガベージ コレクターの共通の特性または設計スタイルにすぎないことに注意してください。これらは、特定の Java 仮想マシンの固有のメモリ レイアウトや、Java 仮想マシン仕様における Java ヒープのより詳細な分割ではなく、この世代別設計を通じてメモリ回復を改善したり、メモリ割り当てを高速化したりするためのものです。

Java 仮想マシン仕様によれば、Java ヒープは物理的には不連続なメモリ空間に存在する可能性がありますが、論理的には連続していると見なされます。これは、ディスク領域を使用してファイルを保存する場合と同様であり、各ファイルを継続的に保存する必要はありません。ただし、大きなオブジェクト (通常は配列オブジェクト) の場合、ほとんどの仮想マシン実装では、単純さと効率的なストレージのために連続したメモリ領域が必要になる可能性があります。

Java ヒープは、固定サイズまたは拡張可能なヒープとして実装できます。現在主流のJava仮想マシンはすべて拡張可能として実装されています(パラメータ-Xmxと-Xmsで設定)

オブジェクト インスタンスの割り当てを完了するためのメモリがヒープ内に存在せず、ヒープをそれ以上拡張できない場合、JVM は OutOfMemoryError 例外をスローします。

スレッド共有: メソッド領域

メソッド領域は、仮想マシンがクラスのロードを完了した後、型情報、定数、静的変数、ジャストインタイムコンパイラによってコンパイルされたコードキャッシュ、およびクラスに関連するその他のデータを格納する領域として簡単に理解できます。

実行時定数プール、フィールドとメソッドのデータ、クラスとインスタンスの初期化やインターフェースの初期化で使用される特殊なメソッドを含むメソッドとコンストラクターのコードなどのクラスごとの構造を格納します。

以下に簡単な例を示します。

メソッド領域自体は実は簡単に理解できるのですが、「Java 仮想マシン仕様」/「Java 仮想マシンの詳細な理解」に記載されている「メソッド領域はヒープ内の論理的な部分である」という文章が、長い間私を混乱させました。

私の理解に基づいて説明させてください。この「メソッド領域はヒープの論理的な一部である」はJDK 8以前には適用されるが、JDK 8には適用されないと思う。

まず、JDK 8 以前の状況を見てみましょう。

JDK 8 より前では、ヒープとメソッド領域は実際に接続されていた、つまり、メソッド領域はヒープの一部であったことがわかります。

ただし、メソッド領域に格納されるものは少し特殊です。過去には、カスタム クラス ローダーが一般的に使用されていなかったため、クラスはほぼ「静的」であり、アンロードされてリサイクルされることはほとんどなかったため、クラスは「永続的」であると見なすこともできました (これが永続世代の意味です)。また、クラスは JVM 実装の一部であり、プログラムによって作成されるものではないため、ヒープと区別するために、クラス情報を格納するための「メソッド領域」という名前が付けられています。メソッド領域を「非ヒープ」と呼ぶ人もいます。

メソッド領域と非ヒープ領域はどちらも単なる論理的な概念であることに注意してください。 JDK 8 より前では、具体的な実装方法は永続的な生成でした。

永続世代は HotSpot 仮想マシンによって実装されますが、BEA JRockit、IBM J9 などの他の仮想マシン実装では永続世代の概念はありません。

永続世代は連続したメモリ空間です。 JVM を起動する前に -XX:MaxPermSize の値を設定することで、永続世代のサイズを制御できます。 32 ビット マシンのデフォルトの永続世代サイズは 64 MB で、64 ビット マシンの場合は 85 MB です。

永久世代のガベージ コレクションと古い世代のガベージ コレクションは結合されています。いずれかの領域がいっぱいになると、両方の領域でガベージ コレクションが実行されます。

明らかに、この設計は良いアイデアではありません。?XX:MaxPermSize を通じて永続世代のサイズを設定できるためです。クラスのメタデータが設定サイズを超えると、プログラムはメモリ不足になり、メモリ オーバーフロー エラー (java.lang.OutOfMemoryError: PermGen space) が発生します。

また、永久世代によって仮想マシンごとにパフォーマンスが異なるメソッドもごくわずかです (たとえば、String の intern() メソッドは、操作中に文字列定数プールに文字列を手動で追加できます。JDK1.7 より前の HotSpot 仮想マシンでは、文字列定数プールは永久世代に格納されます)。

それでは、HotSpots が JDK 8 で permanent 世代を放棄し、メタスペースを使用してメソッド領域を実装した 2 つの主な理由をまとめてみましょう。

  • 永久世代のガベージ コレクションと古い世代のガベージ コレクションは結合されているため、いずれかの領域がいっぱいになると、両方の領域がガベージ コレクションされ、OOM の可能性が高まります。
  • String の intern() メソッドなどのいくつかのメソッドは、永続的な生成のために、異なる仮想マシンで異なる動作をする可能性があり、これはコードの移行には役立ちません。

では、Metaspace とは一体何なのでしょうか。また、Metaspace はメソッド領域とどう違うのでしょうか。

メタスペースと永続世代の最大の違いは、メタスペースはヒープと連続しなくなり、ネイティブ メモリ内に存在することです。

ランタイム データ領域の比較を以下に示します。

メタスペースはローカル メモリ内に存在するため、ローカル メモリが十分にある限り OOM は発生せず、永続世代に java.lang.OutOfMemoryError: PermGenspace は発生しません。

ランタイム定数プール

ランタイム定数プールはメソッド領域の一部です。上で述べたように、メソッド領域にはクラス情報が含まれています。クラス情報を記述する Class ファイルには、クラス バージョン、フィールド、メソッド、インターフェイスなどの記述情報に加えて、さまざまなリテラル (リテラルは、テキスト文字列、final として宣言された定数値など、Java 言語レベルでの定数の概念に相当します) やコンパイル中に生成されるシンボル参照を格納するために使用される定数プール テーブルも含まれています。一部の記事では、クラス定数プール テーブルを静的定数プールと呼んでいます。

それらはすべて定数プールです。定数プール テーブルとランタイム定数プールの関係は何ですか?ランタイム定数プールの目的は何ですか?

ランタイム定数プールは、実行時にクラス定数プール テーブル内のシンボリック参照を直接参照に解決できます。簡単に言えば、クラス定数プール テーブルは一連のインデックスに相当します。ランタイム定数プールは、これらのインデックスを使用して、対応するメソッドまたはフィールドの型情報、名前、および記述子情報を検索します。

なぜ定数プールが必要なのでしょうか?主にシステムパフォーマンスに影響を与えるオブジェクトの頻繁な作成と破棄を回避し、オブジェクトの共有を実現します。文字列定数プールを例に挙げます。文字列は Java のクラスであるため、他のオブジェクトの割り当てと同様に、時間と領域のコストが高くなります。最も基本的かつ一般的に使用されるデータ型であるため、大量の文字列を頻繁に作成すると、プログラムのパフォーマンスに大きな影響を与えます。このため、JVM は、パフォーマンスを向上させ、メモリのオーバーヘッドを削減するために、文字列定数をインスタンス化するときにいくつかの最適化を行います。

  • 文字列定数プール文字列プールは文字列用に開かれ、キャッシュ領域として理解できます。
  • 文字列定数を作成するときは、まず文字列が文字列定数プールに存在するかどうかを確認します。
  • 文字列が文字列定数プールに存在する場合、再インスタンス化なしで参照インスタンスが直接返されます。存在しない場合は、文字列がインスタンス化され、プールに配置されます。

JDK 1.7 の前後では文字列定数プールの場所が変更されていることに注意してください。次の表を参照してください。

最後に、この質問の暗唱バージョンを以下に示します。

インタビュアー: JVMランタイムデータ領域について教えてください

Veal: Java プログラムの実行中、JVM は管理するメモリを複数の異なるデータ領域に分割します。スレッドによって共有される領域はメソッド領域とヒープであり、スレッドにプライベートな領域は仮想マシン スタック、ローカル メソッド スタック、およびプログラム カウンターです。

いわゆるスレッド プライバシーとは、各スレッドが独自のスペースを作成することを意味します。各スレッド間のプライベートスペースは互いに影響を及ぼさず、独立して保存されます。

まず、3 つのスレッドプライベート領域について説明します。

プログラム カウンター: プログラム カウンターは、現在のスレッドによって実行されたバイトコードの行番号インジケーターと見なすことができる、小さなメモリ空間です。バイトコード インタープリタが動作する場合、このカウンタの値を変更して、次に実行するバイトコード命令を選択します。分岐、ループ、ジャンプ、例外処理、スレッド回復などの基本機能はすべて、このカウンターに依存して完了します。

このメモリ領域は、Java 仮想マシン仕様で OutOfMemoryError 条件が指定されていない唯一の領域です。

仮想マシン スタック: 仮想マシン スタックは、実際にはスタック フレームが 1 つずつ構成されています。スタック フレームは、Java メソッド実行のメモリ モデルを記述します。つまり、各メソッドは実行時にスタック フレームを同期的に作成し、ローカル変数テーブル、オペランド スタック、ダイナミック リンク、メソッドの戻りアドレスなどの情報を格納するために使用されます。各メソッドが呼び出されてから実行されるまでのプロセスは、スタック フレームが仮想マシン スタックにプッシュされてからスタックからポップされるまでのプロセスに対応します。

仮想マシン スタックのメモリ領域に 2 つの異常状態があります。

  • スレッドが要求したスタックの深さが仮想マシンで許可されている深さより大きい場合、StackOverflowError例外がスローされます(スタックオーバーフロー)
  • 使用されている JVM が仮想マシン スタック容量の動的な拡張をサポートしている場合、スタック拡張中にメモリ不足が要求されると、OutOfMemoryError 例外がスローされます。

ローカル メソッド スタック: ローカル メソッド スタックと仮想マシン スタックは基本的に同じ機能を持ちます。唯一の違いは、ローカル メソッド スタックは仮想マシンによって使用されるネイティブ メソッドを提供するのに対し、仮想マシン スタックは仮想マシンによって実行される Java メソッド (つまり、バイトコード) を提供するという点です。

ローカル メソッド スタックは、スタック深度がオーバーフローするか、スタック拡張が失敗した場合にも、StackOverflowError および OutOfMemoryError 例外をスローします。

スレッドが共有する 2 つの領域について説明します。

ヒープ: Java ヒープは、仮想マシンによって管理されるメモリの最大の領域です。ヒープはすべてのスレッドで共有されるメモリ領域であり、仮想マシンの起動時に作成されます。このメモリ領域の唯一の目的はオブジェクト インスタンスを格納することであり、ほぼすべての新しいオブジェクト インスタンスにここでメモリが割り当てられます。

Java ヒープは、固定サイズまたは拡張可能なサイズのいずれかとして実装できます。オブジェクト インスタンスの割り当てを完了するためのメモリがヒープ内に存在せず、ヒープをそれ以上拡張できない場合、JVM は OutOfMemoryError 例外をスローします。

メソッド領域: メソッド領域は、仮想マシンがクラスのロードを完了した後に、型情報、定数、静的変数、ジャストインタイム コンパイラによってコンパイルされたコード キャッシュ、およびクラスに関連するその他のデータが格納される場所です。

JDK 8 より前では、ヒープとメソッド領域は実際には接続されていたか、メソッド領域は実際にはヒープの一部でした。 HotSpot 仮想マシンによって提供される特定の実装は、連続したメモリ空間である永続的な世代です。永久世代のガベージ コレクションと古い世代のガベージ コレクションはバインドされているため、いずれかの領域がいっぱいになると、両方の領域をガベージ コレクションする必要があり、OOM の可能性が高まります。さらに、String の intern() メソッドなどのいくつかのメソッドは、永続的な生成のため、異なる仮想マシンではパフォーマンスが異なり、コードの移行には適していません。これら 2 つの理由により、HotSpots は JDK 8 以降、メソッド領域の実装をメタスペースに置き換えました。

メタスペースとパーマネントジェネレーションの最大の違いは、メタスペースはヒープと連続しなくなり、ネイティブメモリ内に存在することです。つまり、ネイティブメモリが十分である限り、OOMは発生しません。

<<:  統合アーキテクチャ: 分散データベースを再定義し、コアビジネスシステムに参入

>>:  魅力的なクラウドイノベーション16選

推薦する

JVM における TLAB の謎を解明

[[413891]]この記事はWeChatの公開アカウント「プログラマーの成長」から転載したもので、...

bMobilized: 間違いのないモバイルウェブページジェネレータ

bMobilized: 間違いのないモバイルウェブページジェネレータ「モバイルインターネットの時代」...

国内のネットワーク間決済政策は調整される可能性があり、近い将来に関連する意見が発表される予定である。

12月10日の報道によると、わが国のインターネット相互接続は2012年に初期成果を達成し、ネットワー...

alphavps: VPS、専用サーバー、販売中、5 つのデータ センター

ブルガリアのホスティング プロバイダー alphavps は、6 月に VPS と専用サーバーの両方...

SEOにおけるウェブサイトのホーム画面デザインの5つの重要な側面に関する実用的な情報を共有します

ウェブサイトのユーザー エクスペリエンスは、アート、デザイン、プログラミング、戦略、フィードバックを...

#ブラックフライデー#: templatemonster-すべてのテンプレートが年に1回だけ40%オフ

インターネット上には2つの人気のテンプレート販売業者があります。1つはここで紹介するtemplate...

画像ハードコアサイエンス: エッジコンピューティングとは何ですか?クラウドコンピューティングとの関係は何ですか?

近年のクラウド コンピューティングの台頭に伴い、「エッジ コンピューティング」という別の用語が徐々に...

新しいサイトのランキングを素早く上げるためのヒント

これは実際のケースです。私は数年間ウェブサイトを作っており、さまざまな規模のウェブサイトを何百も持っ...

クラウドコンピューティングを発明したのは誰ですか?

[[338420]]この記事はWeChatの公開アカウント「Xianzao Classroom」から...

5Gとエッジコンピューティングを組み合わせた新しいサービスモデルがモバイル通信事業者に新たなビジネスチャンスをもたらす

米国のモバイルネットワーク事業者ベライゾンは、5Gの最新の応用例を見つけるために、韓国の電子機器大手...

SEOで1年間の経験を積んだ初心者は、経験を盲目的に崇拝すべきではない

SEOは儲かる!これが私が初めてSEOに触れたときの直感でした。だから短期間でサクサク始めようと、毎...

Baidu ウェブマスターコミュニティ情報の解釈: タイムリーなリソース収集に関する問題

Baidu Webmaster Community が昨日ひっそりと立ち上げられました。また、Bai...

メモリ管理は2つの部分から成ります: 仮想メモリ管理

[[402636]]この記事はWeChatの公開アカウント「Flying Veal」から転載したもの...

buyvm-公式SSD VPSが販売開始

本日より buyvm が正式に SSD ハード ドライブをリリースすることをお知らせします。これは、...

クラウド コンピューティング プロフェッショナルのための新年の抱負

まず第一に、私は新年の抱負を立てることにあまり興味がありません。私の意見では、1月1日に決意を固める...