ソースコードをJavaバイトコードに変換する用途は何ですか?


37

アーキテクチャごとに異なるJVMが必要な場合、この概念の導入の背後にあるロジックはわかりません。他の言語ではマシンごとに異なるコンパイラが必要ですが、Javaでは異なるJVMが必要なため、JVMの概念やこの追加ステップの導入の背後にあるロジックは何ですか?



12
@gnat:実際には、それは複製ではありません。これは「ソースとバイトコード」、つまり最初の変換です。言語的には、これはJavascript対Javaです。リンクはC ++対Javaになります。
–MSalters

2
アップグレードのためにデジタルコーディングを追加する50のアプライアンスモデル、または50の異なるハードウェアに50のコンパイラーを追加するために、単純なバイトコードインタープリターを作成します。Javaはもともと、アプライアンスと機械用に開発されました。それはその強力なスーツでした。これらの回答を読むときは、Javaには真の利点がありません(解釈プロセスの非効率性のため)。これは、引き続き使用するモデルです。
グレートダック

1
仮想マシンとは何かを理解していないようです。そのマシン。ネイティブコードコンパイラを使用してハードウェアで実装できます(JVMの場合は実装されています)。ここで重要なのは「仮想」部分です。つまり、基本的にそのアーキテクチャを別のアーキテクチャの上にエミュレートしています。x86で実行する8088エミュレーターを作成したとします。古い8088コードをx86に移植するのではなく、エミュレートされたプラットフォームで実行するだけです。JVMは、他のプラットフォームと同様にターゲットとするマシンであり、違いは他のプラットフォーム実行されることです。
ジャレッド・スミス

7
@TheGreatDuck通訳プロセス?現在、ほとんどのJVMはマシンコードに対してジャストインタイムコンパイルを実行しています。「解釈」が最近ではかなり広い用語であることは言うまでもありません。CPU自体は、x86コードを独自の内部マイクロコードに「解釈」するだけで、効率を改善するために使用されます。最新のIntel CPUは、通訳者全般にも非常に適しています(もちろん、証明したいことを証明するベンチマークを見つけることができます)。
ルアーン

回答:


79

ロジックは、JVMバイトコードはJavaソースコードよりもはるかに単純だということです。

コンパイラは、非常に抽象的なレベルでは、解析、セマンティック分析、コード生成の3つの基本的な部分を持っていると考えることができます。

解析は、コードを読み取り、それをコンパイラのメモリ内でツリー表現に変換することで構成されます。セマンティック分析は、このツリーを分析し、その意味を理解し、すべての高レベルの構造を低レベルの構造まで簡素化する部分です。また、コード生成では、単純化されたツリーを取得して、フラット出力に書き込みます。

バイトコードファイルを使用すると、再帰(ツリー構造)ソース言語ではなく、JITが使用するのと同じフラットバイトストリーム形式で記述されるため、解析フェーズが大幅に簡素化されます。また、セマンティック分析の多くの手間のかかる作業は、Java(または他の言語)コンパイラーによってすでに実行されています。したがって、コードをストリーム読み取りし、最小限の解析と最小限のセマンティック分析を行ってから、コード生成を実行するだけです。

これにより、理論的には単一ソースのクロスプラットフォームコードを記述することを可能にする高レベルのメタデータとセマンティック情報を保持しながら、JITが実行する必要のあるタスクが非常に単純になり、したがって実行がはるかに高速になります。


7
SafeTCLなど、アプレット配布の初期の試みのいくつかは、実際にソースコードを配布しました。Javaがシンプルで厳密に指定されたバイトコードを使用することにより、プログラムの検証がはるかに扱いやすくなり、それが解決された困難な問題でした。pコードなどのバイトコードは、移植性の問題の解決策の一部としてすでに知られていました(そして、当時ANDFはおそらく開発中でした)。
トビースパイト

9
正確に。バイトコード->マシンコードステップのため、Javaの起動時間はすでに少し問題になっています。(自明ではない)プロジェクトでjavacを実行し、起動するたびにJava->マシンコード全体を実行することを想像してください。
ポールドレイパー

24
もう1つの大きな利点があります:いつか仮想の新しい言語に切り替えたい場合-「Scala」と呼びましょう-何十ものScala->マシンコードではなく、1つのScala->バイトコードコンパイラを記述するだけです。コンパイラ。おまけとして、JVMのプラットフォーム固有の最適化がすべて無料で提供されます。
BlueRaja-ダニーPflughoeft

8
テールコールの最適化など、JVMバイトコードではまだ不可能なことがあります。これは、JVMにコンパイルされる機能言語を大きく損なうことを思い出します。
JDługosz

8
@JDługoszright:残念ながらJVMはかなりの制限/設計イディオムを課していますが、それは命令型言語から来ている場合は完全に自然かもしれませんが、基本的に機能する言語のコンパイラを作成したい場合は非常に人工的な障害になります違います。したがって、LLVMはfuture-language-work-reuseに関する限り、より良いターゲットであると考えます。これには制限もありますが、現在の(そしておそらく将来の)プロセッサーがとにかく制限にほぼ一致します。
leftaround

27

さまざまな種類の中間表現は、いくつかの理由により、コンパイラ/ランタイム設計でますます一般的になっています。

Javaの場合、最初の最大の理由はおそらくポータビリティでした。Javaは当初「Write Once、Run Anywhere」として大々的に販売されていました。これを実現するには、ソースコードを配布し、さまざまなコンパイラを使用してさまざまなプラットフォームをターゲットにしますが、これにはいくつかの欠点があります。

  • コンパイラは、言語のすべての便利な構文を理解する必要がある複雑なツールです。バイトコードは、人間が読み取れるソースよりもマシン実行可能コードに近いため、よりシンプルな言語にすることができます。これの意味は:
    • バイトコードの実行に比べてコンパイルが遅い場合があります
    • 異なるプラットフォームをターゲットとするコンパイラは、異なる動作を生成するか、言語の変更に追いつかない可能性があります
    • 新しいプラットフォーム用のコンパイラの作成は、そのプラットフォーム用のVM(またはバイトコードからネイティブコンパイラ)の作成よりもはるかに困難です
  • ソースコードを配布することが常に望ましいとは限りません。バイトコードは、リバースエンジニアリングに対する保護を提供します(ただし、意図的に難読化しない限り、逆コンパイルは依然としてかなり簡単です)

中間表現の他の利点は次のとおりです。

  • 最適化。パターンをバイトコードで見つけて、より高速の同等物にコンパイルするか、プログラムの実行時に特殊なケースに最適化することもできます(「JIT」または「Just In Time」コンパイラーを使用)
  • 同じVM内の複数の言語間の相互運用性。これはJVM(Scalaなど)で人気が出ており、.netフレームワークの明確な目的です。

1
Javaは組み込みシステムにも適応していました。このようなシステムでは、ハードウェアにメモリとCPUの制約がいくつかありました。
ライヴ

最初にJavaソースコードをバイトコードにコンパイルし、次にバイトコードをマシンコードにコンパイルする方法でコンパイラを開発できますか?それはあなたが言及したほとんどの欠点を排除しますか?
Sher10ck

@ Sher10ckはい、JVMバイトコードを特定のアーキテクチャのマシン命令に静的に変換するコンパイラを書くことは完全に可能です。しかし、ディストリビューターの余分な労力、またはユーザーが最初に使用する余分な時間のいずれかを上回るほどパフォーマンスが向上した場合にのみ意味があります。低電力の組み込みシステムが役立つ場合があります。最新のPCで多くの異なるプログラムをダウンロードして実行する場合は、適切に調整されたJITを使用する方がよいでしょう。Androidはこの方向のどこかに行くと思いますが、詳細はわかりません。
IMSoP

8

なぜソースコードを配布しないのかと疑問に思っているようですね。その質問を振り返ってみましょう:なぜマシンコードを配布しないのですか?

明らかに、ここでの答えは、Javaは設計上、コードが実行されるマシンが何であるかを知っているとは想定していないということです。デスクトップ、スーパーコンピューター、電話、またはその中間とそれ以上のあらゆるものです。Javaには、ローカルJVMコンパイラーが処理を行う余地があります。コードの移植性を高めることに加えて、これは、コンパイラーがマシン固有の最適化が存在する場合はそれを利用し、存在しない場合は少なくとも機能するコードを生成するなどのことをコンパイラーに許可するという素晴らしい利点があります。以下のようなものSSEの命令またはハードウェアアクセラレーションは、それらをサポートする唯一のマシンで使用することができます。

この観点から見ると、生のソースコードではなくバイトコードを使用する理由はより明確です。生の機械語に可能な限り近づけることで、次のような機械コードの利点の一部を実現または部分的に実現できます。

  • コンパイルと分析の一部が既に行われているため、起動時間が短縮されます。
  • セキュリティ。バイトコード形式には配布ファイルに署名するためのメカニズムが組み込まれているため(ソースは慣例によりこれを行うことができますが、これを実現するメカニズムはバイトコードのように組み込まれていません)。

高速実行については言及していません。ソースコードとバイトコードの両方は、実際の実行のために同じマシンコードに完全にコンパイルされるか、理論的にはコンパイルされます。

さらに、バイトコードにより、マシンコードよりもいくつかの改善が可能になります。もちろん、前述したプラットフォーム非依存性とハードウェア固有の最適化がありますが、古いコードから新しい実行パスを生成するためにJVMコンパイラーにサービスを提供するようなものもあります。これは、セキュリティの問題にパッチを当てたり、新しい最適化が発見されたり、新しいハードウェアの指示を利用したりするためのものです。実際には、バグを公開する可能性があるため、このように大きな変更を確認することはめったにありませんが、それは可能であり、常に小さな方法で発生するものです。


8

ここには少なくとも2つの異なる質問が考えられます。1つは実際にはコンパイラ全般に関するもので、Javaは基本的にジャンルの単なる例です。もう1つは、使用する特定のバイトコードがJavaに固有です。

一般的なコンパイラ

最初に一般的な質問を考えてみましょう:コンパイラは、特定のプロセッサで実行するためにソースコードをコンパイルするプロセスで中間表現を使用するのはなぜですか?

複雑さの軽減

その答えの1つは非常に簡単です。O(N * M)問題をO(N + M)問題に変換します。

N個のソース言語とM個のターゲットが与えられ、各コンパイラが完全に独立している場合、それらすべてのソース言語をそれらすべてのターゲットに翻訳するN * Mコンパイラが必要です(「ターゲット」はプロセッサとOS)。

ただし、これらすべてのコンパイラーが共通の中間表現に同意する場合、ソース言語を中間表現に変換するNコンパイラーのフロントエンドと、中間表現を特定のターゲットに適したものに変換するMコンパイラーのバックエンドを持つことができます。

問題のセグメンテーション

さらに良いことには、問題を2つの多かれ少なかれ排他的なドメインに分離します。言語設計、構文解析などを知っている/気にする人はコンパイラのフロントエンドに集中でき、命令セット、プロセッサ設計などを知っている人はバックエンドに集中できます。

したがって、たとえば、LLVMのようなものが与えられた場合、さまざまな言語のフロントエンドがたくさんあります。また、多くの異なるプロセッサ用のバックエンドもあります。言語担当者は、自分の言語の新しいフロントエンドを作成し、多くのターゲットをすばやくサポートできます。プロセッサの開発者は、言語設計や解析などを扱うことなく、ターゲットの新しいバックエンドを作成できます。

コンパイラをフロントエンドとバックエンドに分離し、2つの間で通信するための中間表現を使用することは、Javaのオリジナルではありません。それは長い間かなり一般的な習慣でした(とにかくJavaが登場するかなり前から)。

分布モデル

この点でJavaが新しいものを追加した範囲では、Javaは分散モデルに含まれていました。特に、コンパイラは長い間内部的にフロントエンドとバックエンドに分かれていましたが、通常は単一の製品として配布されていました。たとえば、Microsoft Cコンパイラを購入した場合、内部には「C1」と「C2」があり、それぞれフロントエンドとバックエンドでしたが、購入したのは両方を含む「Microsoft C」だけでしたピース(2つの間の操作を調整する「コンパイラドライバー」を使用)。コンパイラーは2つの部分で構築されていますが、コンパイラーを使用する通常の開発者にとっては、ソースコードからオブジェクトコードに変換されるものは1つであり、間には何も表示されません。

代わりに、JavaはJava Development KitのフロントエンドとJava Virtual Machineのバックエンドを配布しました。すべてのJavaユーザーには、使用しているシステムを対象とするコンパイラバックエンドがありました。Java開発者はコードを中間形式で配布したため、ユーザーがそれをロードすると、JVMは特定のマシンで実行するために必要なことをすべて行いました。

前例

この分布モデルもまったく新しいものではないことに注意してください。たとえば、UCSD Pシステムも同様に機能しました。コンパイラフロントエンドはPコードを生成し、Pシステムの各コピーには、その特定のターゲットでPコードを実行するために必要なことを行う仮想マシンが含まれていました1

Javaバイトコード

JavaバイトコードはPコードに非常に似ています。基本的には、かなり単純なマシンの手順です。そのマシンは、既存のマシンを抽象化することを目的としているため、ほとんどすべての特定のターゲットにすばやく簡単に変換できます。P-Systemが行ったように、元の目的はバイトコードを解釈することだったので、翻訳の容易さは早い段階で重要でした(そして、そう、それはまさに初期の実装が機能した方法です)。

強み

コンパイラのフロントエンドは、Javaバイトコードを簡単に生成できます。(たとえば)式を表すかなり典型的なツリーがある場合、通常はツリーをたどることが非常に簡単で、各ノードで見つけたものからかなり直接コードを生成します。

Javaバイトコードは非常にコンパクトです。ほとんどの場合、ほとんどの典型的なプロセッサ(特に、SunがJavaの設計時に販売したSPARCなどのほとんどのRISCプロセッサ)のソースコードまたはマシンコードよりもはるかにコンパクトです。これは当時特に重要でした。Javaの主な目的の1つは、アプレット(実行前にダウンロードされるWebページに埋め込まれたコード)をサポートすることでした。 1キロビット/秒(もちろん、古い低速のモデムを使用している人はまだかなりいました)。

弱点

Javaバイトコードの主な弱点は、特に表現力がないことです。Javaに存在する概念をかなりうまく表現することはできますが、Javaの一部ではない概念を表現するためにはほとんどうまくいきません。同様に、ほとんどのマシンでバイトコードを実行するのは簡単ですが、特定のマシンを最大限に活用する方法でそれを行うのははるかに困難です。

たとえば、Javaバイトコードを本当に最適化したい場合、基本的にリバースエンジニアリングを行って、マシンコードのような表現から逆方向に変換し、SSA命令(または同様のもの)に戻します2。次に、SSA命令を操作して最適化を行い、そこから本当に気になるアーキテクチャをターゲットとするものに変換します。ただし、このやや複雑なプロセスであっても、Javaに馴染みのない一部の概念は表現が非常に難しく、一部のソース言語からほとんどの一般的なマシンで最適に実行される(ほぼ同じ)マシンコードに翻訳することは困難です。

概要

一般的に中間表現を使用する理由について質問している場合、2つの主要な要因があります。

  1. O(N * M)問題をO(N + M)問題に還元する
  2. 問題をより管理しやすい部分に分割します。

Javaバイトコードの詳細と、なぜ彼らが他のコードではなくこの特定の表現を選んだのかを尋ねているなら、答えはその当時のウェブの元の意図と制限に大きく戻っていると思います、次の優先順位につながります:

  1. コンパクトな表現。
  2. すばやく簡単にデコードおよび実行できます。
  3. ほとんどの一般的なマシンにすばやく簡単に実装できます。

多くの言語を表現したり、さまざまなターゲットで最適に実行したりできることは、はるかに低い優先度でした(それらが優先度であると見なされた場合)。


  1. それでは、なぜPシステムはほとんど忘れられているのでしょうか?主に価格設定の状況。P-systemは、Apple II、Commodore SuperPetsなどでかなりまともに販売されていました。IBMPCが登場したとき、P-systemはサポートされたOSでしたが、MS-DOSは低コストです(ほとんどの人の観点から、基本的に無料で投入されました) MicrosoftやIBM(など)が書いたものであるため、すぐに利用可能なプログラムが増えました。
  2. たとえば、これがすすの仕組みです。

Webアプレットに非常に近い:元の意図は、RPCが関数呼び出しを配布し、CORBAがオブジェクトを配布するのと同じ方法で、コードをアプライアンス(セットトップボックス...)に配布することでした。
ninjalj

2
これは素晴らしい答えであり、異なる中間表現が異なるトレードオフをどのように行うかについての良い洞察です。:)
IMSoP

@ninjalj:それは本当にオークでした。Javaに変身する頃には、セットトップボックス(および同様の)アイデアは棚上げされていたと思います(ただし、OakとJavaは同じものであるという公正な議論があることを認めたのは初めてです)。
ジェリー

@TobySpeight:ええ、表現はおそらくそこにより適しています。ありがとう。
ジェリー

0

他の人が指摘した利点に加えて、バイトコードははるかに小さいため、配布および更新が容易であり、ターゲット環境で占めるスペースが少なくなります。これは、スペースに大きな制約がある環境では特に重要です。

また、著作権で保護されたソースコードを簡単に保護できます。


2
Java(。多分、Webブラウザーのバイトコードを設定します。
LnxPrgr3

0

つまり、バイトコードからマシンコードへのコンパイルは、元のコードをマシンコードにジャストインタイムで解釈するよりも高速です。ただし、アプリケーションをクロスプラットフォームにするためには、変更や準備なしですべてのプラットフォームで元のコードを使用する必要があるため、解釈が必要です。そのため、最初にjavacはソースをバイトコードにコンパイルし、次にこのバイトコードをどこでも実行でき、Java Virtual Machineがマシンコードをより迅速に解釈します。答え:時間を節約できます。


0

当初、JVMは純粋なインタープリターでした。そして、あなたが通訳している言語が可能限りシンプルであれば、あなたは最高のパフォーマンスの通訳を手に入れます。これがバイトコードの目標でした。ランタイム環境に効率的に解釈可能な入力を提供することです。この単一の決定により、Javaはパフォーマンスによって判断されるように、インタプリタ言語よりもコンパイル言語に近づきました。

後になって、通訳JVMのパフォーマンスが依然として劣悪であることが明らかになったとき、人々はパフォーマンスの良いジャストインタイムコンパイラを作成するための努力を投資しました。これにより、CやC ++などの高速言語とのギャップがいくぶん解消されました。(ただし、Java固有の速度の問題がいくつか残っているため、適切に作成されたCコードと同等のパフォーマンスを発揮するJava環境を取得することはおそらくないでしょう。)

もちろん、ジャストインタイムのコンパイル技術を手に入れれば、実際にソースコードを配布し、マシンコードにジャストインタイムでコンパイルすることに戻ることができます。ただし、これにより、コードのすべての関連部分がコンパイルされるまで、起動パフォーマンスが大幅に低下します。バイトコードは、同等のJavaコードよりも解析がはるかに簡単であるため、ここでも大きな助けになります。


downvoterは説明する気にしてくださいだろう、なぜ
cmaster

-5

テキストソースコードは、人間が読みやすく、変更しやすい構造です。

バイトコードは、マシンで簡単に読み取って実行できるようにすることを目的とした構造です。

JVMがコードで行うことはすべて読み取られて実行されるため、バイトコードはJVMによる消費に適しています。

まだ例がなかったことに気づきました。愚かな擬似の例:

//Source code
i += 1 + 5 * 2 + x;

// Byte code
i += 11, i += x
____

//Source code
i = sin(1);

// Byte code
i = 0.8414709848
_____

//Source code
i = sin(x)^2+cos(x)^2;

// Byte code (actually that one isn't true)
i = 1

もちろん、バイトコードは最適化だけではありません。メソッドの大部分は、メソッドが「foo」を参照するときにファイル内のどこかに「foo」というメンバーが含まれているかどうかをチェックするなど、複雑なルールを気にせずにコードを実行できることです。


2
これらのバイトコード「例」は人間が読める形式です。それはまったくバイトコードではありません。これは誤解を招くだけでなく、尋ねられた質問にも対応していません。
ワイルドカード

@Wildcardあなたは、これがフォーラムであり、人間に読まれることを忘れているかもしれません。だから私は人間が読める形式でコンテンツを置いています。フォーラムはソフトウェアエンジニアリングに関するものであるため、読者に単純な抽象化の概念を理解してもらうことはあまり求めていません。
ピーター

人間が読める形式はバイトコードはなくソースコードです。あなたは説明されている表現あらかじめ計算して、ソースコードを しないコード、バイト。そして、私はこれが人間が読めるフォーラムであることを見逃しませんでした。あなたは、私ではなく、バイトコードの例を含めないことで他の回答者を批判した人です。「例がまだないことに気づいた」と言ってから、バイトコードをまったく示していない例以外の例を示します。そして、これはまだ質問にまったく対処していません。質問を読み直してください。
ワイルドカード
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.