Ctrip の面接官は実際に Java 仮想マシン スタックについて質問しました。

Ctrip の面接官は実際に Java 仮想マシン スタックについて質問しました。

[[393190]]

みなさんこんにちは、私はいつまでも18歳のおばけです~

「JVM メモリ領域の分割」の記事から、Java 仮想マシンのメモリ領域は、プログラム カウンター、Java 仮想マシン スタック、ネイティブ メソッド スタック、ヒープという 4 つの領域に分割できることがわかりました。今日は、これらの領域の 1 つである Java 仮想マシン スタックについて詳しく見ていきます。

まず説明させてください。この記事のタイトルには「Ctrip interviewer」という単語が含まれていますが、これはクリックベイトのタイトルである可能性があります。しかし、正直に言うと、前回の記事に読者がメッセージを残して、Ctrip のインタビュアーが Java 仮想マシンのメモリに関するいくつかの知識ポイントを質問したと書いていたので、今日のタイトルではそのトピックを「拝借」しました。

「もっと早く会えなかったのは残念です」という言葉から、私はこの読者がこのインタビューの質問に失敗するだろうと推測しました。言い換えれば、面接官は Java 仮想マシンの知識ポイントについて質問するのが好きです。それは、応募者の真の能力をテストできるためです。そのため、このトピックについてさらにいくつかの記事を書いて、皆さんにもう少しお役に立てればと思っています。

Java 仮想マシンはメソッドを基本的な実行単位として使用し、「スタック フレーム」はメソッドの呼び出しと実行を Java 仮想マシンにサポートするために使用される基本的なデータ構造です。各スタック フレームには、ローカル変数テーブル、オペランド スタック、動的リンク、メソッドの戻りアドレス、およびいくつかの追加情報 (デバッグやパフォーマンス監視に関連する情報など) が含まれます。これらの概念は以前の記事でも触れられており、簡単に紹介されていますが、詳細が足りないと思うので、この記事ではスタック フレームでのこれらの概念の紹介に重点を置きます。

1) ローカル変数テーブル

ローカル変数テーブルは、メソッド内のローカル変数とメソッド パラメータを格納するために使用されます。 Java ソース コード ファイルをクラス ファイルにコンパイルすると、ローカル変数テーブルの最大容量が決定されます。

そのようなコードを見てみましょう。

  1. パブリッククラスLocalVaraiablesTable {
  2. プライベートvoid書き込み( int年齢){
  3. 文字列= "サイレントキング2" ;
  4. }
  5. }

write() メソッドには、1 つのパラメーター age と 1 つのローカル変数 name があります。

次に、Intellij IDEA の jclasslib を使用して、コンパイルされたバイトコード ファイル LocalVaraiablesTable.class を表示します。 write() メソッドの Code プロパティで、Maximum local variables (ローカル変数テーブルの最大容量) の値が 3 であることがわかります。

論理的には、ローカル変数テーブルの最大容量は 2、年齢 1 つと名前 1 つであるはずですが、なぜ 3 なのでしょうか?

メンバー メソッド (非静的メソッド) が呼び出されると、0 番目の変数は実際にはメンバー メソッドを呼び出すオブジェクト参照 (有名な this) になります。 write(18)メソッドを呼び出すと、実際にはwrite(this, 18)が呼び出されます。

Code プロパティをクリックし、LocalVaraiableTable をチェックして詳細情報を表示します。

0 番目は、LocalVaraiablesTable オブジェクト型のものです。 1 つ目はメソッド パラメーター age で、これは int 型です。 2 番目はメソッド内のローカル変数名で、String 型です。

もちろん、ローカル変数テーブルのサイズは、メソッド内のすべてのローカル変数の数の合計ではありません。これは変数の型とスコープに関係します。ローカル変数のスコープが終了すると、ローカル変数テーブル内でその変数が占めていた位置は次のローカル変数に置き換えられます。

次のコードを見てみましょう。

  1. 公共 静的voidメソッド(){
  2. // ①
  3. )の場合{
  4. // ②
  5. 文字列= "サイレントキング2" ;
  6. }
  7. // ③
  8. 場合
  9. // ④
  10. 年齢= 18;
  11. }
  12. // ⑤
  13. }
  • method() メソッドのローカル変数テーブルのサイズは 1 です。これは静的メソッドなので、これをローカル変数テーブルの最初の要素として追加する必要はありません。
  • ②ローカル変数に名前が付くと、ローカル変数テーブルのサイズは1になる。
  • ③name変数のスコープが終了したとき
  • ④ ローカル変数に年齢がある場合、ローカル変数テーブルのサイズは1になります。
  • ⑤年齢変数の範囲が終了するとき

ローカル変数のスコープについては、Effective Java のヒント 57 を参照してください。

ローカル変数のスコープを最小限に抑えると、コードの可読性と保守性が向上し、エラーの可能性が減ります。

ここで、一つ思い出していただきたいことがあります。スタック フレームによって消費されるメモリ領域をできるだけ節約するために、method() メソッドで示されているように、ローカル変数テーブル内のスロットを再利用できます。つまり、適切なスコープはプログラムのパフォーマンスの向上に役立ちます。

ローカル変数テーブルの容量は、最小単位であるスロットで測定されます。スロットは 32 ビットのデータ型 (int など) を保持できます。もちろん、Java 仮想マシン仕様ではスロットが占有するメモリ空間のサイズは明示的には規定されていませんが、この方が理解しやすいと思います。 float や double など、明示的に 64 ビットを占有するデータ型は、隣接する 2 つのスロットを占有します。

次のコードを見てみましょう。

  1. パブリックvoidソルト(){
  2. ダブルd = 1.0;
  3. 整数i = 1;
  4. }

jclasslib を使用すると、solt() メソッドの最大ローカル変数の値が 4 であることがわかります。

なぜ4になるのでしょうか?これを含めると3つだけでしょうか?

LocalVaraiableTable を見ると、変数 i の添え字が 3 であることがわかります。つまり、変数 d は 2 つのスロットを占有します。

2) オペランドスタック

ローカル変数テーブルと同様に、オペランド スタックの最大深度もコンパイル時に決定され、Code プロパティの最大スタック サイズに書き込まれます。メソッドの実行が開始されると、オペランド スタックは空になります。メソッドの実行中、さまざまなバイトコード命令がオペランド スタックにデータを書き込んだり、オペランド スタックからデータを取得したりします。これはプッシュ操作とポップ操作です。

次のコードを見てみましょう。

  1. パブリッククラスOperandStack {
  2. パブリックボイドテスト(){
  3. (1,2)を加える
  4. }
  5.  
  6. プライベートint   int a、 int b)を追加します
  7. a + bを返します
  8. }
  9. }

OperandStack クラスには 2 つのメソッドがあります。 add() メソッドは test() メソッド内で呼び出され、2 つのパラメータが渡されます。 jclasslib を使用すると、test() メソッドの最大スタック サイズが 3 であることがわかります。

これは、メンバー メソッドが呼び出されると、 this とすべてのパラメーターがスタックにプッシュされ、呼び出しが完了すると、 this とパラメーターがスタックから 1 つずつポップされるためです。対応するバイトコード命令は、「バイトコード」パネルから表示できます。

aload_0 は、ローカル変数テーブル内の添え字 0 の参照型変数、つまり this をオペランド スタックにロードするために使用されます。

  • iconst_1 は、整数 1 をオペランド スタックにロードするために使用されます。
  • iconst_2 は整数 2 をオペランド スタックにロードするために使用されます。
  • invokevirtual はオブジェクトのメンバーメソッドを呼び出すために使用されます。
  • pop はスタックの一番上の値をポップするために使用されます。
  • return は void メソッドの戻り命令です。

add() メソッドのバイトコード命令を見てみましょう。

  • iload_1 は、ローカル変数テーブル内の添え字 1 の int 型変数をオペランド スタックにロードするために使用されます (添え字 0 はこれです)。
  • iload_2 は、ローカル変数テーブル内の添え字 2 を持つ int 型変数をオペランド スタックにロードするために使用されます。
  • iadd は int 型の加算演算に使用されます。
  • ireturn は、戻り値が int であるメソッド戻り命令です。

オペランドのデータ型はバイトコード命令と一致する必要があります。上記の iadd 命令を例に挙げます。この命令は整数データの加算演算にのみ使用できます。実行時には、スタックの先頭にある 2 つのデータは int 型である必要があります。 iadd コマンドを使用して long 型および double 型のデータを追加することはできません。

3) 動的リンク

各スタック フレームには、ランタイム定数プール内でスタック フレームが属するメソッドへの参照が含まれています。この参照は、メソッド呼び出しプロセス中の動的リンクをサポートするために保持されます。

次のコードを見てみましょう。

  1. パブリッククラスDynamicLinking{
  2. 静的抽象クラス Human {
  3. 保護された抽象 void sayHello();
  4. }
  5.      
  6. 静的クラス Man は Human を拡張します {
  7. @オーバーライド
  8. 保護されたvoid sayHello() {
  9. システム。 out .println( "男が泣くことは罪ではない" );
  10. }
  11. }
  12.      
  13. 静的クラス Woman は Human を拡張します {
  14. @オーバーライド
  15. 保護されたvoid sayHello() {
  16. システム。 out .println( "山のふもとにいる女性は虎です" );
  17. }
  18. }
  19.  
  20. 公共 静的void main(String[] args) {
  21. 人間 man = new Man();
  22. 人間の女性 = 新しい Woman();
  23. 男はこんにちはと言います。
  24. 女性.sayHello();
  25. 男性 = 新しい女性();
  26. 男はこんにちはと言います。
  27. }
  28. }

Java の書き換えに関する知識があれば、このコードの意味を理解できるはずです。 Man クラスと Woman クラスは Human クラスを継承し、 sayHello() メソッドをオーバーライドします。実行結果を見てみましょう:

  1. 男性が泣くことは罪ではありません。
  2. 山のふもとにいる女性は虎です
  3. 山のふもとにいる女性は虎です

この操作の結果は簡単に理解できます。 man の参照型は Human ですが、Man オブジェクトを指しています。女性の参照型も Human ですが、これは Woman オブジェクトを指します。その後、男性は新しい女性オブジェクトを指さします。

オブジェクト指向プログラミングとポリモーフィズムの観点から、実行結果を非常によく理解できます。しかし、Java 仮想マシンの観点からは、男性と女性がどのメソッドを呼び出すべきかをどのように決定するのでしょうか?

jclasslib を使用して、メイン メソッドのバイトコード命令を確認します。

  • 1 行目: 新しい命令は Man オブジェクトを作成し、オブジェクトのメモリ アドレスをスタックにプッシュします。
  • 2 行目: dup 命令はスタックの一番上の値をコピーし、スタックの一番上にプッシュします。次の命令であるinvokespecialは現在のクラスへの参照を消費するため、コピーが必要です。
  • 3 行目:invokespecial 命令は、初期化のためにコンストラクターを呼び出すために使用されます。
  • 4 行目: astore_1、Java 仮想マシンはスタックの先頭から Man オブジェクトの参照をポップし、インデックス 1 のローカル変数 man に格納します。
  • 5 行目、6 行目、7 行目、8 行目の命令は、Woman オブジェクトを除いて、1 行目、2 行目、3 行目、4 行目の命令と似ています。
  • 9 行目: aload_1 命令は、ローカル変数 man をオペランド スタックにプッシュします。
  • 10 行目:invokevirtual 命令は、オブジェクトのメンバー メソッド sayHello() を呼び出します。この時点でのオブジェクト タイプは com/itwanger/jvm/DynamicLinking$Human であることに注意してください。
  • 11 行目: aload_2 命令は、ローカル変数 woman をオペランド スタックにプッシュします。
  • 12行目は10行目と同じです。

バイトコードの観点から見ると、man.sayHello() (行 10) と woman.sayHello() (行 12) のバイトコードはまったく同じですが、これら 2 つの命令によって最終的に実行されるターゲット メソッドが異なることは誰もが知っています。

どうしたの?

ポリモーフィズムがどのように実装されるかを確認するには、invokevirtual 命令から始める必要があります。 Java 仮想マシン仕様によれば、invokevirtual 命令の実行時解析プロセスは次のステップに分けられます。

①.オペランド スタックの最上部にある要素 (C で示される) が指すオブジェクトの実際の型を検索します。

②定数プール内の記述子に一致するメソッドがC型に見つかった場合、アクセス許可チェックが行われます。合格すると、このメソッドへの直接参照が返され、検索は終了します。それ以外の場合は、java.lang.IllegalAccessError 例外が返されます。

③.それ以外の場合は、継承関係に従って C の各親クラスを下から上に検索して検証する 2 番目の手順を実行します。

④.適切なメソッドが見つからない場合は、java.lang.AbstractMethodError 例外がスローされます。

つまり、invokevirtual 命令は最初のステップで実行時に実際の型を決定します。したがって、2 つの呼び出しの invokevirtual 命令は、定数プール内のメソッドのシンボリック参照を直接参照に解決するだけでなく、メソッド レシーバーの実際の型に基づいてメソッド バージョンを選択します。このプロセスは、Java 書き換えの本質です。実行時に実際の型に基づいてメソッド実行バージョンを決定するこのプロセスを、動的リンクと呼びます。

4) メソッドの戻りアドレス

メソッドの実行が開始されると、メソッドを終了する方法は 2 つしかありません。

通常の終了では、上位レベルのメソッド呼び出し元に戻り値が渡される場合があります。メソッドに戻り値があるかどうか、また戻り値の型は、メソッドによって返される命令によって決まります。たとえば、ireturn は int 型を返すために使用され、return は void メソッドに使用されます。他にも、long 型の lreturn、float 型の freturn、double 型の dreturn、参照型の areturn などがあります。

異常終了: メソッドの実行中に例外が発生し、適切に処理されませんでした。この場合、上位レベルの呼び出し元には値は返されません。

終了方法に関係なく、メソッドが終了した後、プログラムが実行を続行する前に、メソッドが最初に呼び出された場所に戻る必要があります。一般的に、メソッドが正常に終了すると、PC カウンタの値が戻りアドレスとして使用され、このカウンタの値はスタック フレームに保存される可能性が高くなりますが、異常終了した場合は保存されません。

メソッド終了のプロセスは、実際には現在のスタック フレームをポップすることと同じなので、次に実行される操作は、上位メソッドのローカル変数テーブルとオペランド スタックの復元、戻り値 (ある場合) を呼び出し元スタック フレームのオペランド スタックにプッシュ、PC カウンターの値の調整、次に実行される命令の検索などです。

この記事はWeChatの公式アカウント「沈黙王二」から転載したもので、以下のQRコードからフォローできます。この記事を転載する場合は、Silent King Erの公式アカウントまでご連絡ください。

<<:  データ テクノロジーの過去と現在を明らかにする、Techo TVP 開発者カンファレンスが開催されます。

>>:  クラウドコンピューティングの PAAS と SAAS の違いを 1 つの記事で理解する

推薦する

「下品と著作権」の原罪:荀雷は快博の師であり、百度は嫉妬している

iHeima は、現在の Web サイト、ダウンロード ソフトウェア、ソーシャル ソフトウェアのほと...

「2021 年の調査データの傾向を見て、企業のデジタル変革におけるクラウドの道を解釈する」

クラウド コンピューティングは、当初の構想から今日の広範な実装に至るまで、企業向けクラウド コンピュ...

ピークサーバー - 6.99ドル/KVM/フェニックス/1gメモリ/25gハードディスク/1Tトラフィック

最近、peakservers の広告が多数あり、さまざまな場所で見かけます。この会社についての紹介は...

ウェブサイトのソースコードを使用してウェブサイトを素早く構築する方法

はじめに: ウェブサイトのソース コードを使用してウェブサイトを構築することは、ウェブサイトを構築す...

昇る太陽か、衰える太陽か?We-mediaは今後の発展の道について考えたことがあるだろうか?

国内の自主メディアが新星であろうと、すでに衰退期に入っているかにかかわらず、自主メディア連盟リストは...

分類サイトの進化: 緩やかな成長から激しい競争へ

コンテンツ紹介:分類情報サイトはもはや魅力的ではない、58.com CEOのヤオ・ジンボはIPOに賭...

仮想化は仮想イメージで構成されます。基本的な仮想イメージを作成するにはどうすればよいですか?

仮想化テクノロジーは、データセンター管理に多くの利点をもたらします。一方では、インフラストラクチャの...

Appleの検索連想ワードが再びASOのホットスポットに。表示ロジックとは?

一昨日、検索連想語の共有に関する記事が界隈で大きな話題となり、全国で白熱した議論が巻き起こりました。...

orangewebsite: アイスランド サーバー、アイスランド VPS、アイスランド ホスティング

2006 年に設立された orangewebsite は、仮想ホスティング、VPS、専用サーバーを運...

適切なロングテールキーワードを拡張するためのウェブマスター向けの3つのヒント

メインキーワードの選択については、ほとんどのウェブマスターがその方法をご存知でしょう。ただし、ウェブ...

cloudcone: クリスマス オファー、米国 VPS は年間 15 ドルから、月間 5T のトラフィック、1Gbps の帯域幅

これは今年最後のプロモーションになるのでしょうか? Cloudcone はクリスマス プロモーション...

Discuz! 共同 A5 推奨事項: 訪問者を簡単に会員に変える

8 月 6 日の Web マスター ネットワーク ニュース: Discuz! X2.5 の正式リリー...

gcore トルコ VPS はどうですか? gcore イスタンブール データセンターの VPS の簡単なレビュー

gcore トルコ VPS はどうですか? Gcore は、中東のトルコに、デフォルトで 200Mb...