インタビューの質問 005-Java-JVM (パート 1)
2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
インタビューの質問 005-Java-JVM (パート 1)
セルフテストの質問
質疑応答
1. JVM はどのような部分で構成されていますか?
回答: JVM は実行可能ファイルですバイトコード (.class) ファイル仮想コンピュータ。メモリ管理、ガベージ コレクション、その他のメカニズムも提供します。以下の主要な部分が含まれています。
- クラスロードサブシステム: バイトコードファイル (.class) を JVM にロードします。
- 実行時データ領域: 実行中に JVM によって使用されるメモリ領域です。
- 実行エンジン: バイトコードを解釈したり、プロセッサー実行用のマシンコードにコンパイルしたりする役割を果たします。
- ネイティブ ライブラリ インターフェイス: オペレーティング システムまたは他の言語で記述されたネイティブ ライブラリを呼び出すための API セットを提供します。
2. 実行時データ領域にはどのような領域が含まれますか?
回答: 実行時データ領域は、Java プログラムの実行時に JVM によって割り当てられるメモリ領域です。
- プログラム カウンタ: これは小さなメモリ空間であり、スレッドによって現在実行されているバイトコード命令のアドレスです。スレッドがネイティブ メソッドを実行している場合、このカウンタの値は未定義です。
- Java 仮想マシン スタック: 各スレッドは、作成時に仮想マシン スタックを作成します。これは、スレッドのローカル変数テーブル、オペランド フレーム、ダイナミック リンク、メソッド終了情報などを格納するために使用されます。 Java 仮想マシンのスタックには複数のスタック フレームが含まれており、各メソッドの呼び出しから実行完了までの処理が、スタック フレームを仮想マシンのスタックにプッシュするまでの処理に相当します。
- ネイティブ メソッド スタック: ネイティブ メソッドを実行するために JVM によって用意される空間であり、Java 仮想マシン スタックと同様の機能を持ち、ネイティブ メソッドの実行プロセスを記述します。
- ヒープ: ほぼすべてのオブジェクト インスタンスと配列を保存するために使用され、ガベージ コレクターが動作する主要な領域です。
- メソッド領域: JVM によってロードされたクラス情報、定数、静的変数、ジャストインタイム コンパイラによってコンパイルされたコードなどを格納するために使用されます。 JDK1.8 より前は、永続世代として実装されていました。 JDK1.8 以降、永続世代は元のスペースに置き換えられます。メタスペースはヒープ メモリではなくローカル メモリを使用します。

3. スタックとヒープにはそれぞれどのようなデータが格納されますか?
回答: スタック (Java 仮想マシン スタック) に保存されるデータ:
- ローカル変数テーブル: 主にメソッド パラメータとメソッド内のローカル変数を格納するために使用されます。データ型には、基本データ型とオブジェクト参照が含まれます。
- オペランド スタック: メソッドの実行中に演算命令と中間結果を一時的に格納するために使用されます。
- ダイナミック リンク: メソッドが属するクラスの定数プールへの参照。メソッド内のシンボル参照を解決するために使用されます。
- メソッド戻りアドレス: メソッド呼び出し後に実行される次の命令のアドレスを格納します。
ヒープに保存されるデータ: - オブジェクト インスタンス: プログラム内の new キーワードによって作成されたオブジェクト インスタンス。オブジェクトのプロパティとメソッドが含まれます。
- 配列: 基本タイプの配列やオブジェクト配列を含む、すべてのタイプの配列。
4. 永続世代 (PermGen) をメタスペース (MetaSpace) に置き換える必要があるのはなぜですか?
回答: 永続世代をメタスペースに置き換えるのは、主に永続世代に固有の問題と制限を解決し、JVM のパフォーマンスと柔軟性を向上させるためです。
- メモリ管理の柔軟性と効率の向上: 永続世代のメモリ サイズは、JVM の起動時に設定され、動的に調整することはできません。メタスペースは Java ヒープ メモリの代わりにローカル メモリを使用し、そのサイズは必要に応じて動的に調整できます。
- クラスのアンロードとガベージ コレクションの問題を解決します。永続世代の GC 動作は複雑で予測不可能であり、リサイクル効率は低いです。
- パフォーマンスと安定性の向上: メタスペースは他のメモリ領域と同様にローカル メモリを使用して管理されるため、メタスペースを使用すると、JVM メモリ管理がより統合され、一貫性が高まります。これにより、メモリ管理戦略が簡素化され、全体的なパフォーマンスと安定性が向上します。
- JVM メモリ管理を簡素化する
5. ヒープ領域の基本構造を理解していますか?どのような状況でオブジェクトが古い世代に入るでしょうか?
回答: ヒープ領域の基本構造は、主に新世代、旧世代、永続世代で構成されます。 JDK8 以降、永続世代はメタスペースに置き換えられ、ストレージにローカル メモリを使用します。
- 新生代世代: 新しい世代の進行は、エデン エリアと 2 つの生存エリア (生存者 0 と生存者 1) に細分化されます。
- Eden 領域: 新しく作成されたオブジェクトは、最初に Eden 領域にメモリを割り当てます。
- Survivor 領域 (S0、S1): 新世代のガベージ コレクションで生き残ったオブジェクトを格納するために使用されます。各マイナー GC の後、存続するオブジェクトがこれら 2 つの領域間で往復コピーされます。
- 古い世代: 複数のマイナー GC の後でもまだ生きているオブジェクト。古い世代では、ガベージ コレクション (メジャー GC またはフル GC) の実行頻度が低くなります。
- 永続的な生成/メタスペース: クラス定義、定数、静的変数、ジャストインタイムでコンパイルされたコードなどを含むクラスのメタデータを保存するために使用されます。
オブジェクトが古い世代にある場合の状況:
- 経過時間のしきい値に達した: 新しい世代でメモリが割り当てられるとき、各オブジェクトには経過時間があり、マイナー GC が発生するたびに経過時間が 1 ずつ増加します。年齢が特定のしきい値 (デフォルトは 15) に達すると、オブジェクトは古い世代に昇格します。
- 大きなオブジェクト: オブジェクトが大きすぎて、JVM によって設定されたしきい値を超える場合、オブジェクトは古い世代のスペースを直接割り当てます。
- Survivor 領域のスペースが不十分です: Survivor 領域に、マイナー GC 中にすべての存続オブジェクトを収容するのに十分なスペースがない場合、これらのオブジェクトは
- 動的なオブジェクトの年齢決定: Survivor 空間内の同じ年齢のすべてのオブジェクトのサイズが Survivor 空間の半分を超える場合、この年齢以上の年齢を持つオブジェクトは直接古い世代に入ることができます。
uint ageTable::compute_tenuring_threshold(size_t survivor_capacity) {
size_t desired_survivor_size = (size_t)((((double) survivor_capacity)*TargetSurvivorRatio)/100);
size_t total = 0;
uint age = 1;
while (age < table_size) {
total += sizes[age];
if (total > desired_survivor_size) break;
age++;
}
uint result = age < MaxTenuringThreshold ? age : MaxTenuringThreshold;
...
}
6. 大きなオブジェクトはどのメモリ領域に配置されますか?
回答: 大きなオブジェクト (非常に大きな配列や文字列) は通常、旧世代のメモリ領域に直接割り当てられます。これは、新しい世代が頻繁にパフォーマンスを発揮するのを防ぐためです。ガベージコレクションこのとき、Eden 領域と Survivor 領域の間で大きなオブジェクトが頻繁にコピーされるため、ガベージ コレクションの効率が向上します。
大きなオブジェクトが古い世代に直接入るようにしきい値を構成します。
# 将大于1MB的对象直接分配在老年代
java -XX:PretenureSizeThreshold=1m -jar your-application.jar
7. Java オブジェクトの作成プロセスは何ですか?
答え:
- クラスロードチェック
- クラスがロード、接続、および初期化されていない場合、JVM は最初にクラスをロードします。これには次の手順が含まれます。
- ロード: クラス ローダーを通じてクラス ファイルを読み取り、クラスのバイトコードをメモリにロードします。
- 接続: 検証、準備、解析という 3 つの段階が含まれます。クラス ファイルの正当性を確認し、クラスの静的変数を準備してメモリを割り当て、シンボル参照を直接参照に解決します。
- 初期化: クラスの静的初期化ブロックと静的変数の初期化を実行します。
- メモリ割り当て
- JVM は、ヒープ内の新しいオブジェクトにメモリを割り当てます。割り当てられるメモリのサイズは、オブジェクト ヘッダーやインスタンス データなどのオブジェクトの構造によって決まります。
- JVM には、メモリを割り当てる主な方法が 2 つあります。
- ポインタのバンプ: ヒープ メモリが通常の場合、割り当てポインタは空きメモリ領域に指定された距離だけ移動する必要があります。
- フリー リスト: ヒープ メモリが不規則な場合、JVM はフリー リストを維持し、メモリを割り当てるときにフリー リストから適切なブロックを見つける必要があります。
- ゼロ値に初期化する
- JVM はオブジェクトのすべてのインスタンス変数をデフォルト値に初期化します。たとえば、数値型変数は 0 に、ブール型変数は false に、参照型変数は null に初期化されます。
- オブジェクトヘッダーを設定する
- オブジェクトのメモリ空間にオブジェクト ヘッダー情報を設定します。これには、オブジェクトのハッシュ コード、GC 生成経過時間、ロック ステータス フラグ、スレッドが保持するロック、バイアスされたスレッド ID などが含まれます。
- コンストラクターの初期化
- オブジェクトのコンストラクターを呼び出して、オブジェクトの初期化を完了します。これには、コンストラクター本体内のコードだけでなく、インスタンス変数に対して明示的な初期化操作を実行することも含まれます。具体的な手順は次のとおりです。
- クラスのインスタンス初期化ブロックを実行します。
- 親クラスのコンストラクターメソッドを継承階層に従って上から下に実行します。
- インスタンス変数を明示的に指定された値に初期化します。
- クラスのコンストラクター メソッドの主要部分を実行します。
public class MyClass {
private int value;
public MyClass(int value) {
this.value = value;
}
public static void main(String[] args) {
MyClass obj = new MyClass(10);
}
}
- 上記のコード例の実行プロセスは次のとおりです。
- クラスのロード: JVM は MyClass がロードされているかどうかを確認します。ロードされていない場合は、MyClass クラスがロードされます。
- メモリ割り当て: MyClass の新しいインスタンスのヒープにメモリを割り当てます。
- メモリの初期化: 割り当てられたメモリをデフォルト値に初期化します。
- オブジェクトヘッダーの設定: オブジェクトヘッダーにメタデータを設定します。
- コンストラクターの初期化: MyClass のコンストラクターを実行し、インスタンス変数の値を 10 に初期化します。
参考文献