Javaインタビューの質問でのメモリ管理(+回答)
1. 序章
この記事では、Java開発者のインタビュー中に頻繁に出てくるメモリ管理の質問について説明します。 メモリ管理は、それほど多くの開発者が精通していない分野です。
実際、開発者は通常、この概念に直接対処する必要はありません。JVMが本質的な詳細を処理するためです。 何か深刻な問題が発生しない限り、経験豊富な開発者でさえ、メモリ管理に関する正確な情報をすぐに入手できない可能性があります。
一方、これらの概念は実際にはインタビューで非常に普及しているので、すぐに始めましょう。
2. 質問
Q1。 「メモリはJavaで管理されている」というステートメントはどういう意味ですか?
メモリは、アプリケーションを効果的に実行するために必要な主要なリソースであり、他のリソースと同様に、ほとんどありません。 そのため、アプリケーションまたはアプリケーションのさまざまな部分との間での割り当てと割り当て解除には、多くの注意と考慮が必要です。
ただし、Javaでは、開発者はメモリの明示的な割り当てと割り当て解除を行う必要はありません。JVM、より具体的にはガベージコレクターは、開発者が行う必要がないようにメモリ割り当てを処理する義務があります。
これは、プログラマーがメモリに直接アクセスし、コード内のメモリセルを文字通り参照する、Cのような言語で発生することとは対照的であり、メモリリークの余地がたくさんあります。
Q2。 ガベージコレクションとは何ですか?その利点は何ですか?
ガベージコレクションは、ヒープメモリを調べ、使用中のオブジェクトと使用されていないオブジェクトを識別し、未使用のオブジェクトを削除するプロセスです。
使用中のオブジェクト、または参照されるオブジェクトは、プログラムの一部がそのオブジェクトへのポインターを保持していることを意味します。 未使用のオブジェクト、または参照されていないオブジェクトは、プログラムのどの部分からも参照されなくなりました。 したがって、参照されていないオブジェクトによって使用されたメモリを再利用できます。
ガベージコレクションの最大の利点は、手動でのメモリの割り当て/割り当て解除の負担がなくなるため、目前の問題の解決に集中できることです。
Q3。 ガベージコレクションのデメリットはありますか?
はい。 ガベージコレクターが実行されるたびに、アプリケーションのパフォーマンスに影響を及ぼします。 これは、ガベージコレクタスレッドが効果的に機能できるようにするには、アプリケーション内の他のすべてのスレッドを停止する必要があるためです。
アプリケーションの要件によっては、これはクライアントが受け入れられない実際の問題になる可能性があります。 ただし、この問題は、巧みな最適化とガベージコレクターの調整、およびさまざまなGCアルゴリズムの使用により、大幅に削減または排除することができます。
Q4。 「Stop-The-World」という用語の意味は何ですか?
ガベージコレクタスレッドが実行されているときは、他のスレッドが停止します。つまり、アプリケーションは一時的に停止します。 これは、プロセスが完了するまで居住者がアクセスを拒否される家の掃除や燻蒸に似ています。
アプリケーションのニーズによっては、「世界を止める」ガベージコレクションが許容できないフリーズを引き起こす可能性があります。 これが、発生したフリーズが少なくとも許容できるように、ガベージコレクターの調整とJVMの最適化を行うことが重要である理由です。
Q5。 スタックとヒープとは何ですか? これらのメモリ構造のそれぞれに何が格納され、それらはどのように相互に関連していますか?
スタックは、プログラム内の現在の位置までのネストされたメソッド呼び出しに関する情報を含むメモリの一部です。 また、現在実行中のメソッドで定義されているヒープ上のオブジェクトへのすべてのローカル変数と参照も含まれています。
この構造により、ランタイムは、呼び出されたアドレスを知っているメソッドから戻ることができ、メソッドを終了した後にすべてのローカル変数をクリアすることもできます。 すべてのスレッドには独自のスタックがあります。
ヒープは、オブジェクトの割り当てを目的としたメモリの大部分です。 new キーワードを使用してオブジェクトを作成すると、そのオブジェクトはヒープに割り当てられます。 ただし、このオブジェクトへの参照はスタック上にあります。
Q6。 ジェネレーションガベージコレクションとは何ですか?それを人気のあるガベージコレクションアプローチにする理由は何ですか?
世代別ガベージコレクションは、ヒープが世代と呼ばれるいくつかのセクションに分割され、各セクションがヒープ上の「年齢」に応じてオブジェクトを保持する、ガベージコレクターによって使用される戦略として大まかに定義できます。
ガベージコレクターが実行されているときはいつでも、プロセスの最初のステップはマーキングと呼ばれます。 これは、ガベージコレクタが使用中のメモリと使用されていないメモリを識別する場所です。 システム内のすべてのオブジェクトをスキャンする必要がある場合、これは非常に時間のかかるプロセスになる可能性があります。
割り当てられるオブジェクトが増えるにつれて、オブジェクトのリストが増え、ガベージコレクションの時間が長くなります。 ただし、アプリケーションの経験的分析では、ほとんどのオブジェクトが短命であることが示されています。
世代別ガベージコレクションでは、オブジェクトは、存続したガベージコレクションサイクルの数という観点から、「年齢」に従ってグループ化されます。 このようにして、作業の大部分はさまざまなマイナーおよびメジャーの収集サイクルに分散されます。
今日、ほとんどすべてのガベージコレクターは世代を超えています。 この戦略は、時間の経過とともに最適なソリューションであることが証明されているため、非常に人気があります。
Q7。 世代別ガベージコレクションの仕組みを詳しく説明する
世代別ガベージコレクションがどのように機能するかを正しく理解するには、最初にJavaヒープがどのように構造化されているかを覚えて世代別ガベージコレクションを容易にすることが重要です。
ヒープは、より小さなスペースまたは世代に分割されます。 これらのスペースは、若い世代、古い世代または古い世代、および永続的な世代です。
若い世代は、新しく作成されたオブジェクトのほとんどをホストします。 ほとんどのアプリケーションの経験的研究は、オブジェクトの大部分がすぐに短命であり、したがって、すぐに収集の対象になることを示しています。 したがって、新しいオブジェクトはここで旅を開始し、特定の「年齢」に達した後にのみ古い世代のスペースに「昇格」します。
世代別ガベージコレクションの「年齢」という用語は、オブジェクトが存続した収集サイクルの数を指します。
若い世代のスペースはさらに3つのスペースに分割されます。エデンスペースと、サバイバー1(s1)とサバイバー2(s2)などの2つのサバイバースペースです。
旧世代は、が特定の「年齢」よりも長くメモリに存在していたオブジェクトをホストします。 若い世代からのガベージコレクションを生き延びたオブジェクトは、このスペースに昇格します。 それは一般的に若い世代よりも大きいです。 サイズが大きいため、ガベージコレクションは若い世代よりも費用がかかり、発生頻度も低くなります。
永続世代またはより一般的に呼ばれるPermGenには、アプリケーションで使用されるクラスとメソッドを記述するためにJVMが必要とするメタデータが含まれています。 また、インターンされた文字列を格納するための文字列プールも含まれています。 これは、アプリケーションで使用されているクラスに基づいて、実行時にJVMによって入力されます。 さらに、プラットフォームライブラリのクラスとメソッドをここに保存できます。
まず、新しいオブジェクトがエデンスペースに割り当てられます。 両方のサバイバースペースは空から始まります。 エデンスペースがいっぱいになると、マイナーなガベージコレクションがトリガーされます。 参照されたオブジェクトは、最初のサバイバースペースに移動されます。 参照されていないオブジェクトは削除されます。
次のマイナーGCの間に、同じことがエデン空間にも起こります。 参照されていないオブジェクトは削除され、参照されているオブジェクトはサバイバースペースに移動されます。 ただし、この場合、2番目のサバイバースペース(S2)に移動します。
さらに、最初のサバイバースペース(S1)の最後のマイナーGCからのオブジェクトは、年齢が増加し、S2に移動されます。 残っているすべてのオブジェクトがS2に移動されると、S1とEdenの両方のスペースがクリアされます。 この時点で、S2にはさまざまな年齢のオブジェクトが含まれています。
次のマイナーGCでは、同じプロセスが繰り返されます。 ただし、今回はサバイバースペースが切り替わります。 参照されるオブジェクトは、EdenとS2の両方からS1に移動されます。 生き残ったオブジェクトは古くなります。 エデンとS2がクリアされます。
マイナーなガベージコレクションサイクルごとに、各オブジェクトの経過時間がチェックされます。 たとえば8歳など、特定の任意の年齢に達した人は、若い世代から古い世代または終身世代に昇進します。 以降のすべてのマイナーGCサイクルでは、オブジェクトは引き続き旧世代のスペースに昇格されます。
これは、若い世代のガベージコレクションのプロセスをかなり使い果たします。 最終的には、そのスペースをクリーンアップして圧縮する古い世代で大規模なガベージコレクションが実行されます。 メジャーGCごとに、いくつかのマイナーGCがあります。
Q8。 オブジェクトがガベージコレクションの対象になるのはいつですか? Gcが適格なオブジェクトを収集する方法を説明してください。
オブジェクトがライブスレッドまたは静的参照から到達できない場合、オブジェクトはガベージコレクションまたはGCの対象になります。
オブジェクトがガベージコレクションの対象になる最も簡単なケースは、そのすべての参照がnullの場合です。 ライブ外部参照のない循環依存もGCの対象となります。 したがって、オブジェクトAがオブジェクトBを参照し、オブジェクトBがオブジェクトAを参照し、他にライブ参照がない場合、オブジェクトAとオブジェクトBの両方がガベージコレクションの対象になります。
もう1つの明らかなケースは、親オブジェクトがnullに設定されている場合です。 キッチンオブジェクトが内部で冷蔵庫オブジェクトとシンクオブジェクトを参照し、キッチンオブジェクトがnullに設定されている場合、冷蔵庫とシンクの両方が、親であるキッチンと一緒にガベージコレクションの対象になります。
Q9。 Javaコードからガベージコレクションをトリガーするにはどうすればよいですか?
Javaプログラマーとして、Javaでガベージコレクションを強制することはできません; これは、JVMがJavaヒープサイズに基づくガベージコレクションが必要であると判断した場合にのみトリガーされます。
オブジェクトをメモリから削除する前に、ガベージコレクションスレッドはそのオブジェクトのfinalize()メソッドを呼び出し、必要なあらゆる種類のクリーンアップを実行する機会を与えます。 オブジェクトコードのこのメソッドを呼び出すこともできますが、このメソッドを呼び出したときにガベージコレクションが発生する保証はありません。
さらに、ガベージコレクションの要求をJVMに送信するために使用されるSystem.gc()やRuntime.gc()のようなメソッドがありますが、ガベージコレクションが発生することは保証されていません。
Q10。 新しいオブジェクトのストレージを収容するのに十分なヒープスペースがない場合はどうなりますか?
ヒープに新しいオブジェクトを作成するためのメモリスペースがない場合、Java仮想マシンはOutOfMemoryErrorまたはより具体的にはjava.lang.OutOfMemoryErrorヒープスペースをスローします。
Q11。 ガベージコレクションの対象となったオブジェクトを「復活」させることは可能ですか?
オブジェクトがガベージコレクションの対象になると、GCはそのオブジェクトに対してfinalizeメソッドを実行する必要があります。 finalize メソッドは1回だけ実行されることが保証されているため、GCはオブジェクトにファイナライズ済みのフラグを立て、次のサイクルまで休止します。
finalize メソッドでは、たとえば、オブジェクトを static フィールドに割り当てることにより、オブジェクトを技術的に「復活」させることができます。 オブジェクトは再び有効になり、ガベージコレクションの対象外になるため、GCは次のサイクルでオブジェクトを収集しません。
ただし、オブジェクトはfinalizedとしてマークされるため、再び適格になると、finalizeメソッドは呼び出されません。 本質的に、この「復活」トリックは、オブジェクトの存続期間中に1回だけ回すことができます。 この醜いハックは、自分が何をしているかを本当に理解している場合にのみ使用する必要があることに注意してください。ただし、このトリックを理解すると、GCがどのように機能するかについての洞察が得られます。
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つの参照を作成しました。 最初の行は強い参照sb を作成し、2番目の行はソフト参照 sbRefを作成します。 3行目では、オブジェクトをコレクションの対象にする必要がありますが、 sbRef のため、ガベージコレクターはオブジェクトの収集を延期します。
ストーリーは、メモリが不足し、JVMがOutOfMemoryエラーをスローする寸前の場合にのみ変更されます。 つまり、メモリを回復するための最後の手段として、ソフト参照のみを持つオブジェクトが収集されます。
弱参照は、弱参照クラスを使用して同様の方法で作成できます。 sb がnullに設定され、 StringBuilder オブジェクトの参照が弱い場合、JVMのガベージコレクターはまったく妥協せず、次のサイクルですぐにオブジェクトを収集します。
ファントム参照は弱参照に似ており、ファントム参照のみを持つオブジェクトが待機せずに収集されます。 ただし、ファントム参照は、オブジェクトが収集されるとすぐにキューに入れられます。 参照キューをポーリングして、オブジェクトがいつ収集されたかを正確に知ることができます。
Q13。 循環参照(相互に参照する2つのオブジェクト)があるとします。 このようなオブジェクトのペアは、ガベージコレクションの対象になる可能性がありますか?その理由は何ですか?
はい、循環参照を持つオブジェクトのペアは、ガベージコレクションの対象になる可能性があります。 これは、Javaのガベージコレクターが循環参照を処理する方法が原因です。 オブジェクトへの参照がある場合ではなく、ガベージコレクションルート(ライブスレッドのローカル変数または静的フィールド)からオブジェクトグラフをナビゲートすることでオブジェクトに到達できる場合に、オブジェクトがライブであると見なします。 循環参照を持つオブジェクトのペアがどのルートからも到達できない場合、ガベージコレクションの対象と見なされます。
Q14。 文字列はメモリ内でどのように表されますか?
JavaのStringインスタンスは、 char []valueフィールドとinthashフィールドの2つのフィールドを持つオブジェクトです。 value フィールドは、文字列自体を表す文字の配列であり、 hash フィールドには、ゼロで初期化される文字列の hashCode が含まれ、最初のhashCode()呼び出しとそれ以降のキャッシュ。 奇妙なエッジケースとして、文字列の hashCode の値がゼロの場合、 hashCode()が呼び出されるたびに再計算する必要があります。
重要なことは、Stringインスタンスは不変であるということです。基になるchar[]配列を取得または変更することはできません。 文字列のもう1つの機能は、静的定数文字列が文字列プールにロードおよびキャッシュされることです。 ソースコードに複数の同一のStringオブジェクトがある場合、それらはすべて実行時に単一のインスタンスで表されます。
Q15。 Stringbuilderとは何ですか?そのユースケースは何ですか? 文字列ビルダーに文字列を追加することと、+演算子を使用して2つの文字列を連結することの違いは何ですか? StringbuilderはStringbufferとどのように異なりますか?
StringBuilder を使用すると、文字と文字列を追加、削除、および挿入することにより、文字シーケンスを操作できます。 これは、不変の String クラスとは対照的に、可変のデータ構造です。
2つのStringインスタンスを連結すると、新しいオブジェクトが作成され、文字列がコピーされます。 ループ内で文字列を作成または変更する必要がある場合、これによりガベージコレクタのオーバーヘッドが大幅に増加する可能性があります。 StringBuilder を使用すると、文字列操作をより効率的に処理できます。
StringBuffer は、スレッドセーフであるという点でStringBuilderとは異なります。 シングルスレッドで文字列を操作する必要がある場合は、代わりにStringBuilderを使用してください。
3. 結論
この記事では、Javaエンジニアのインタビューで頻繁に出てくる最も一般的な質問のいくつかを取り上げました。 インタビュアーは、多くの場合、メモリの問題に悩まされている重要なアプリケーションを構築したことを期待しているため、メモリ管理に関する質問は主にシニアJava開発者の候補者に尋ねられます。
これは、質問の完全なリストとして扱われるべきではなく、さらなる研究のための出発点として扱われるべきです。 Baeldungでは、今後のインタビューで成功することを願っています。