Javaインタビューでのメモリ管理に関する質問(+回答)

Javaインタビューのメモリ管理に関する質問(+回答)

1. 前書き

この記事では、Java開発者のインタビュー中に頻繁に出てくるメモリ管理の質問について説明します。 メモリ管理は、それほど多くの開発者が精通していない分野です。

実際、開発者は通常、この概念に直接対処する必要はありません。JVMが本質的な詳細を処理するためです。 重大な問題が発生しない限り、ベテランの開発者でさえ、メモリ管理に関する正確な情報をすぐに入手できない場合があります。

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

2. 質問

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

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

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

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

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

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

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

ガベージコレクションの最大の利点は、手作業でのメモリの割り当て/割り当て解除の負担がなくなるため、当面の問題の解決に集中できることです。

Q3. ガベージコレクションのデメリットはありますか?

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

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

Q4. 「Stop-The-World」という用語の意味は何ですか?

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

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

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

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

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

ヒープは、オブジェクトの割り当てを目的とした大量のメモリです。 newキーワードを使用してオブジェクトを作成すると、そのオブジェクトはヒープに割り当てられます。 ただし、このオブジェクトへの参照はスタック上に存在します。

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

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

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

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

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

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

世代別ガベージコレクションがどのように機能するかを正しく理解するには、最初にremember how Java heap is structuredを実行して世代別ガベージコレクションを容易にすることが重要です。

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

young generation hosts most of the newly created objects。 ほとんどのアプリケーションの経験的研究は、オブジェクトの大部分がすぐに短命であるため、すぐに収集の対象になることを示しています。 したがって、新しいオブジェクトはここで旅を開始し、特定の「年齢」に到達した後にのみ古い世代の空間に「昇格」します。

世代別ガベージコレクションrefers to the number of collection cycles the object has survivedの用語“age”

若い世代のスペースは、エデンスペースと、サバイバー1(s1)およびサバイバー2(s2)などの2つのサバイバースペースの3つのスペースにさらに分割されます。

old generation hosts objects thathave lived in memory longer than a certain “age”。 若い世代のガベージコレクションを生き残ったオブジェクトは、このスペースに昇格されます。 一般的に若い世代よりも大きいです。 サイズが大きいため、ガベージコレクションは高価であり、若い世代よりも頻繁に発生しません。

permanent generationor more commonly called, PermGen, contains metadata required by the JVMは、アプリケーションで使用されるクラスとメソッドを記述します。 また、インターンされた文字列を保存するための文字列プールも含まれています。 アプリケーションが使用しているクラスに基づいて、実行時にJVMによって設定されます。 さらに、プラットフォームライブラリのクラスとメソッドをここに保存できます。

まず、any new objects are allocated to the Eden space。 両方のサバイバースペースは空で始まります。 Edenスペースがいっぱいになると、マイナーガベージコレクションがトリガーされます。 参照されたオブジェクトは、最初のサバイバースペースに移動されます。 参照されていないオブジェクトは削除されます。

次のマイナーGCの間に、同じことがEdenスペースで発生します。 参照されていないオブジェクトは削除され、参照されたオブジェクトはサバイバースペースに移動されます。 ただし、この場合、2番目のサバイバースペース(S2)に移動されます。

さらに、最初のサバイバースペース(S1)の最後のマイナーGCのオブジェクトの経過時間は増分され、S2に移動されます。 残っているすべてのオブジェクトがS2に移動されると、S1スペースとEdenスペースの両方がクリアされます。 この時点で、S2にはさまざまな年齢のオブジェクトが含まれています。

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

マイナーガベージコレクションサイクルごとに、各オブジェクトの経過時間がチェックされます。 8歳など、特定の任意の年齢に達したものは、若い世代から古い世代または終身世代に昇格します。 後続のすべてのマイナーGCサイクルでは、オブジェクトは引き続き古い世代のスペースに昇格されます。

これは、若い世代のガベージコレクションのプロセスをかなり使い果たします。 最終的に、古い世代でメジャーガベージコレクションが実行され、そのスペースがクリーンアップおよび圧縮されます。 各メジャーGCには、いくつかのマイナーGCがあります。

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

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

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

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

Q9. Javaコードからガベージコレクションをどのようにトリガーしますか?

You, as Java programmer, can not force garbage collection in Java;これは、JVMがJavaヒープサイズに基づくガベージコレクションが必要であると判断した場合にのみトリガーされます。

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

さらに、System.gc()やRuntime.gc()などのメソッドがあります。これらのメソッドは、ガベージコレクションのリクエストをJVMに送信するために使用されますが、ガベージコレクションが発生することは保証されません。

Q10. 新しいオブジェクトのストレージを収容するのに十分なヒープスペースがない場合はどうなりますか?

ヒープに新しいオブジェクトを作成するためのメモリスペースがない場合、Java仮想マシンはOutOfMemoryError、より具体的にはjava.lang.OutOfMemoryError heap space.をスローします。

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

オブジェクトがガベージコレクションの対象になると、GCはそのオブジェクトに対してfinalizeメソッドを実行する必要があります。 finalizeメソッドは1回だけ実行されることが保証されているため、GCはオブジェクトにファイナライズ済みのフラグを立て、次のサイクルまで休憩します。

finalizeメソッドでは、たとえば、オブジェクトをstaticフィールドに割り当てることにより、オブジェクトを技術的に「復活」させることができます。 オブジェクトは再びアクティブになり、ガベージコレクションの対象外になるため、GCは次のサイクル中にオブジェクトを収集しません。

ただし、オブジェクトにはファイナライズ済みのマークが付けられるため、再び対象になると、finalizeメソッドは呼び出されません。 本質的に、この「復活」のトリックは、オブジェクトの存続期間中に一度だけ有効にできます。 この醜いハックは、自分が何をしているかを本当に理解している場合にのみ使用する必要があることに注意してください。ただし、このトリックを理解すると、GCがどのように機能するかについての洞察が得られます。

Q12. 強い、弱い、柔らかい、ファントムの参照と、ガベージコレクションにおけるそれらの役割について説明します。

メモリはJavaで管理されるため、重要なアプリケーションでは、エンジニアはレイテンシを最小限に抑えてスループットを最大化するために、可能な限り最適化を実行する必要があります。 JVMのit is impossible to explicitly control when garbage collection is triggeredと同様に、it is possible to influence how it occurs as regards the objects we have created.

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

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

StringBuilder sb = new StringBuilder();

上記のスニペットでは、newキーワードが新しいStringBuilderオブジェクトを作成し、それをヒープに格納します。 次に、変数sbは、strong referenceをこのオブジェクトに格納します。 これがガベージコレクターにとって意味することは、特定のStringBuilderオブジェクトは、sbによって保持されている強い参照のために、まったく収集に適格ではないということです。 ストーリーは、次のようにsbを無効にした場合にのみ変更されます。

sb = null;

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

オブジェクトとガベージコレクターの間のこの関係は、java.lang.refパッケージ内にある別の参照オブジェクト内に明示的にラップすることで変更できます。

soft referenceは、次のように上記のオブジェクトに作成できます。

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

上記のスニペットでは、StringBuilderオブジェクトへの2つの参照を作成しました。 最初の行はstrong referencesbを作成し、2番目の行はsoft referencesbRefを作成します。 3行目では、オブジェクトをコレクションに適格にする必要がありますが、ガベージコレクターはsbRefのためにオブジェクトの収集を延期します。

ストーリーは、メモリが不足し、JVMがOutOfMemoryエラーをスローする寸前の場合にのみ変更されます。 つまり、ソフト参照のみを持つオブジェクトは、メモリを回復する最後の手段として収集されます。

weak referenceは、WeakReferenceクラスを使用して同様の方法で作成できます。 sbがnullに設定され、StringBuilderオブジェクトの参照が弱い場合、JVMのガベージコレクターはまったく妥協せず、次のサイクルですぐにオブジェクトを収集します。

phantom referenceは弱参照に似ており、ファントム参照のみを持つオブジェクトが待機せずに収集されます。 ただし、ファントム参照は、オブジェクトが収集されるとすぐにキューに入れられます。 参照キューをポーリングして、オブジェクトがいつ収集されたかを正確に知ることができます。

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

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

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

JavaのStringインスタンスは、char[] valueフィールドとint hashフィールドの2つのフィールドを持つオブジェクトです。 valueフィールドは、文字列自体を表す文字の配列であり、hashフィールドには、最初のhashCode()中に計算された、ゼロで初期化される文字列のhashCodeが含まれます。それ以来、呼び出してキャッシュします。 奇妙なエッジケースとして、文字列のhashCodeの値がゼロの場合、hashCode()が呼び出されるたびに再計算する必要があります。

重要なことは、Stringインスタンスは不変であるということです。基になるchar[]配列を取得または変更することはできません。 文字列のもう1つの特徴は、静的定数文字列が文字列プールに読み込まれてキャッシュされることです。 ソースコードに複数の同一のStringオブジェクトがある場合、それらはすべて実行時に単一のインスタンスで表されます。

Q15. Stringbuilderとは何か、その使用例は何ですか? Stringbuilderに文字列を追加することと、+演算子で2つの文字列を連結することの違いは何ですか? StringbuilderはStringbufferとどのように異なりますか?

StringBuilderを使用すると、文字と文字列を追加、削除、および挿入することにより、文字シーケンスを操作できます。 これは、不変であるStringクラスとは対照的に、可変のデータ構造です。

2つのStringインスタンスを連結すると、新しいオブジェクトが作成され、文字列がコピーされます。 ループ内で文字列を作成または変更する必要がある場合、これにより巨大なガベージコレクタのオーバーヘッドが発生する可能性があります。 StringBuilderを使用すると、文字列操作をより効率的に処理できます。

StringBufferは、スレッドセーフであるという点でStringBuilderとは異なります。 シングルスレッドで文字列を操作する必要がある場合は、代わりにStringBuilderを使用してください。

3. 結論

この記事では、Javaエンジニアのインタビューでよく出てくる最も一般的な質問のいくつかを取り上げました。 インタビュアーは、多くの場合、メモリの問題に悩まされている非自明なアプリケーションを構築したことを期待しているため、メモリ管理に関する質問は主にシニアJava開発者候補者に求められます。

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