Apakah pembuatan file kelas Java bersifat deterministik?

94

Saat menggunakan JDK yang sama (yaitu javacexecutable yang sama ), apakah file kelas yang dihasilkan selalu identik? Apakah ada perbedaan bergantung pada sistem operasi atau perangkat keras ? Kecuali versi JDK, apakah ada faktor lain yang menyebabkan perbedaan? Apakah ada opsi kompiler untuk menghindari perbedaan? Apakah perbedaan hanya mungkin dalam teori atau apakah Oracle javacbenar - benar menghasilkan file kelas yang berbeda untuk opsi input dan compiler yang sama?

Pembaruan 1 Saya tertarik pada generasi , yaitu keluaran kompiler, bukan apakah file kelas dapat dijalankan pada berbagai platform.

Perbarui 2 Dengan 'JDK yang Sama', maksud saya juga javacdapat dieksekusi yang sama .

Perbarui 3 Perbedaan antara perbedaan teoritis dan perbedaan praktis di kompiler Oracle.

[EDIT, menambahkan pertanyaan yang diparafrasekan]
"Bagaimana keadaan di mana javac yang sama dapat dieksekusi, ketika dijalankan pada platform yang berbeda, akan menghasilkan bytecode yang berbeda?"

mstrap
sumber
5
@Gamb CORA tidak berarti bahwa kode byte akan persis sama jika dikompilasi pada platform yang berbeda; semua itu berarti bahwa kode byte yang dihasilkan akan melakukan hal yang persis sama.
dasblinkenlight
10
Kenapa kamu peduli Ini berbau seperti Masalah XY .
Joachim Sauer
4
@JoachimSauer Pertimbangkan jika Anda mengontrol versi biner Anda - Anda mungkin ingin mendeteksi perubahan hanya jika kode sumber telah berubah, tetapi Anda akan tahu ini bukan ide yang masuk akal jika JDK dapat mengubah biner keluaran secara sewenang-wenang.
RB.
7
@RB .: kompilator diperbolehkan untuk menghasilkan kode byte yang sesuai yang mewakili kode yang dikompilasi. Faktanya, beberapa update compiler memperbaiki bug yang menghasilkan kode yang sedikit berbeda (biasanya dengan perilaku runtime yang sama). Dengan kata lain: jika Anda ingin mendeteksi perubahan sumber, periksa perubahan sumber.
Joachim Sauer
3
@dasblinkenlight: Anda berasumsi bahwa jawaban yang mereka klaim sebenarnya benar dan terkini (diragukan, mengingat pertanyaan tersebut dari tahun 2003).
Joachim Sauer

Jawaban:

68

Mari kita begini:

Saya dapat dengan mudah menghasilkan kompiler Java yang sepenuhnya sesuai yang tidak pernah menghasilkan .classfile yang sama dua kali, mengingat .javafile yang sama .

Saya dapat melakukan ini dengan mengutak-atik semua jenis konstruksi bytecode atau hanya dengan menambahkan atribut yang berlebihan ke metode saya (yang diizinkan).

Mengingat bahwa spesifikasi tidak memerlukan kompilator untuk menghasilkan file kelas identik byte-untuk-byte, saya akan menghindari bergantung pada hasil seperti itu.

Namun , beberapa kali saya memeriksa, mengkompilasi file sumber yang sama dengan kompilator yang sama dengan sakelar yang sama (dan pustaka yang sama!) Memang menghasilkan .classfile yang sama .

Pembaruan: Saya baru-baru ini tersandung pada posting blog yang menarik ini tentang penerapan switchon Stringdi Java 7 . Dalam posting blog ini, ada beberapa bagian yang relevan, yang akan saya kutip di sini (penekanan milik saya):

Untuk membuat keluaran kompiler dapat diprediksi dan diulang, peta dan set yang digunakan dalam struktur data ini adalah LinkedHashMaps dan LinkedHashSets, bukan hanya HashMapsdan HashSets. Dalam hal kebenaran fungsional kode yang dihasilkan selama kompilasi tertentu, menggunakan HashMapdan HashSetakan baik-baik saja ; urutan iterasi tidak masalah. Namun, kami merasa bermanfaat untuk memiliki javackeluaran yang tidak bervariasi berdasarkan detail implementasi kelas sistem .

Ini cukup jelas menggambarkan masalah: Kompilator tidak diharuskan untuk bertindak secara deterministik, selama cocok dengan spesifikasi. Pengembang kompiler, bagaimanapun, menyadari bahwa pada umumnya ide yang bagus untuk dicoba (asalkan tidak terlalu mahal, mungkin).

Joachim Sauer
sumber
@GaborSch apa yang hilang? "Bagaimana keadaan di mana javac yang sama yang dapat dieksekusi, ketika dijalankan pada platform yang berbeda, akan menghasilkan bytecode yang berbeda?" pada dasarnya tergantung pada keinginan grup yang menghasilkan kompilator
emory
3
Bagi saya ini akan menjadi alasan yang cukup untuk tidak bergantung padanya: JDK yang diperbarui dapat merusak sistem build / arsip saya jika saya bergantung pada fakta bahwa kompilator selalu menghasilkan kode yang sama.
Joachim Sauer
3
@ GaborSch: Anda sudah memiliki contoh yang sangat bagus dari situasi seperti itu, jadi beberapa pandangan tambahan tentang masalah itu secara berurutan. Tidak ada gunanya menduplikasi pekerjaan Anda.
Joachim Sauer
1
@GaborSch Akar masalahnya adalah kami ingin menerapkan "pembaruan online" yang efisien dari aplikasi kami di mana pengguna hanya akan mengambil JAR yang dimodifikasi dari situs web. Saya dapat membuat JAR identik yang memiliki file kelas identik sebagai masukan. Tetapi pertanyaannya adalah apakah file kelas selalu identik ketika dikompilasi dari file sumber yang sama. Seluruh konsep kami berdiri dan gagal dengan fakta ini.
mstrap
2
@mstrap: jadi ini adalah Masalah XY. Nah, Anda dapat melihat pembaruan diferensial dari toples (sehingga perbedaan satu byte pun tidak akan menyebabkan seluruh jar diunduh ulang) dan Anda harus memberikan nomor versi eksplisit untuk rilis Anda, sehingga intinya diperdebatkan, menurut pendapat saya .
Joachim Sauer
38

Tidak ada kewajiban bagi kompiler untuk menghasilkan bytecode yang sama di setiap platform. Anda harus berkonsultasi dengan javacutilitas vendor yang berbeda untuk mendapatkan jawaban yang spesifik.


Saya akan menunjukkan contoh praktis untuk ini dengan pemesanan file.

Katakanlah kita memiliki 2 file jar: my1.jardan My2.jar. Mereka diletakkan di libdirektori, berdampingan. Kompilator membacanya dalam urutan abjad (karena ini lib), tetapi urutannya adalah my1.jar, My2.jarjika sistem file tidak peka huruf besar / kecil, dan My2.jar, my1.jarjika peka huruf besar / kecil.

The my1.jarmemiliki kelas A.classdengan metode

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

Itu My2.jarsama A.class, tetapi dengan tanda tangan metode yang berbeda (menerima Object):

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

Jelas bahwa jika Anda memiliki panggilan

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

itu akan mengkompilasi panggilan metode dengan tanda tangan berbeda dalam kasus yang berbeda. Jadi, bergantung pada sensitivitas huruf besar sistem file Anda, Anda akan mendapatkan kelas yang berbeda sebagai hasilnya.

gaborsch
sumber
1
+1 Ada banyak sekali perbedaan antara compiler Eclipse dan javac, misalnya bagaimana konstruktor sintetik dibuat .
Paul Bellora
2
@ GaborSch Saya tertarik pada apakah kode byte identik untuk JDK yang sama, yaitu javac yang sama. Saya akan membuatnya lebih jelas.
mstrap
2
@mstrap Saya mengerti pertanyaan Anda, tetapi jawabannya masih sama: tergantung pada vendornya. Ini javactidak sama, karena Anda memiliki binari yang berbeda di setiap platform (mis. Win7, Linux, Solaris, Mac). Untuk vendor, tidak masuk akal untuk memiliki implementasi yang berbeda, tetapi masalah spesifik platform apa pun dapat memengaruhi hasilnya (mis. Pemesanan flie dalam direktori (pikirkan libdirektori Anda ), endianness, dll).
gaborsch
1
Biasanya, sebagian besar javacdiimplementasikan di Java (dan javachanya launcher asli sederhana), jadi sebagian besar perbedaan platform seharusnya tidak berdampak.
Joachim Sauer
2
@mstrap - poin yang dia buat adalah bahwa tidak ada persyaratan bagi vendor mana pun untuk membuat kompiler mereka menghasilkan bytecode yang persis sama di seluruh platform, hanya saja bytecode yang dihasilkan menghasilkan hasil yang sama. Mengingat tidak ada standar / spesifikasi / persyaratan, jawaban untuk pertanyaan Anda adalah "Tergantung pada vendor, kompilator, dan platform tertentu".
Brian Roach
6

Jawaban Singkat - TIDAK


Jawaban panjang

Mereka bytecodetidak harus sama untuk platform yang berbeda. Ini adalah JRE (Java Runtime Environment) yang tahu bagaimana persisnya menjalankan bytecode.

Jika Anda melihat spesifikasi Java VM, Anda akan mengetahui bahwa ini tidak harus benar bahwa bytecode sama untuk platform yang berbeda.

Melalui format file kelas , ini menunjukkan struktur file kelas sebagai

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];
}

Memeriksa tentang versi minor dan mayor

minor_version, major_version

Nilai item minor_version dan major_version adalah nomor versi minor dan mayor dari file kelas ini. Secara bersama-sama, nomor versi mayor dan minor menentukan versi format file kelas. Jika file kelas memiliki nomor versi utama M dan nomor versi kecil m, kami menunjukkan versi format file kelasnya sebagai Mm. Dengan demikian, versi format file kelas dapat diurutkan secara leksikografis, misalnya, 1.5 <2.0 <2.1. Implementasi mesin virtual Java dapat mendukung format file kelas versi v jika dan hanya jika v berada dalam rentang yang berdekatan Mi.0 v Mj.m. Hanya Sun yang dapat menentukan kisaran versi apa yang dapat didukung oleh implementasi mesin virtual Java yang sesuai dengan tingkat rilis tertentu dari platform Java

Membaca lebih banyak melalui catatan kaki

1 Implementasi mesin virtual Java dari rilis JDK Sun 1.0.2 mendukung format file kelas versi 45.0 hingga 45.3 inklusif. Rilis Sun JDK 1.1.X dapat mendukung format file kelas versi dalam kisaran 45.0 hingga 45.65535 inklusif. Penerapan versi 1.2 dari platform Java 2 dapat mendukung format file kelas versi dalam kisaran 45.0 hingga 46.0 inklusif.

Jadi, menyelidiki semua ini menunjukkan bahwa file kelas yang dihasilkan pada platform berbeda tidak harus identik.

mtk
sumber
Bisakah Anda memberikan tautan yang lebih detail?
mstrap
Saya pikir dengan 'platform' mereka mengacu pada platform Java, bukan sistem operasi. Tentu saja, saat menginstruksikan javac 1.7 untuk membuat file kelas yang kompatibel dengan 1.6, akan ada perbedaan.
mstrap
@mtk +1 untuk menunjukkan berapa banyak properti yang dibuat untuk satu kelas selama kompilasi.
gaborsch
3

Pertama, sama sekali tidak ada jaminan seperti itu dalam spesifikasi. Kompiler yang sesuai dapat menandai waktu kompilasi ke dalam file kelas yang dihasilkan sebagai atribut tambahan (khusus), dan file kelas tersebut akan tetap benar. Namun itu akan menghasilkan file yang berbeda tingkat byte pada setiap build, dan sepele.

Kedua, bahkan tanpa trik buruk seperti itu, tidak ada alasan untuk mengharapkan kompilator melakukan hal yang persis sama dua kali berturut-turut kecuali jika konfigurasi dan inputnya identik dalam kedua kasus tersebut. Spesifikasi tidak menggambarkan nama file sumber sebagai salah satu atribut standar, dan menambahkan baris kosong ke file sumber baik bisa mengubah tabel nomor baris.

Ketiga, saya tidak pernah menemukan perbedaan dalam build karena platform host (selain yang disebabkan oleh perbedaan dalam classpath). Kode yang akan bervariasi berdasarkan platform (yaitu, pustaka kode asli) bukan bagian dari file kelas, dan pembuatan kode asli sebenarnya dari bytecode terjadi setelah kelas dimuat.

Keempat (dan yang paling penting) itu berbau proses yang buruk bau (seperti bau kode, tapi bagaimana Anda bertindak pada kode) ingin tahu ini. Buat versi sumber jika memungkinkan, bukan build, dan jika Anda perlu membuat versi build, buat versi di tingkat seluruh komponen dan bukan pada file kelas individual. Untuk preferensi, gunakan server CI (seperti Jenkins) untuk mengelola proses mengubah sumber menjadi kode yang dapat dijalankan.

Donal Fellows
sumber
2

Saya yakin, jika Anda menggunakan JDK yang sama, kode byte yang dihasilkan akan selalu sama, tanpa ada hubungannya dengan harware dan OS yang digunakan. Produksi kode byte dilakukan oleh kompilator java, yang menggunakan algoritma deterministik untuk "mengubah" kode sumber menjadi kode byte. Jadi, outputnya akan selalu sama. Dalam kondisi ini, hanya pembaruan pada kode sumber yang akan memengaruhi keluaran.

viniciusjssouza
sumber
3
Apakah Anda punya referensi untuk ini? Seperti yang saya katakan di pertanyaan komentar, ini pasti bukan kasus C # , jadi saya ingin melihat referensi yang menyatakan itu kasus untuk Java. Saya secara khusus berpikir bahwa kompilator multi-utas mungkin menetapkan nama pengenal yang berbeda pada proses yang berbeda.
RB.
1
Ini adalah jawaban atas pertanyaan saya dan apa yang saya harapkan, namun saya setuju dengan RB bahwa referensi untuk itu penting.
mstrap
Saya percaya hal yang sama. Saya rasa Anda tidak akan menemukan referensi yang pasti. Jika itu penting bagi Anda, maka Anda dapat melakukan studi. Kumpulkan banyak yang terkemuka dan cobalah di platform berbeda yang mengumpulkan beberapa kode sumber terbuka. Bandingkan file byte. Publikasikan hasilnya. Pastikan untuk meletakkan tautan di sini.
emory
1

Secara keseluruhan, saya harus mengatakan tidak ada jaminan bahwa sumber yang sama akan menghasilkan bytecode yang sama ketika dikompilasi oleh kompiler yang sama tetapi pada platform yang berbeda.

Saya akan melihat skenario yang melibatkan bahasa yang berbeda (halaman kode), misalnya Windows dengan dukungan bahasa Jepang. Pikirkan karakter multi-byte; kecuali kompilator selalu menganggap ia perlu mendukung semua bahasa yang mungkin dioptimalkan untuk ASCII 8-bit.

Ada bagian tentang kompatibilitas biner di Spesifikasi Bahasa Java .

Dalam kerangka kerja Kompatibilitas Biner Rilis-ke-Rilis di SOM (Forman, Conner, Danforth, dan Raper, Proceedings of OOPSLA '95), biner bahasa pemrograman Java kompatibel dengan biner di bawah semua transformasi relevan yang diidentifikasi oleh penulis (dengan beberapa peringatan sehubungan dengan penambahan variabel instan). Menggunakan skema mereka, berikut adalah daftar beberapa perubahan penting yang kompatibel dengan biner yang didukung oleh bahasa pemrograman Java:

• Menerapkan kembali metode, konstruktor, dan penginisialisasi yang ada untuk meningkatkan kinerja.

• Mengubah metode atau konstruktor untuk mengembalikan nilai pada input yang sebelumnya mereka lemparkan pengecualian yang biasanya tidak boleh terjadi atau gagal dengan masuk ke loop tak terbatas atau menyebabkan kebuntuan.

• Menambahkan bidang, metode, atau konstruktor baru ke kelas atau antarmuka yang ada.

• Menghapus bidang privat, metode, atau konstruktor kelas.

• Ketika seluruh paket diperbarui, menghapus bidang akses default (hanya paket), metode, atau konstruktor kelas dan antarmuka dalam paket.

• Menyusun ulang bidang, metode, atau konstruktor dalam deklarasi tipe yang ada.

• Memindahkan metode ke atas dalam hierarki kelas.

• Menyusun ulang daftar superinterfaces langsung dari sebuah kelas atau antarmuka.

• Memasukkan kelas atau tipe antarmuka baru dalam hierarki tipe.

Bab ini menetapkan standar minimum untuk kompatibilitas biner yang dijamin oleh semua implementasi. Bahasa pemrograman Java menjamin kompatibilitas ketika biner kelas dan antarmuka dicampur yang tidak diketahui berasal dari sumber yang kompatibel, tetapi sumbernya telah dimodifikasi dengan cara kompatibel yang dijelaskan di sini. Perhatikan bahwa kita membahas kompatibilitas antara rilis aplikasi. Diskusi tentang kompatibilitas di antara rilis platform Java SE berada di luar cakupan bab ini.

Kelly S. Prancis
sumber
Artikel itu membahas apa yang bisa terjadi jika kita mengubah versi Java. Pertanyaan OP adalah apa yang bisa terjadi jika kita mengubah platform dalam versi Java yang sama. Kalau tidak, itu tangkapan yang bagus.
gaborsch
1
Itu sedekat yang bisa saya temukan. Ada lubang ganjil antara spesifikasi bahasa dan spesifikasi JVM. Sejauh ini, saya harus menjawab OP dengan 'tidak ada jaminan bahwa compiler java yang sama akan menghasilkan bytecode yang sama ketika dijalankan pada platform yang berbeda.'
Kelly S. French
1

Java allows you write/compile code on one platform and run on different platform. AFAIK ; ini hanya mungkin jika file kelas yang dihasilkan pada platform yang berbeda sama atau secara teknis sama yaitu identik.

Edit

Yang saya maksud dengan komentar yang secara teknis sama adalah itu. Keduanya tidak harus persis sama jika Anda membandingkan byte demi byte.

Jadi, sesuai spesifikasi file .class kelas pada platform yang berbeda tidak perlu mencocokkan byte-by-byte.

rai.skumar
sumber
Pertanyaan OP adalah apakah file kelas sama atau "secara teknis sama".
bdesham
Saya tertarik apakah mereka identik .
mstrap
dan jawabannya adalah ya. yang saya maksud adalah mereka mungkin tidak sama jika Anda membandingkan byte demi byte, itulah mengapa saya menggunakan kata yang secara teknis sama.
rai.skumar
@bdesham dia ingin tahu apakah mereka identik. tidak yakin apa yang Anda pahami dengan "secara teknis sama" ... apakah itu alasan untuk tidak memilih?
rai.skumar
@ rai.skumar Jawaban Anda pada dasarnya mengatakan, "Dua kompiler akan selalu menghasilkan keluaran yang berperilaku sama." Tentu saja ini benar; itulah motivasi keseluruhan dari platform Java. OP ingin mengetahui apakah kode yang dipancarkan adalah byte untuk byte identik , yang tidak Anda alamatkan dalam jawaban Anda.
bdesham
1

Untuk pertanyaan:

"Bagaimana keadaan di mana javac yang sama yang dapat dieksekusi, ketika dijalankan pada platform yang berbeda, akan menghasilkan bytecode yang berbeda?"

Itu Cross-Compilation menunjukkan bagaimana kita dapat menggunakan opsi Javac: -target version

Bendera ini menghasilkan file kelas yang kompatibel dengan versi Java yang kami tentukan saat menjalankan perintah ini. Oleh karena itu, file kelas akan berbeda tergantung pada atribut yang kami berikan selama kompaliasi menggunakan opsi ini.

PhilipJoseParampettu
sumber
0

Kemungkinan besar, jawabannya adalah "ya", tetapi untuk mendapatkan jawaban yang tepat, seseorang perlu mencari beberapa kunci atau generasi panduan selama kompilasi.

Saya tidak dapat mengingat situasi di mana ini terjadi. Misalnya memiliki ID untuk keperluan serialisasi itu adalah hardcoded, yaitu dihasilkan oleh programmer atau IDE.

PS Juga JNI bisa menjadi masalah.

PPS yang saya temukan javacsendiri ditulis dalam java. Artinya, ini identik pada platform yang berbeda. Karenanya itu tidak akan menghasilkan kode yang berbeda tanpa alasan. Jadi, ini hanya dapat dilakukan dengan panggilan asli.

Suzan Cioc
sumber
Perhatikan bahwa Java tidak melindungi Anda dari semua perbedaan platform. Urutan file kembali ketika daftar isi direktori tidak didefinisikan, dan ini bisa dibayangkan memiliki beberapa dampak pada kompilator.
Joachim Sauer
0

Ada dua pertanyaan.

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

Ini adalah pertanyaan teoretis, dan jawabannya jelas, ya, mungkin ada. Seperti yang dikatakan orang lain, spesifikasi tidak memerlukan kompilator untuk menghasilkan file kelas identik byte-untuk-byte.

Bahkan jika setiap kompiler yang ada saat ini menghasilkan kode byte yang sama dalam semua keadaan (perangkat keras yang berbeda, dll.), Jawabannya besok mungkin berbeda. Jika Anda tidak pernah berencana untuk memperbarui javac atau sistem operasi Anda, Anda dapat menguji perilaku versi tersebut dalam keadaan khusus Anda, tetapi hasilnya mungkin berbeda jika Anda beralih dari, misalnya, Java 7 Update 11 ke Java 7 Update 15.

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

Itu tidak bisa diketahui.

Saya tidak tahu apakah manajemen konfigurasi adalah alasan Anda mengajukan pertanyaan, tetapi ini alasan yang dapat dimengerti untuk peduli. Membandingkan kode byte adalah kontrol TI yang sah, tetapi hanya untuk menentukan apakah file kelas berubah, bukan menentukan apakah file sumber berubah.

Lewati Addison
sumber
0

Saya akan mengatakannya dengan cara lain.

Pertama, saya pikir pertanyaannya bukan tentang deterministik:

Tentu saja ini deterministik: keacakan sulit dicapai dalam ilmu komputer, dan tidak ada alasan penyusun akan memperkenalkannya di sini untuk alasan apa pun.

Kedua, jika Anda memformulasi ulang dengan "seberapa mirip file bytecode untuk file kode sumber yang sama?", Maka Tidak , Anda tidak dapat mengandalkan fakta bahwa keduanya akan serupa .

Cara yang baik untuk memastikan ini, adalah dengan membiarkan .class (atau .pyc dalam kasus saya) di tahap git Anda. Anda akan menyadari bahwa di antara komputer yang berbeda dalam tim Anda, pemberitahuan git berubah di antara file .pyc, ketika tidak ada perubahan yang dibawa ke file .py (dan .pyc tetap dikompilasi ulang).

Setidaknya itulah yang saya amati. Jadi letakkan * .pyc dan * .class di .gitignore Anda!

Augustin Riedinger
sumber