みなさんこんにちは、私はいつまでも18歳のおばけです~ 「JVM メモリ領域の分割」の記事から、Java 仮想マシンのメモリ領域は、プログラム カウンター、Java 仮想マシン スタック、ネイティブ メソッド スタック、ヒープという 4 つの領域に分割できることがわかりました。今日は、これらの領域の 1 つである Java 仮想マシン スタックについて詳しく見ていきます。 まず説明させてください。この記事のタイトルには「Ctrip interviewer」という単語が含まれていますが、これはクリックベイトのタイトルである可能性があります。しかし、正直に言うと、前回の記事に読者がメッセージを残して、Ctrip のインタビュアーが Java 仮想マシンのメモリに関するいくつかの知識ポイントを質問したと書いていたので、今日のタイトルではそのトピックを「拝借」しました。 「もっと早く会えなかったのは残念です」という言葉から、私はこの読者がこのインタビューの質問に失敗するだろうと推測しました。言い換えれば、面接官は Java 仮想マシンの知識ポイントについて質問するのが好きです。それは、応募者の真の能力をテストできるためです。そのため、このトピックについてさらにいくつかの記事を書いて、皆さんにもう少しお役に立てればと思っています。 Java 仮想マシンはメソッドを基本的な実行単位として使用し、「スタック フレーム」はメソッドの呼び出しと実行を Java 仮想マシンにサポートするために使用される基本的なデータ構造です。各スタック フレームには、ローカル変数テーブル、オペランド スタック、動的リンク、メソッドの戻りアドレス、およびいくつかの追加情報 (デバッグやパフォーマンス監視に関連する情報など) が含まれます。これらの概念は以前の記事でも触れられており、簡単に紹介されていますが、詳細が足りないと思うので、この記事ではスタック フレームでのこれらの概念の紹介に重点を置きます。 1) ローカル変数テーブルローカル変数テーブルは、メソッド内のローカル変数とメソッド パラメータを格納するために使用されます。 Java ソース コード ファイルをクラス ファイルにコンパイルすると、ローカル変数テーブルの最大容量が決定されます。 そのようなコードを見てみましょう。
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 型です。 もちろん、ローカル変数テーブルのサイズは、メソッド内のすべてのローカル変数の数の合計ではありません。これは変数の型とスコープに関係します。ローカル変数のスコープが終了すると、ローカル変数テーブル内でその変数が占めていた位置は次のローカル変数に置き換えられます。 次のコードを見てみましょう。
ローカル変数のスコープについては、Effective Java のヒント 57 を参照してください。 ローカル変数のスコープを最小限に抑えると、コードの可読性と保守性が向上し、エラーの可能性が減ります。 ここで、一つ思い出していただきたいことがあります。スタック フレームによって消費されるメモリ領域をできるだけ節約するために、method() メソッドで示されているように、ローカル変数テーブル内のスロットを再利用できます。つまり、適切なスコープはプログラムのパフォーマンスの向上に役立ちます。 ローカル変数テーブルの容量は、最小単位であるスロットで測定されます。スロットは 32 ビットのデータ型 (int など) を保持できます。もちろん、Java 仮想マシン仕様ではスロットが占有するメモリ空間のサイズは明示的には規定されていませんが、この方が理解しやすいと思います。 float や double など、明示的に 64 ビットを占有するデータ型は、隣接する 2 つのスロットを占有します。 次のコードを見てみましょう。
jclasslib を使用すると、solt() メソッドの最大ローカル変数の値が 4 であることがわかります。 なぜ4になるのでしょうか?これを含めると3つだけでしょうか? LocalVaraiableTable を見ると、変数 i の添え字が 3 であることがわかります。つまり、変数 d は 2 つのスロットを占有します。 2) オペランドスタック ローカル変数テーブルと同様に、オペランド スタックの最大深度もコンパイル時に決定され、Code プロパティの最大スタック サイズに書き込まれます。メソッドの実行が開始されると、オペランド スタックは空になります。メソッドの実行中、さまざまなバイトコード命令がオペランド スタックにデータを書き込んだり、オペランド スタックからデータを取得したりします。これはプッシュ操作とポップ操作です。 次のコードを見てみましょう。
OperandStack クラスには 2 つのメソッドがあります。 add() メソッドは test() メソッド内で呼び出され、2 つのパラメータが渡されます。 jclasslib を使用すると、test() メソッドの最大スタック サイズが 3 であることがわかります。 これは、メンバー メソッドが呼び出されると、 this とすべてのパラメーターがスタックにプッシュされ、呼び出しが完了すると、 this とパラメーターがスタックから 1 つずつポップされるためです。対応するバイトコード命令は、「バイトコード」パネルから表示できます。 aload_0 は、ローカル変数テーブル内の添え字 0 の参照型変数、つまり this をオペランド スタックにロードするために使用されます。
add() メソッドのバイトコード命令を見てみましょう。
オペランドのデータ型はバイトコード命令と一致する必要があります。上記の iadd 命令を例に挙げます。この命令は整数データの加算演算にのみ使用できます。実行時には、スタックの先頭にある 2 つのデータは int 型である必要があります。 iadd コマンドを使用して long 型および double 型のデータを追加することはできません。 3) 動的リンク 各スタック フレームには、ランタイム定数プール内でスタック フレームが属するメソッドへの参照が含まれています。この参照は、メソッド呼び出しプロセス中の動的リンクをサポートするために保持されます。 次のコードを見てみましょう。
Java の書き換えに関する知識があれば、このコードの意味を理解できるはずです。 Man クラスと Woman クラスは Human クラスを継承し、 sayHello() メソッドをオーバーライドします。実行結果を見てみましょう:
この操作の結果は簡単に理解できます。 man の参照型は Human ですが、Man オブジェクトを指しています。女性の参照型も Human ですが、これは Woman オブジェクトを指します。その後、男性は新しい女性オブジェクトを指さします。 オブジェクト指向プログラミングとポリモーフィズムの観点から、実行結果を非常によく理解できます。しかし、Java 仮想マシンの観点からは、男性と女性がどのメソッドを呼び出すべきかをどのように決定するのでしょうか? jclasslib を使用して、メイン メソッドのバイトコード命令を確認します。
バイトコードの観点から見ると、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は2017年9月30日にソフトウェア会社として英国で登録されましたが、公式サイトに掲載され...
パンデミックにより、企業のデジタル技術への支出が増加し続け、デジタルとクラウドの導入が加速しました。...
この記事は、Titanium Mediaの独占コラム「Corporate Relativity」シリ...
9月2日、「デジタルが未来を切り開き、サービスが発展を促進する」をテーマにした2021年中国国際サー...
インターネット時代は、まるで山から降りてきた虎のように急速に発展しており、インターネットの発展のスピ...
サロンイベントでは、中国ソフトウェア産業協会企業製品クラウドサービス支部秘書長、中国ソフトウェアネッ...
ウェブマスター向けのウェブサイトであるため、GG広告を掲載する際には「推奨」を重視し、オンライン収益...
昨夜の百度の微調整は、新年を迎えて落ち着かない気分だった私に一筋の希望を与え、ここ数日の私の懸命な努...
[[243320]] 2年前、私はバックエンド開発経験を持つモバイルソフトウェアエンジニアとしてUb...
個人ウェブマスターは非常に負け犬の肩書きだと思っている人が多いですが、負け犬でも再起できます。個人ウ...
[[413929]]この記事はWeChatの公開アカウント「New Vision of Progra...
IDC Review Network (idcps.com) は3月20日に次のように報じた。海外ド...
今日の世界を見ると、前例のない課題が存在します。イノベーションに注力することによってのみ、さらなる成...
はるか北極圏にあるスヴァールバル諸島の「最後の審判」種子貯蔵庫には、世界中から集められた何百万もの作...