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 つの記事で理解する

推薦する

エッジコンピューティングは多様なアプリケーションに拡張されます

エッジ コンピューティングにより、分散コンピューティング インフラストラクチャを通じて、コンピューテ...

クラウドコンピューティング業界2018年半期レポート:全体的な状況は決まり、変数はまだ存在し、新しい軌道が出現する

あっという間に2018年も半分が過ぎ、総括する時期が来ました。 2018年上半期を振り返ると、徐々に...

エッジコンピューティングはヘルスケア業界に明るい未来をもたらす

ヘルスケア業界の重要性が日々高まる中、エッジ コンピューティングの導入は、この業界により良い、より明...

画像サイトのコンテンツと画像記事のレイアウトを最適化するためのポイント

多くの画像ウェブサイト、特に美しい写真を表示するウェブサイトは、Baidu のキーワードランキングに...

ユーザーエクスペリエンスを無視すると、サイトは失敗する運命にある

今日のウェブマスター サークルは急速に発展しており、サークルに参加する人が増え、誰もがこの小さな領域...

検索エンジン最適化における投票の原則と運用上のエラーについての簡単な説明

インターネットの急速な発展に伴い、近年SEO業界の競争はますます激しくなっています。Baiduのアル...

リベート ウェブサイトの徹底調査: Wanjia Shopping が「ポンジー」詐欺を開始した方法

衝撃的な知らせを聞いた潘阿成は台州から金華へ急いだ。浙江省金華市のイノベーション国際ビルの広場の入り...

デコレーションマップのウェブサイトで1ヶ月間のサイト最適化の体験を共有する

1か月間、サイトを中心に最適化を行った後、ランキングは急速に上昇し、トラフィックは5〜6倍に増加しま...

minivps 1Gメモリ 4コア 月額7ドル データセンター3つ(オプション)

minivps は英国の VPS 販売業者であり、XAVVO のサブブランドです。Xavvo Ltd...

ウェブサイトのタイトルのキーワードを正しく書くにはどうすればいいですか?

Baidu の検索エンジン最適化では、ウェブサイトのタイトルの重要性は明らかです。ウェブサイトの t...

専門家相談12306:ビジネスモデルから製品設計まで、プロではない

国慶節の連休が近づくにつれ、列車のチケットとその背後にある鉄道予約サイト12306が引き続き話題にな...

電子商取引戦争が迫る、必見のウェブデザインボード5選

毎年恒例の電子商取引プロモーションイベントが近づいてきました。昨年の天猫ダブル11は、私たちの記憶に...

米国防総省はクラウドコンピューティング契約の入札を再開し、アマゾン、グーグル、マイクロソフトが参加するよう招待された。

現地時間金曜日、政府調達を担当する米国一般調達局(GSA)は、米国防総省がアマゾン、グーグル、マイク...

戴志康:O2Oは私を不安にさせ、苦痛にさせる

以下は、テンセント電子商取引持株会社およびディスカスの創設者である戴志康氏がプロダクトホームサロンで...

2023年にはクラウド導入の増加によりビジネス価値が増大

クラウド コンピューティングは長年にわたり、分散コンピューティング機能の拡張、ソフトウェアと技術の進...