1.はじめに

この記事では、Java開発者へのインタビューの際によく現れる、メモリ管理に関する質問をいくつか探ります。メモリ管理は、あまり多くの開発者が慣れていない分野です。

実際、開発者は一般的にこの概念を直接処理する必要はありません – JVMが重要な詳細を処理するためです。何かがひどく悪くならない限り、熟練した開発者でさえ彼らの指先でメモリ管理についての正確な情報を持っていないかもしれません。

一方、これらの概念は実際にはインタビューではかなり一般的なので、すぐに始めましょう。

2.質問


Q1. 「メモリはJavaで管理される」という文はどういう意味ですか?

メモリはアプリケーションが効果的に動作するために必要な重要なリソースであり、他のリソースと同様に不足しています。そのため、アプリケーションまたはアプリケーションのさまざまな部分との間での割り当てと割り当て解除には、十分な注意と考慮が必要です。

ただし、Javaでは、開発者が明示的にメモリの割り当てと割り当て解除を行う必要はありません。JVM、より具体的にはガベージコレクタは、開発者が行う必要がないようにメモリ割り当てを処理する義務があります。

これは、プログラマがメモリに直接アクセスし、文字通り自分のコード内のメモリセルを参照するCのような言語では起こることとは反対で、メモリリークのための多くの余地を作り出します。


Q2. ガベージコレクションとは何ですか?また、その利点は何ですか?

ガベージコレクションは、ヒープメモリを調べて、どのオブジェクトが使用中でどのオブジェクトが使用中でないかを識別し、未使用のオブジェクトを削除するプロセスです。

使用中のオブジェクト、または参照されているオブジェクトは、プログラムの一部がそのオブジェクトへのポインタを保持していることを意味します。未使用のオブジェクト、または参照されていないオブジェクトは、プログラムのどの部分からも参照されなくなります。そのため、参照されていないオブジェクトによって使用されていたメモリを再利用できます。

ガベージコレクションの最大の利点は、手動でのメモリ割り当て/割り当て解除の負担を取り除いて、問題の解決に集中できるようにすることです。


Q3. ガベージコレクションの不利な点はありますか?

はい。ガベージコレクタが実行されるたびに、アプリケーションのパフォーマンスに影響を与えます。これは、ガベージコレクタスレッドが効果的に機能するためには、アプリケーション内の他のすべてのスレッドを停止する必要があるためです。

アプリケーションの要件によっては、これはクライアントからは受け入れられない実際の問題となる可能性があります。ただし、この問題は、巧妙な最適化とガベージコレクタのチューニング、およびさまざまなGCアルゴリズムの使用によって、大幅に軽減または解消することができます。


Q4. 「世界を止める」という用語の意味は何ですか?

ガベージコレクタスレッドが実行されているとき、他のスレッドは停止されます。つまり、アプリケーションは一時的に停止されます。これは、処理が完了するまで居住者がアクセスを拒否されている場合のハウスクリーニングまたは燻蒸に似ています。

アプリケーションのニーズによっては、「世界を止めよう」ガベージコレクションは受け入れられないフリーズを引き起こす可能性があります。このため、発生したフリーズが少なくとも許容範囲内に収まるように、ガベージコレクタの調整とJVMの最適化を行うことが重要です。


Q5. スタックとヒープとは何ですか?これらの各メモリ構造体には何が格納されていますか?またそれらはどのように相互に関連していますか?

スタックは、プログラム内の現在位置までのネストされたメソッド呼び出しに関する情報を含むメモリの一部です。現在実行中のメソッドで定義されているすべてのローカル変数とヒープ上のオブジェクトへの参照も含まれています。

この構造により、ランタイムは呼び出されたアドレスを知ってメソッドから戻ることができ、またメソッドを終了した後にすべてのローカル変数をクリアすることができます。どのスレッドにも独自のスタックがあります。

ヒープは、オブジェクトの割り当てを目的とした大量のメモリです。


new

キーワードを使用してオブジェクトを作成すると、そのオブジェクトはヒープに割り当てられます。ただし、このオブジェクトへの参照はスタック上にあります。


Q6. 世代別ガベージコレクションとは何ですか?また、それが一般的なガベージコレクションアプローチとなっている理由は?

世代別ガベージコレクションは、ヒープが世代と呼ばれるいくつかのセクションに分割され、それぞれがヒープの「年齢」に従ってオブジェクトを保持するガベージコレクタによって使用される戦略として大まかに定義できます。

ガベージコレクタが実行されているときはいつでも、プロセスの最初のステップはマーキングと呼ばれます。これは、ガベージコレクタが使用中のメモリと使用されていないメモリを識別するための場所です。システム内のすべてのオブジェクトをスキャンする必要がある場合、これは非常に時間のかかるプロセスになる可能性があります。

割り当てられるオブジェクトが増えるにつれて、オブジェクトのリストが大きくなり、ガベージコレクションの時間が長くなります。しかし、アプリケーションの実証分析では、ほとんどのオブジェクトが短命であることが示されています。

世代別ガベージコレクションでは、オブジェクトは、存続したガベージコレクションサイクルの数に関して、「年齢」に従ってグループ化されます。このように、作業の大部分は、マイナーおよびメジャーのさまざまな収集サイクルに分散していました。

今日、ほとんどすべてのガベージコレクタは世代別です。この戦略は、時が経つにつれて最適なソリューションであることが証明されているので、とても人気があります。


Q7. 世代別ガベージコレクションの仕組みを詳しく説明してください

世代別ガベージコレクションの仕組みを正しく理解するためには、まず世代別ガベージコレクションを容易にするためにJavaヒープがどのように構造化されているかを覚えておくことが重要です。

ヒープは小さなスペースまたは世代に分割されています。これらのスペースは、若い世代、古い世代またはTenured世代、および永久世代です。

若い世代は、新しく作成されたオブジェクトのほとんどをホストしています。ほとんどのアプリケーションの実証的な調査によると、オブジェクトの大部分は短時間で存続するため、すぐに回収の対象になります。

したがって、新しいオブジェクトはここで彼らの旅を開始し、彼らが特定の「年齢」を達成した後にのみ古い世代の空間に「昇格」します。

世代別ガベージコレクションの

「年齢」

は、オブジェクトが生き残った** 収集サイクル数を表します。

若い世代の空間はさらに3つの空間に分かれています。1つはEden空間で、2つはSurvivor 1(s1)と2つの生存者(s2)です。

  • 旧世代は、


    特定の「年齢」** よりも長くメモリに住んでいたオブジェクトをホストします。若い世代からのガベージコレクションを生き残ったオブジェクトは、このスペースに昇格します。それは一般的に若い世代よりも大きいです。サイズが大きいため、ガベージコレクションは高価で、若い世代よりも発生頻度が低くなります。

  • 永続的な生成


    またはより一般的に呼ばれる

    PermGen、

    には、アプリケーションで使用されるクラスとメソッドを記述するためにJVM ** が必要とするメタデータが含まれています。また、インターン文字列を格納するための文字列プールも含まれています。アプリケーションで使用されているクラスに基づいて、実行時にJVMによって生成されます。さらに、プラットフォームライブラリのクラスとメソッドをここに格納することができます。

まず、

すべての新しいオブジェクトがEden空間に割り当てられます

。両方の生存者スペースは空から始まります。 Edenスペースがいっぱいになると、マイナーガベージコレクションがトリガーされます。参照されたオブジェクトは最初の生存者スペースに移動されます。参照されていないオブジェクトは削除されます。

次のマイナーGCで、同じことがEdenスペースでも起こります。

参照されていないオブジェクトは削除され、参照されているオブジェクトは生存スペースに移動されます。しかしながら、この場合、それらは第2の生存者空間に移動される(S1)。

さらに、最初の生存者スペース(S0)の最後のマイナーGCからのオブジェクトは、年齢が増加してS1に移動します。すべての生き残ったオブジェクトがS1に移動されると、S0とEdenの両方のスペースがクリアされます。この時点で、S1には年齢の異なるオブジェクトが含まれています。

次のマイナーGCでは、同じプロセスが繰り返されます。しかし今回は生存者のスペースが切り替わります。参照オブジェクトはEdenとS1の両方からS0に移動されます。生き残った物は古くなっています。 EdenとS1がクリアされます。

マイナーガベージコレクションのサイクルが終わるたびに、各オブジェクトの経過時間が確認されます。例えば8歳のように、ある任意の年齢に達した人たちは、若い世代から老いもしい世代へと昇格します。その後のすべてのマイナーGCサイクルでは、オブジェクトは引き続き古い世代のスペースにプロモートされます。

これは、若い世代のガベージコレクションのプロセスをほとんど使い果たしています。やがて、古いスペースで大規模なガベージコレクションが実行され、そのスペースがクリーンアップされて圧縮されます。各メジャーGCには、いくつかのマイナーGCがあります。


Q8. オブジェクトはいつガベージコレクションの対象になりますか? GCが適格なオブジェクトを収集する方法を説明してください.

オブジェクトは、どのライブスレッドからも静的参照からもアクセスできない場合、ガベージコレクションまたはGCの対象になります。

オブジェクトがガベージコレクションの対象になる最も直接的なケースは、その参照がすべてnullの場合です。ライブ外部参照がない循環依存関係もGCに適格です。そのため、オブジェクトAがオブジェクトBを参照し、オブジェクトBがオブジェクトAを参照し、他のライブ参照がない場合、オブジェクトAとBの両方がガベージコレクションの対象になります。

もう1つの明らかなケースは、親オブジェクトがnullに設定されている場合です。キッチンオブジェクトが内部で冷蔵庫オブジェクトと流し台オブジェクトを参照していて、台所オブジェクトがnullに設定されていると、冷蔵庫と流し台の両方が、その親である台所と一緒にガベージコレクションの対象になります。


Q9. Javaコードからどのようにガベージコレクションを引き起こすのですか?

あなたは、Javaプログラマとして、Javaでガベージコレクションを強制することはできません。 JVMがJavaヒープサイズに基づいたガベージコレクションを必要としているとJVMが判断した場合にのみトリガされます。

オブジェクトをメモリガベージコレクションスレッドから削除する前に、そのオブジェクトのfinalize()メソッドを呼び出して、必要なクリーンアップを実行する機会を与えます。このメソッドをオブジェクトコードから呼び出すこともできますが、このメソッドを呼び出したときにガベージコレクションが発生するとは限りません。

さらに、System.gc()やRuntime.gc()のような、ガベージコレクションのリクエストをJVMに送るためのメソッドもありますが、ガベージコレクションが行われることが保証されるわけではありません。


Q10. 新しいオブジェクトを格納するのに十分なヒープスペースがないとどうなりますか?

ヒープ内に新しいオブジェクトを作成するためのメモリスペースがない場合、Java Virtual Machineは

OutOfMemoryError

、または**

java.lang.OutOfMemoryError

ヒープスペースをスローします。


Q11. ガベージコレクションの対象となったオブジェクトを「復活」することは可能ですか?

オブジェクトがガベージコレクションの対象になると、GCはそれに対して

finalize

メソッドを実行する必要があります。

finalize

メソッドは1回しか実行されないことが保証されているので、GCはオブジェクトにファイナライズ済みのフラグを立て、次のサイクルまでそれを休ませます。


finalize

メソッドでは、例えば

static

フィールドに割り当てることで、技術的にオブジェクトを「復活」させることができます。オブジェクトは再び生きた状態になり、ガベージコレクションに適格ではなくなるため、GCは次のサイクルでそれを収集しません。

ただし、オブジェクトはファイナライズ済みとしてマークされるため、再度適格になると、finalizeメソッドは呼び出されません。基本的に、この「復活」のトリックは、オブジェクトの有効期間中1回だけ有効にできます。この醜いハックは、自分が何をしているのかを本当に知っている場合にのみ使用するようにしてください。

==== * Q12。強い、弱い、柔らかい、そして幻想的な参照と、それらのガベージコレクションにおける役割を説明してください。

メモリがJavaで管理されるのと同じくらい、重要なアプリケーションでは、エンジニアは待ち時間を最小にし、スループットを最大にするためにできるだけ多くの最適化を実行する必要があるかもしれません。 JVMでガベージコレクションが起動されるタイミングを明示的に制御することは不可能であるのと同じくらい、作成したオブジェクトに関して発生方法に影響を与えることも可能です。

Javaは、作成したオブジェクトとガベージコレクタの関係を制御するための参照オブジェクトを提供しています。

デフォルトでは、Javaプログラムで作成するすべてのオブジェクトは変数によって強く参照されます。

StringBuilder sb = new StringBuilder();

上記のスニペットでは、

new

キーワードは新しい

StringBuilder

オブジェクトを作成し、それをヒープに格納します。変数

sb

は、このオブジェクトへの

強い参照

を格納します。これがガベージコレクタにとって意味することは、特定の

StringBuilder

オブジェクトが

sb

によって保持されている強い参照のために、まったくコレクションの対象にならないということです。このように

sb

を無効にした場合にのみ、ストーリーは変わります。

sb = null;

上記の行を呼び出した後、オブジェクトはコレクションの対象となります。

オブジェクトとガベージコレクタの間のこの関係は、

java.lang.ref

パッケージ内にある別の参照オブジェクト内に明示的にラップすることで変更できます。

このように

ソフトリファレンス

を上記のオブジェクトに対して作成することができます。

StringBuilder sb = new StringBuilder();
SoftReference<StringBuilder> sbRef = new SoftReference<>(sb);
sb = null;

上記のスニペットでは、

StringBuilder

オブジェクトへの2つの参照を作成しました。 1行目は

強い参照


sb

を作成し、2行目は

ソフト参照


sbRef

を作成します。 3行目では、オブジェクトを収集対象にする必要がありますが、

sbRef

により、ガベージコレクタは収集を延期します。

メモリが窮屈になり、JVMが

OutOfMemory

エラーを投げかけようとしているときにだけ、物語は変わります。つまり、ソフト参照のみを持つオブジェクトは、メモリを回復するための最後の手段として収集されます。

弱参照** は、

WeakReference

クラスを使用して同様の方法で作成できます。

sb

がnullに設定されており、

StringBuilder

オブジェクトに弱い参照しかない場合、JVMのガベージコレクタはまったく妥協することなく、次のサイクルでただちにオブジェクトを収集します。

ファントム参照は弱い参照に似ており、ファントム参照のみを持つオブジェクトは待たずに収集されます。ただし、ファントム参照は、それらのオブジェクトが収集されるとすぐにエンキューされます。

オブジェクトがいつ収集されたかを正確に知るために参照キューをポーリングすることができます。

==== * Q13。循環参照(互いに参照する2つのオブジェクト)があるとします。そのようなオブジェクトのペアは、ガベージコレクションの対象になる可能性がありますか。またその理由は?**

はい、循環参照を持つオブジェクトのペアはガベージコレクションの対象になります。これは、Javaのガベージコレクタが循環参照をどのように処理するかによるものです。オブジェクトが参照されているときではなく、ガベージコレクションのルート(ライブスレッドのローカル変数または静的フィールド)からオブジェクトグラフを移動して到達可能なときに、オブジェクトは生きていると見なします。循環参照を持つオブジェクトのペアがどのルートからも到達できない場合は、ガベージコレクションの対象と見なされます。

==== * Q14。文字列はメモリ内でどのように表現されますか?**

Javaの

String

インスタンスは、

char[]value

フィールドと

int hash

フィールドの2つのフィールドを持つオブジェクトです。

value

フィールドは文字列自体を表す文字の配列で、

hash

フィールドは最初の

hashCode()呼び出しの間に計算され、それ以降にキャッシュされたゼロで初期化される文字列の

hashCode

を含みます。興味深いことに、文字列の

ハッシュコード

の値がゼロの場合、

ハッシュコード()が呼び出されるたびに再計算する必要があります。

重要なことは、

String

インスタンスは不変であるということです。基礎となる

char[]

配列を取得または変更することはできません。文字列のもう1つの機能は、静的定数文字列が文字列プールにロードされキャッシュされることです。

ソースコードに複数の同一の

String

オブジェクトがある場合、それらはすべて実行時に単一のインスタンスによって表されます。


Q15.

StringBuilder

とは何ですか?また、その使用例は何ですか?

StringBuilder

に文字列を追加することと、


演算子を使用して2つの文字列を連結することの違いは何ですか?

StringBuilder



StringBufferとどう違うのですか?



StringBuilder

では、文字や文字列を追加、削除、挿入することによって文字シーケンスを操作できます。不変である

String

クラスとは対照的に、これは可変データ構造です。

2つの

String

インスタンスを連結すると、新しいオブジェクトが作成され、文字列がコピーされます。ループ内で文字列を作成または変更する必要がある場合、これは巨大なガベージコレクタのオーバーヘッドをもたらす可能性があります。

StringBuilder

を使用すると、文字列操作をはるかに効率的に処理できます。


StringBuffer

は、スレッドセーフであるという点で

StringBuilder

とは異なります。シングルスレッドで文字列を操作する必要がある場合は、代わりに

StringBuilder

を使用してください。


3結論

この記事では、Javaエンジニアのインタビューに頻繁に現れるいくつかの最も一般的な質問について説明しました。インタビュアーは、メモリの問題に悩まされることが多い自明ではないアプリケーションを構築したとインタビュアーが予想しているので、メモリー管理についての質問は主に上級Java開発者候補に尋ねられます。

これは質問の網羅的なリストとして扱われるべきではなく、さらなる研究のための発射台として扱われるべきです。 Baeldungでは、今後のインタビューであなたが成功することを願っています。




  • «** 前へ