Javaクラスファイルの作成は確定的ですか?


94

同じJDK(つまり、同じjavac実行可能ファイル)を使用する場合、生成されたクラスファイルは常に同じですか?オペレーティングシステムハードウェアによって違いはありますか?JDKのバージョンを除いて、違いが生じるその他の要因はありますか?違いを避けるためのコンパイラオプションはありますか?違いは理論的にのみ可能ですか、それともOracleはjavac実際には同じ入力とコンパイラオプションに対して異なるクラスファイルを生成しますか?

Update 1 生成、つまりコンパイラの出力に興味があります。クラスファイルをさまざまなプラットフォームで実行できるかどうかではありません。

Update 2「同じJDK」とは、同じjavac実行可能ファイルを意味します。

Update 3 Oracleのコンパイラにおける理論上の違いと実際的な違いの違い。

[編集、言い換え質問を追加]
「同じjavac実行可能ファイルを別のプラットフォームで実行すると、異なるバイトコードが生成される状況は何ですか?」


5
@Gamb CORAは、異なるプラットフォームでコンパイルされた場合、バイトコードがまったく同じになることを意味しませ。つまり、生成されたバイトコードがまったく同じことを行うということです。
dasblinkenlight 2013

10
なんで気にするの?これはXY問題のようなにおいがします。
Joachim Sauer

4
@JoachimSauerバイナリのバージョン管理を検討してください。ソースコードが変更された場合にのみ変更を検出することもできますが、JDKが出力バイナリを任意に変更できる場合、これは賢明なアイデアではありません。
RB。

7
@RB .:コンパイラーは、コンパイルされたコードを表す準拠するバイトコードを生成できます。実際、一部のコンパイラアップデートでは、わずかに異なるコードを生成するバグが修正されています(通常、同じ実行時の動作)。つまり、ソースの変更を検出する場合は、ソースの変更を確認します。
Joachim Sauer

3
@dasblinkenlight:あなたが彼らが持っていると主張する答えが実際に正確で最新のものであると仮定しています(質問が2003年からのものであることを考えると、疑わしいです)。
Joachim Sauer、

回答:


68

このようにしましょう:

同じ.classファイルを指定した場合、同じファイルを2回生成することのない、完全に準拠したJavaコンパイラを簡単に生成でき.javaます。

これを行うには、あらゆる種類のバイトコード構成を調整するか、メソッドに余分な属性を追加します(許可されています)。

ことを考えると仕様がありません必要農産物バイト単位の同一のクラスファイルにコンパイルし、私は思いによっては避ける ような結果を。

しかし、私は同じスイッチ(と同じライブラリ!)と同じコンパイラと同じソースファイルをコンパイルし、チェックしたことを数回やったと同じで結果を.classファイル。

更新:私は最近、Java 7 でのswitchon の実装に関するこの興味深いブログ投稿にString遭遇しました。このブログ投稿には、ここで引用するいくつかの関連する部分があります(強調は私のものです):

コンパイラの出力を予測可能かつ反復可能にするために、これらのデータ構造で使用されるマップとセットは、単なるand ではなくLinkedHashMapsとLinkedHashSets です。特定のコンパイル中に生成されたコードの機能的な正確さに関しては、とを使用して問題ありません。反復順序は重要ではありません。ただし、システムクラスの実装の詳細に基づいて出力が変化しないことが有益であることがわかりますHashMapsHashSetsHashMapHashSetjavac

これは問題をかなり明確に示しています。仕様に一致している限り、コンパイラーは決定論的な方法で動作する必要はありません。ただし、コンパイラの開発者は、試しみるのが一般的には良い考えであることを認識しています(ただし、あまり高価ではない場合)。


@GaborSchそれは何が欠けているのですか?「同じjavac実行可能ファイルを別のプラットフォームで実行すると、別のバイトコードが生成される状況は何ですか?」基本的には、コンパイラーを作成したグループの気まぐれに依存します
エモリー2013

3
私にとって、これはそれに依存しない十分な理由です。コンパイラが常に同じコードを生成するという事実に依存している場合、更新されたJDKがビルド/アーカイブシステムを破壊する可能性があります。
Joachim Sauer

3
@GaborSch:あなたはすでにそのような状況の完全に素晴らしい例を持っているので、問題についてのいくつかの追加の見解が整いました。作業を複製しても意味がありません。
Joachim Sauer

1
@GaborSch根本的な問題は、ユーザーが変更されたJARをWebサイトからのみフェッチするアプリケーションの効率的な「オンライン更新」を実装することです。入力として同じクラスファイルを持つ同じJARを作成できます。しかし問題は、同じソースファイルからコンパイルした場合に、クラスファイルが常に同じであるかどうかです。私たちのコンセプト全体がこの事実で成り立ち、失敗します。
mstrap 2013

2
@mstrap:結局のところ、それはXY問題です。ええと、jarの差分更新を調べることができ(1バイトの違いがあってもjar全体が再ダウンロードされることはありません)、とにかくリリースに明示的なバージョン番号を提供する必要があります。 。
Joachim Sauer

38

コンパイラが各プラットフォームで同じバイトコードを生成する必要はありません。javac特定の答えを得るには、さまざまなベンダーのユーティリティを参照してください。


ファイルの順序付けを使用して、このための実用的な例を示します。

2つのjarファイルがあるmy1.jarとします:とMy2.jar。それらはlib並んでディレクトリに置かれます。(これがあるため、コンパイラは、アルファベット順にそれらを読み込むlib)が、順序はmy1.jarMy2.jarファイルシステムは大文字小文字を区別しないで、そしてときMy2.jarmy1.jarそれは大文字と小文字が区別されます。

に はメソッドをmy1.jar持つクラスA.classがあります

public class A {
     public static void a(String s) {}
}

My2.jar同じ有するA.classが、異なるメソッドシグネチャと(受け入れObject):

public class A {
     public static void a(Object o) {}
}

電話をかけたら

String s = "x"; 
A.a(s); 

異なる場合に異なるシグネチャでメソッド呼び出しをコンパイルします。したがって、ファイルシステムの大文字と小文字の区別応じて、結果として異なるクラスを取得します。


1
+1 Eclipseコンパイラーとjavacの間には、合成コンストラクターの生成方法など、無数の違いがあります。
ポールベローラ2013

2
@GaborSchバイトコードが同じJDK、つまり同じjavacで同一であるかどうかに興味があります。私はそれをより明確にします。
mstrap 2013

2
@mstrap私はあなたの質問を理解しましたが、答えはまだ同じです:ベンダーに依存します。javacあなたはそれぞれのプラットフォーム(例えばWin7の、Linuxでは、Solaris版、Mac)を上に異なるバイナリを持っているので、同じではありません。ベンダーにとって、異なる実装をすることは意味がありませんが、プラットフォーム固有の問題が結果に影響を与える可能性があります(たとえば、ディレクトリでの飛行順序(ディレクトリを考えるlib)、エンディアンなど)。
gaborsch 2013

1
通常、ほとんどはjavacJavaで実装されており(javac単純なネイティブランチャーです)、ほとんどのプラットフォームの違いによる影響はありません。
Joachim Sauer

2
@mstrap-彼が主張している点は、ベンダーがコンパイラにプラットフォーム間でまったく同じバイトコードを生成させる必要はなく、結果のバイトコードが同じ結果を生成することだけです。標準/仕様/要件がないことを考えると、あなたの質問に対する答えは、「特定のベンダー、コンパイラ、およびプラットフォームによって異なります」です。
ブライアンローチ

6

短い答え- いいえ


長い答え

bytecode異なるプラットフォームで同じである必要はありません。バイトコードを正確に実行する方法を知っているのはJRE(Javaランタイム環境)です。

Java VM仕様に目を通すと、バイトコードが異なるプラットフォームでも同じである必要がないことがわかります。

クラスファイルフォーマットを通過すると、クラスファイルの構造を次のように示します

ClassFile {
    u4 magic;
    u2 minor_version;
    u2 major_version;
    u2 constant_pool_count;
    cp_info constant_pool[constant_pool_count-1];
    u2 access_flags;
    u2 this_class;
    u2 super_class;
    u2 interfaces_count;
    u2 interfaces[interfaces_count];
    u2 fields_count;
    field_info fields[fields_count];
    u2 methods_count;
    method_info methods[methods_count];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

マイナーバージョンとメジャーバージョンの確認

minor_version、major_version

minor_versionとmajor_versionの項目の値は、このクラスファイルのマイナーバージョン番号とメジャーバージョン番号です。まとめて、メジャーバージョン番号とマイナーバージョン番号によって、クラスファイル形式のバージョンが決まります。クラスファイルのメジャーバージョン番号がMで、マイナーバージョン番号がmの場合、そのクラスファイル形式のバージョンをMmと表します。したがって、クラスファイル形式のバージョンは、辞書順で並べ替えられます(例:1.5 <2.0 <2.1)。Java仮想マシンの実装は、vが連続する範囲Mi.0 v Mj.mにある場合に限り、バージョンvのクラスファイル形式をサポートできます。Javaプラットフォームの特定のリリースレベルに準拠するJava仮想マシンの実装がサポートできるバージョンの範囲を指定できるのはSunだけです。1

脚注でさらに読む

1 SunのJDKリリース1.0.2のJava仮想マシン実装は、クラスファイル形式バージョン45.0〜45.3をサポートしています。SunのJDKリリース1.1.Xは、45.0から45.65535までの範囲のバージョンのクラスファイル形式をサポートできます。Java 2プラットフォームのバージョン1.2の実装は、45.0から46.0までの範囲のバージョンのクラスファイル形式をサポートできます。

したがって、これらすべてを調査すると、異なるプラットフォームで生成されたクラスファイルが同一である必要はないことがわかります。


より詳しいリンクをお願いします。
mstrap 2013

「プラットフォーム」とは、オペレーティングシステムではなく、Javaプラットフォームを指していると思います。もちろん、javac 1.7に1.6互換のクラスファイルを作成するように指示すると、違いが生じます。
mstrap 2013

@mtk +1は、コンパイル中に1つのクラスに対して生成されるプロパティの数を示します。
gaborsch 2013

3

まず、仕様にはそのような保証はまったくありません。適合コンパイラは、生成されたクラスファイルへのコンパイル時間を追加(カスタム)属性としてスタンプすることができますが、クラスファイルは正しいままです。ただし、ビルドごとにバイトレベルの異なるファイルが生成されます。

次に、そのような厄介なトリックがなくても、2つのケースで構成と入力の両方が同一でない限り、コンパイラーがまったく同じことを2回続けて行うことを期待する理由はありません。仕様ソースファイル名を標準属性の1つとして記述しており、ソースファイルに空白行を追加すると、行番号テーブルが変更される可能性があります。

3番目に、ホストプラットフォームによるビルドの違いは一度もありませんでした(クラスパスにあるものの違いに起因するものを除く)。プラットフォーム(つまり、ネイティブコードライブラリ)によって異なるコードはクラスファイルの一部ではなく、クラスがロードされた後に、バイトコードからのネイティブコードの実際の生成が行われます。

第4に(そして最も重要なこととして)プロセスの臭い(コードの臭いのようですが、コードにどのように対処するか)を知りたいと思っています。可能であれば、ビルドではなくソースにバージョンを付けます。ビルドにバージョンを付ける必要がある場合は、個々のクラスファイルではなく、コンポーネント全体のレベルでバージョンを付けます。優先的に、CIサーバー(Jenkinsなど)を使用して、ソースを実行可能なコードに変換するプロセスを管理します。


2

同じJDKを使用する場合、生成されるバイトコードは、使用するハードウェアやOSに関係なく、常に同じになると思います。バイトコードの生成は、Javaコンパイラによって行われます。Javaコンパイラは、確定的アルゴリズムを使用して、ソースコードをバイトコードに「変換」します。したがって、出力は常に同じになります。これらの状況では、ソースコードの更新のみが出力に影響します。


3
これについてのリファレンスはありますか?質問のコメントで述べたように、これはC#は当てはまりません。Javaの場合もそうであると述べているリファレンスを参照してください。特に、マルチスレッドコンパイラーは異なる実行で異なる識別子名を割り当てる可能性があると考えています。
RB。

1
これが私の質問に対する答えであり、私が期待することですが、RBについては、その参照が重要であることに同意します。
mstrap 2013

私も同じだと思います。あなたが決定的な参照を見つけることはないと思います。それがあなたにとって重要であるなら、あなたは勉強をすることができます。主要なコードを集めて、いくつかのオープンソースコードをコンパイルするさまざまなプラットフォームで試してみてください。バイトファイルを比較します。結果を公開します。必ずここにリンクを貼ってください。
emory 2013

1

全体として、同じコンパイラで異なるプラットフォームでコンパイルした場合、同じソースが同じバイトコードを生成するという保証はないと言っておく必要があります。

さまざまな言語(コードページ)が関係するシナリオ(たとえば、日本語をサポートするWindows)を検討します。マルチバイト文字を考えてください。コンパイラーがすべての言語をサポートする必要があると常に想定しない限り、8ビットASCII用に最適化される可能性があります。

Java言語仕様には、バイナリ互換性に関するセクションがあります。

SOM(Forman、Conner、Danforth、およびRaper、OOPSLA '95の議事録)のリリース間バイナリ互換性のフレームワーク内で、Javaプログラミング言語のバイナリは、作成者が特定するすべての関連する変換の下でバイナリ互換性があります(いくつかの注意点があります)インスタンス変数の追加に関して)。それらのスキームを使用して、Javaプログラミング言語がサポートするいくつかの重要なバイナリ互換の変更のリストを次に示します。

•既存のメソッド、コンストラクター、および初期化子を再実装してパフォーマンスを向上させます。

•メソッドまたはコンストラクターを変更して、入力で値を返すようにしました。以前は、通常は発生しないはずの例外をスローしたり、無限ループに入ったり、デッドロックを引き起こしたりして失敗しました。

•既存のクラスまたはインターフェイスに新しいフィールド、メソッド、またはコンストラクターを追加します。

•クラスのプライベートフィールド、メソッド、またはコンストラクターを削除する。

•パッケージ全体が更新されると、パッケージ内のクラスおよびインターフェースのデフォルト(パッケージのみ)アクセスフィールド、メソッド、またはコンストラクターが削除されます。

•既存の型宣言のフィールド、メソッド、またはコンストラクターを並べ替える。

•メソッドをクラス階層の上位に移動します。

•クラスまたはインターフェイスの直接スーパーインターフェイスのリストを並べ替えます。

•型階層に新しいクラスまたはインターフェイス型を挿入します。

この章では、すべての実装で保証されるバイナリ互換性の最小標準を指定します。Javaプログラミング言語は、互換性のあるソースからのものであることがわかっていないが、ここで説明する互換性のある方法でソースが変更されているクラスとインターフェイスのバイナリが混在している場合に互換性を保証します。アプリケーションのリリース間の互換性について説明していることに注意してください。Java SEプラットフォームのリリース間の互換性についての説明は、この章の範囲を超えています。


この記事では、Javaのバージョンを変更したときに何が起こるかについて説明します。OPの質問は、同じJavaバージョン内でプラットフォームを変更するとどうなるかということでした。そうでなければそれは良いキャッチです。
gaborsch 2013

1
それは私が見つけることができる限り近いです。言語の仕様とJVMの仕様の間には奇妙な穴があります。これまでのところ、私はOPに「別のプラットフォームで実行したときに同じJavaコンパイラが同じバイトコードを生成するという保証はありません」と答える必要があります。
ケリーS.フランス語

1

Java allows you write/compile code on one platform and run on different platform. AFAIK ; これは、異なるプラットフォームで生成されたクラスファイルが同じまたは技術的に同じ、つまり同一である場合にのみ可能です。

編集する

技術的に同じコメントで私が意味するのはそれです。バイトごとに比較する場合、それらは完全に同じである必要はありません。

したがって、仕様に従って、異なるプラットフォーム上のクラスの.classファイルは、バイトごとに一致する必要はありません。


OPの問題、クラスファイルが同じか、「技術的に同じ」かということでした。
bdesham 2013

それらが同一であるかどうかに興味があります
mstrap 2013

返事はイエスです。つまり、バイト単位で比較すると同じではない可能性があるということです。そのため、技術的に同じ単語を使用しました。
rai.skumar 2013

@bdesham彼はそれらが同一かどうか知りたかった。あなたが「技術的に同じ」で何を理解したかわからない...それが反対投票の理由ですか?
rai.skumar 2013

@ rai.skumar基本的に、「2つのコンパイラーは常に同じ動作をする出力を生成します」という答えが返されます。もちろん、これは本当です。それがJavaプラットフォーム全体の動機です。OPは、出力されたコードがバイトごとに同一であるかどうかを知りたいと思っていました。
bdesham 2013

1

質問について:

「同じjavac実行可能ファイルを別のプラットフォームで実行すると、別のバイトコードが生成される状況は何ですか?」

クロスコンパイルの例 -targetバージョン:我々はあるJavacオプションを使用する方法を示し

このフラグは、このコマンドを呼び出すときに指定するJavaバージョンと互換性のあるクラスファイルを生成します。したがって、クラスファイルは、このオプションを使用した照合中に提供する属性によって異なります。


0

ほとんどの場合、答えは「はい」ですが、正確な答えを得るためには、コンパイル中にいくつかのキーまたはGUID生成を検索する必要があります。

これが起こった状況を思い出せません。たとえば、シリアル化の目的でIDを使用する場合、IDはハードコードされます。つまり、プログラマーまたはIDEによって生成されます。

PSまた、JNIは重要です。

javacそれ自体がjavaで記述されているPPSを見つけました。つまり、異なるプラットフォームでも同じです。したがって、理由なしに異なるコードを生成することはありません。したがって、これはネイティブ呼び出しでのみこれを行うことができます。


Javaはすべてのプラットフォームの違いからユーザーを保護するわけではないことに注意してください。ディレクトリの内容を一覧表示するときに返されるファイルの順序は定義されていません。これ、コンパイラに何らかの影響を与える可能性があります。
Joachim Sauer

0

2つの質問があります。

Can there be a difference depending on the operating system or hardware? 

これは理論的な質問ですが、答えが明確で、はい、そこにできること。他の人が言ったように、仕様では、コンパイラーがバイトごとに同一のクラスファイルを生成する必要はありません。

現在存在しているすべてのコンパイラがすべての状況(異なるハードウェアなど)で同じバイトコードを生成したとしても、明日の答えは異なる可能性があります。javacまたはオペレーティングシステムを更新する予定がない場合は、特定の状況でそのバージョンの動作をテストできますが、たとえばJava 7 Update 11からJava 7 Update 15に移行すると、結果が異なる場合があります。

What are the circumstances where the same javac executable, when run on a different platform, will produce different bytecode?

それは分からない。

構成管理があなたの質問をする理由であるかどうかはわかりませんが、気にするのは理解できる理由です。バイトコードの比較は正当なIT制御ですが、クラスファイルが変更されたかどうかを判断するためだけのものであり、ソースファイルが変更されたかどうかを判断するためではありません。


0

別の言い方をすると。

まず、問題は決定論的であることではないと思います。

もちろん、それは決定論的です。コンピュータサイエンスではランダム性を実現するのは難しいため、コンパイラが何らかの理由でランダム性をここに導入する理由はありません。

次に、「同じソースコードファイルのバイトコードファイルはどの程度似ているか」で再定式化すると、いいえ、似ているという事実に頼ることはできません

これを確認する良い方法は、.class(または私の場合は.pyc)をgitステージに残すことです。チーム内のさまざまなコンピューター間で、.pyファイルに変更が加えられていない(そして.pycが再コンパイルされている)場合、gitは.pycファイル間の変更に気づくことがわかります。

少なくともそれは私が観察したものです。* .pycと* .classを.gitignoreに入れてください!

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.