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

推薦する

画像読み取りの時代: ソーシャルネットワークは「視覚化」に向かっている

Pinterestのような画像ソーシャルネットワーキングサイトの台頭は、ソーシャルネットワークの視覚...

regxa: 高セキュリティ VPS、月額 2.5 ドル、1G メモリ/1 コア/15g NVMe/1Gbps 帯域幅

Regxaは2017年9月30日にソフトウェア会社として英国で登録されましたが、公式サイトに掲載され...

クラウドファーストのプロセス統合がクラウド アプリケーションに最適なのはなぜですか?

パンデミックにより、企業のデジタル技術への支出が増加し続け、デジタルとクラウドの導入が加速しました。...

オレンジクラウドが2021年CIFTISに初登場、中国工業企業の発展に新たな活力を注入

9月2日、「デジタルが未来を切り開き、サービスが発展を促進する」をテーマにした2021年中国国際サー...

ウェブサイト構築には権威あるコンテンツマーケティングが必要

インターネット時代は、まるで山から降りてきた虎のように急速に発展しており、インターネットの発展のスピ...

デジタル変革における人材の道を議論する、テンセントクラウド「人材プログラム」企業新技術実践クラウドサロン北京駅が開催されました

サロンイベントでは、中国ソフトウェア産業協会企業製品クラウドサービス支部秘書長、中国ソフトウェアネッ...

Google ADSense 配信プラン

ウェブマスター向けのウェブサイトであるため、GG広告を掲載する際には「推奨」を重視し、オンライン収益...

正しい姿勢があれば、SEO実践者は将来の課題に冷静に立ち向かうことができる

昨夜の百度の微調整は、新年を迎えて落ち着かない気分だった私に一筋の希望を与え、ここ数日の私の懸命な努...

Uber の大規模決済システムの構築中に学んだ分散アーキテクチャの概念

[[243320]] 2年前、私はバックエンド開発経験を持つモバイルソフトウェアエンジニアとしてUb...

適切な最適化を行えば、個人のウェブマスターが月に10,000元以上を稼ぐことも夢ではありません。

個人ウェブマスターは非常に負け犬の肩書きだと思っている人が多いですが、負け犬でも再起できます。個人ウ...

分散システムにおける「スプリットブレイン」とは一体何でしょうか?

[[413929]]この記事はWeChatの公開アカウント「New Vision of Progra...

Amazon Cloud は、「コンピューティングパワーの爆発的増加 + グローバルレイアウト」の課題に対応するために、技術革新を続けています。

今日の世界を見ると、前例のない課題が存在します。イノベーションに注力することによってのみ、さらなる成...

Kingsoft Cloud がクラウド上に「データシードウェアハウス」を構築する新しいアーカイブストレージ製品をリリース

はるか北極圏にあるスヴァールバル諸島の「最後の審判」種子貯蔵庫には、世界中から集められた何百万もの作...