11

どちらの命令も、動的ディスパッチではなく静的ディスパッチを使用します。唯一の大きな違いはinvokespecial、最初の引数として、ディスパッチされたメソッドが属するクラスのインスタンスであるオブジェクトが常に存在することです。ただし、invokespecial実際にはオブジェクトをそこに配置しません。コンパイラは、を発行する前にスタック操作の適切なシーケンスを発行することによってそれを実現する責任がありますinvokespecial。したがって、に置き換えinvokespecialinvokestaticも、ランタイムスタック/ヒープの操作方法に影響はありませんがVerifyError、仕様に違反する原因になると思います。

本質的に同じことを行う2つの異なる命令を作成する背後にある考えられる理由について興味があります。OpenJDKインタープリターのソースを調べたところ、ほとんど同じように処理されinvokespecialているようです。invokestatic2つの別々の命令があると、JITコンパイラがコードをより適切に最適化するのに役立ちますか、それともクラスファイルベリファイアがいくつかの安全プロパティをより効率的に証明するのに役立ちますか?それとも、これはJVMの設計の単なる癖ですか?

4

3 に答える 3

3

免責事項:これについての明示的なOracleの声明を読んだことがないので、確実に言うのは難しいですが、これが理由だと思います:

Java バイト コードを見ると、他の命令について同じ質問をすることができます。スタックに2 つの s をプッシュし、その直後にintそれらを 1 つとして扱うと、検証者が停止するのはなぜですか? long(試してみてください。停止します。)これを許可することで、同じロジックをより小さな命令セットで表現できると主張できます。(この議論をさらに進めると、1 バイトはあまりにも多くの命令を表現できないため、Java バイト コード セットは可能な限り削減する必要があります。)

もちろん、理論的には、ints とs をスタックにプッシュするためのバイト コード命令は必要なく、メソッド呼び出しを表現するためにlong2 つの命令を必要としないという事実については正しいですINVOKESPECIALメソッドはそのメソッド記述子INVOKESTATIC(名前と未加工の引数の型)によって一意に識別され、同じクラス内で同じ記述を持つ静的メソッドと非静的メソッドの両方を定義することはできませんでした。また、バイト コードを検証するために、Java コンパイラはターゲット メソッドが有効かどうかを確認する必要があります。static

備考: これは v6ak の回答と矛盾します。ただし、非静的メソッドのメソッド記述子は、 への参照を含むように変更されませんthis.getClass()。したがって、Java ランタイムは、仮想INVOKESMART命令のメソッド記述子から適切なメソッド バインディングを常に推測できます。JVMS §4.3.3 を参照してください。

理論は以上です。ただし、両方の呼び出しタイプで表現される意図はまったく異なります。また、Java バイトコードは、 javac以外のツールでもJVM アプリケーションを作成するために使用されることになっていることを思い出してください。これらのツールは、バイト コードを使用して、Java ソース コードよりもマシン コードに似たものを生成します。しかし、それはまだかなり高いレベルです。たとえば、バイト コードは引き続き検証され、バイト コードはマシン コードにコンパイルされるときに自動的に最適化されます。ただし、バイトコードは、意味を持たせるために意図的に冗長性を含む抽象化です。バイトコードのより明示的。また、Java 言語が言語を読みやすくするために類似のものに異なる名前を使用しているように、バイトコード命令セットにも冗長性が含まれています。また、別の利点として、メソッドの呼び出しタイプを常に推測する必要はなく、バイト コードで明示的に記述されているため、検証とバイト コードの解釈/コンパイルを高速化できます。検証、解釈、およびコンパイルは実行時に行われるため、これは望ましいことです。

最後の逸話として、Java 5 より前はクラスの静的イニシャライザーに<clinit>フラグが立てられていなかったことに言及する必要がありstaticます。このコンテキストでは、メソッドの名前から静的呼び出しを推測することもできますが、これはさらに多くの実行時のオーバーヘッドを引き起こします。

于 2013-11-29T23:56:49.630 に答える
2

定義があります:


大きな違いがあります。との間でinvokesmart賢く選択する命令を設計したいとします。inkovestaticinvokespecial

まず、静的呼び出しと仮想呼び出しを区別することは問題ではありません。1つが静的で、2つ目が仮想であっても、同じ名前、同じパラメータータイプ、同じリターンタイプの2つのメソッドを持つことはできないからです。JVMはそれを許可しません(奇妙な理由で)。それに気づいてくれてありがとうraphw。

まず、invokesmartfoo/Bar.baz(I)Iはどういう意味ですか?それは意味するかもしれません:

  • オペランドスタックからfoo.Bar.baz消費し、別のを追加する静的メソッド呼び出し。intint// (int) -> (int)
  • オペランドスタックfoo.Bar.bazを消費foo.Barしてから、を追加するインスタンスメソッド呼び出し。intint// (foo.Bar, int) -> (int)

それらからどのように選択しますか?両方の方法が存在する可能性があります。

foo/Bar.baz(Lfoo/Bar;I)静的呼び出しを要求することによってそれを解決しようとするかもしれません。ただし、との両方がある場合がpublic static int baz(Bar, int)あり public int baz(int)ます。


それは問題ではなく、おそらくそのような状況を無効にすると言うかもしれません。(それは良い考えではないと思いますが、想像するだけです。)それはどういう意味ですか?

  • メソッドが静的である場合、おそらく追加の制限はありません。一方、メソッドが静的でない場合は、いくつかの制限があります。「最後に、解決されたメソッドが保護されていて(§4.6)、現在のクラスのメンバーまたは現在のスーパークラスのメンバーである場合classの場合、objectrefのクラスは、現在のクラスまたは現在のクラスのサブクラスのいずれかである必要があります。」
  • さらにいくつかの違いがあります。に関する注記を参照してくださいACC_SUPER
  • これは、バイトコード検証の前に、参照されているすべてのクラスをロードする必要があることを意味します。これが今は必要ないことを願っていますが、100%確信はありません。

したがって、それは非常に一貫性のない動作を意味します。

于 2013-01-06T16:16:54.563 に答える