Cukup bahwa di Jawa, penebang diinisialisasi pada saat kelas diinisialisasi, mengapa mereka membuang seluruh peluncuran? John Rose untuk menyelamatkan!
Begini tampilannya:
lazy private final static Logger LOGGER = Logger.getLogger("com.foo.Bar");
Dokumen ini memperluas perilaku variabel akhir, memungkinkan Anda untuk secara opsional mendukung eksekusi malas - baik dalam bahasa itu sendiri maupun dalam JVM. Diusulkan untuk meningkatkan perilaku mekanisme malas yang ada komputer dengan mengubah rincian: sekarang tidak akan akurat untuk kelas, tetapi akurat untuk variabel tertentu.

Motivasi
Java telah memiliki banyak komputasi malas. Hampir setiap operasi tautan dapat menarik kode malas. Misalnya, menjalankan metode <clinit>
(bytecode dari initializer kelas) atau menggunakan metode bootstrap (untuk situs panggilan invokedynamic atau konstanta CONSTANT_Dynamic
).
Inisialisasi kelas adalah sesuatu yang sangat kasar dalam hal granularitas jika dibandingkan dengan mekanisme yang menggunakan metode bootstrap, karena kontrak mereka adalah untuk menjalankan semua kode inisialisasi untuk kelas secara keseluruhan , daripada membatasi diri pada inisialisasi yang terkait dengan bidang spesifik dari kelas. Efek inisialisasi kasar semacam itu sulit diprediksi. Sulit untuk mengisolasi efek samping dari menggunakan satu bidang statis kelas, karena menghitung satu bidang mengarah untuk menghitung semua bidang statis kelas ini.
Jika Anda menyentuh satu bidang, Anda akan memengaruhi semuanya. Dalam kompiler AOT, ini membuatnya sangat sulit untuk mengoptimalkan referensi bidang statis, bahkan untuk bidang dengan nilai konstan yang mudah diurai. Setelah setidaknya satu bidang statis yang dirancang ulang berantakan di antara bidang, menjadi tidak mungkin untuk menganalisis sepenuhnya semua bidang kelas ini. Masalah serupa memanifestasikan dirinya dengan mekanisme yang diusulkan sebelumnya untuk mengimplementasikan konvolusi konstanta (selama operasi javac ) untuk bidang konstan dengan inisialisasi kompleks.
Contoh inisialisasi bidang yang dirancang ulang, yang terjadi di berbagai proyek di setiap langkah, di setiap file, adalah inisialisasi dari logger.
private final static Logger LOGGER = Logger.getLogger("com.foo.Bar");
Inisialisasi yang terlihat tidak berbahaya ini meluncurkan di bawah tenda sejumlah besar pekerjaan yang akan dilakukan selama inisialisasi kelas - namun, sangat tidak mungkin bahwa logger benar-benar diperlukan pada saat kelas diinisialisasi, atau mungkin tidak diperlukan sama sekali. Kemampuan untuk menunda pembuatannya sampai penggunaan nyata pertama akan menyederhanakan inisialisasi, dan dalam beberapa kasus akan membantu untuk menghindari inisialisasi ini sama sekali.
Variabel final sangat berguna, mereka adalah mekanisme utama Java API untuk menunjukkan keteguhan nilai. Variabel malas juga bekerja dengan baik. Dimulai dengan Java 7, mereka mulai memainkan peran yang semakin penting di bagian dalam JDK, ditandai dengan penjelasan @Stable
. JIT dapat mengoptimalkan variabel final dan stabil - jauh lebih baik daripada hanya beberapa variabel. Menambahkan variabel akhir yang malas akan memungkinkan pola penggunaan yang bermanfaat ini menjadi lebih umum, sehingga memungkinkan untuk digunakan di lebih banyak tempat. Akhirnya, menggunakan variabel akhir malas akan memungkinkan perpustakaan seperti JDK untuk mengurangi ketergantungan pada kode <clinit>
, yang pada gilirannya akan mengurangi waktu startup dan meningkatkan kualitas optimasi AOT.
Deskripsi
Bidang ini dapat dideklarasikan dengan pengubah lazy
baru, yang merupakan kata kunci kontekstual yang dirasakan secara eksklusif sebagai pengubah. Bidang seperti ini disebut bidang malas, dan juga harus memiliki pengubah static
dan final
.
Bidang malas harus memiliki penginisialisasi. Compiler dan runtime setuju untuk memulai penginisialisasi tepat ketika variabel pertama kali digunakan, dan bukan saat menginisialisasi kelas yang dimiliki bidang ini.
Setiap bidang lazy static final
dikaitkan pada waktu kompilasi dengan elemen pool konstan yang mewakili nilainya. Karena elemen-elemen dari kumpulan konstan sendiri dihitung dengan malas, itu cukup untuk hanya menetapkan nilai yang tepat untuk setiap variabel akhir malas statis yang terkait dengan elemen ini. (Anda dapat mengikat lebih dari satu variabel malas ke satu elemen, tetapi ini bukan fitur yang berguna atau bermakna.) Nama atribut adalah LazyValue
, dan itu harus merujuk ke elemen gender konstan yang dapat dikodekan-ldc menjadi nilai yang dapat dikonversi ke tipe bidang lazy . Hanya gips yang sudah digunakan di MethodHandle.invoke
.
Dengan demikian, bidang statis malas dapat dianggap sebagai alias bernama untuk elemen kumpulan konstan dalam kelas yang menyatakan bidang ini. Alat seperti kompiler entah bagaimana dapat mencoba menggunakan bidang ini.
Bidang lazy tidak pernah merupakan variabel konstan (dalam arti JLS 4.12.4) dan secara eksplisit dikecualikan dari berpartisipasi dalam ekspresi konstan (dalam arti JLS 15.28). Oleh karena itu, ia tidak pernah menangkap atribut ConstantValue
, bahkan jika initializer-nya adalah ekspresi konstan. Sebagai gantinya, bidang malas menangkap jenis atribut classfile baru yang disebut LazyValue
, yang LazyValue
JVM ketika menautkan ke bidang tertentu ini. Format atribut baru ini mirip dengan yang sebelumnya, karena atribut ini juga menunjuk ke elemen kumpulan konstan, dalam hal ini, atribut yang diselesaikan ke nilai bidang.
Ketika bidang statis malas terhubung, proses normal mengeksekusi inisialisasi kelas tidak boleh hilang. Sebaliknya, metode mendeklarasikan kelas <clinit>
diinisialisasi sesuai dengan aturan yang didefinisikan dalam JVMS 5.5. Dengan kata lain, bytecode getstatic
untuk bidang statis malas melakukan penautan yang sama seperti untuk bidang statis apa pun . Setelah inisialisasi (atau selama inisialisasi yang sudah dimulai dari utas saat ini), JVM menyelesaikan elemen pool konstan yang terkait dengan bidang dan menyimpan nilai yang diperoleh dari pool konstan di bidang ini.
Karena lazy static final tidak boleh kosong, mereka tidak dapat diberi nilai apa pun - bahkan dalam beberapa konteks tempat ini berfungsi untuk variabel final kosong.
Selama kompilasi, semua bidang statis malas diinisialisasi secara independen dari bidang statis non-malas, terlepas dari lokasi mereka di kode sumber. Oleh karena itu, pembatasan lokasi bidang statis tidak berlaku untuk bidang statis malas. Penginisialisasi bidang statis malas dapat menggunakan bidang statis apa pun dari kelas yang sama, terlepas dari urutan di mana mereka muncul di sumber. Penginisialisasi bidang non-statis atau kelas penginisialisasi dapat mengakses bidang malas, terlepas dari apa urutan dalam sumber mereka relatif satu sama lain. Biasanya, melakukan ini bukanlah ide yang paling masuk akal, karena seluruh makna nilai-nilai malas hilang, tetapi mungkin dapat digunakan entah bagaimana dalam ekspresi kondisional atau pada aliran kontrol. Oleh karena itu, bidang statis malas dapat diperlakukan lebih seperti bidang kelas lain - dalam arti bahwa bidang tersebut dapat dirujuk dalam urutan apa pun dari bagian mana pun dari kelas tempat mereka dideklarasikan.
Bidang malas dapat dideteksi menggunakan API refleksi menggunakan dua metode API baru di java.lang.reflect.Field
. Metode isLazy
baru mengembalikan true
jika dan hanya jika bidang memiliki pemodifikasi lazy
. Metode isAssigned
baru mengembalikan false
jika dan hanya jika bidangnya malas dan masih belum diinisialisasi pada saat isAssigned
. (Ini dapat mengembalikan true hampir pada panggilan berikutnya di utas yang sama, tergantung pada keberadaan ras). Tidak ada cara untuk mengetahui apakah suatu bidang diinisialisasi, selain menggunakan isAssigned
.
(Panggilan isAssigned
diperlukan untuk membantu dengan masalah yang jarang terjadi terkait dengan penyelesaian dependensi melingkar. Mungkin kita bisa melakukannya tanpa menerapkan metode ini. Namun, orang yang menulis kode dengan variabel malas terkadang ingin tahu apakah nilainya diatur ke variabel seperti itu atau belum, dengan cara yang sama seperti pengguna mutex kadang-kadang ingin mengetahui dari mutex apakah terkunci atau tidak, tetapi mereka tidak benar-benar ingin dikunci)
Ada satu batasan yang tidak biasa pada bidang akhir malas: mereka tidak boleh diinisialisasi ke nilai default mereka. Yaitu, bidang referensi malas tidak boleh diinisialisasi ke null
, dan tipe numerik tidak boleh memiliki nilai nol. Nilai boolean yang malas dapat diinisialisasi dengan hanya satu nilai - true
, karena false
adalah nilai standarnya. Jika penginisialisasi bidang statis malas mengembalikan nilai standarnya, penautan bidang ini akan gagal dengan kesalahan yang sesuai.
Pembatasan ini diperkenalkan untuk itu. untuk memungkinkan implementasi JVM untuk memesan nilai default sebagai nilai pengawas internal yang menandai keadaan bidang yang tidak diinisialisasi. Nilai default sudah ditetapkan dalam nilai awal bidang apa pun, ditetapkan pada saat persiapan (ini dijelaskan dalam JLS 5.4.2). Jadi nilai ini secara alami sudah ada pada awal siklus hidup bidang apa pun, dan karenanya merupakan pilihan logis untuk digunakan sebagai nilai pengawas yang memantau keadaan bidang ini. Menggunakan aturan ini, Anda tidak akan pernah bisa mendapatkan nilai default asli dari bidang statis malas. Untuk ini, JVM dapat, misalnya, menerapkan bidang malas sebagai tautan abadi ke elemen kumpulan konstan yang sesuai.
Pembatasan pada nilai-nilai standar dapat dielakkan dengan membungkus nilai-nilai (yang mungkin sama dengan nilai-nilai standar) dalam kotak atau wadah dari beberapa jenis yang sesuai. Angka nol dapat dibungkus dengan referensi Integer bukan nol. Jenis non-primitif dapat dibungkus dalam Opsional, yang menjadi kosong jika mencapai nol.
Untuk mempertahankan kebebasan dalam cara mengimplementasikan fitur, persyaratan untuk metode yang isAssigned
khusus diremehkan. Jika JVM dapat membuktikan bahwa variabel statis malas dapat diinisialisasi tanpa efek eksternal yang dapat diamati, ia dapat melakukan inisialisasi ini kapan saja. Dalam hal ini, isAssigned
akan mengembalikan true
bahkan jika getfield
tidak pernah dipanggil. Satu-satunya persyaratan yang dikenakan pada isAssigned
adalah bahwa jika mengembalikan false
, maka tidak ada efek samping dari inisialisasi variabel yang harus diamati di utas saat ini. Dan jika dia kembali true
, maka utas saat ini mungkin di masa depan mengamati efek samping inisialisasi. Kontrak semacam itu memungkinkan kompiler untuk mengganti getstatic
dengan getstatic
untuk bidangnya sendiri, yang memungkinkan JVM untuk tidak memonitor status terperinci dari variabel akhir yang memiliki elemen umum atau yang mengalami degenerasi dalam kumpulan konstan.
Beberapa utas dapat memasuki kondisi balapan untuk menginisialisasi bidang akhir yang malas. Seperti yang sudah terjadi dengan CONSTANT_Dynamic
, JVM memilih pemenang yang arbitrer dari lomba ini dan memberikan nilai pemenang ini ke semua utas yang berpartisipasi dalam perlombaan, dan menulisnya untuk semua upaya selanjutnya untuk mendapatkan nilai. Untuk menyiasati balapan, implementasi JVM tertentu dapat mencoba menggunakan operasi CAS, jika platform mendukungnya, pemenang balapan akan melihat nilai default sebelumnya, dan yang kalah akan melihat nilai non-default yang memenangkan perlombaan.
Dengan demikian, aturan yang ada untuk penugasan tunggal variabel final terus bekerja dan sekarang menangkap semua kesulitan komputasi malas.
Logika yang sama berlaku untuk penerbitan aman menggunakan bidang terakhir - itu sama untuk bidang malas dan non-malas.
Perhatikan bahwa kelas dapat mengonversi bidang statis menjadi bidang statis malas tanpa merusak kompatibilitas biner. getstatic
klien getstatic
identik dalam kedua kasus. Ketika deklarasi variabel berubah menjadi lazy, getstatic
ditautkan dengan cara yang berbeda.
Solusi alternatif
Anda bisa menggunakan kelas bersarang sebagai wadah untuk variabel malas.
Anda dapat mendefinisikan sesuatu seperti API perpustakaan untuk mengelola nilai malas atau (lebih umum) data yang monoton.
Refactor apa yang akan mereka buat sebagai variabel statis malas sehingga mereka berubah menjadi metode statis nolary dan tubuh mereka diterbitkan menggunakan konstanta ldc CONSTANT_Dynamic, dalam beberapa cara.
(Catatan: <clinit>
di atas tidak menyediakan cara biner yang kompatibel untuk memisahkan secara konstan konstanta statis yang ada dari <clinit>
)
Jika kita berbicara tentang menyediakan lebih banyak fungsionalitas, Anda dapat mengizinkan bidang malas menjadi non-statis atau non-final, sambil mempertahankan korespondensi dan analogi saat ini antara perilaku bidang statis dan non-statis. Pool konstan tidak bisa menjadi repositori untuk bidang non-statis, tetapi masih bisa menampung metode bootstrap (tergantung pada instance saat ini). Array beku (jika diterapkan) bisa mendapatkan opsi malas. Studi semacam itu adalah dasar yang baik untuk proyek masa depan yang dibangun berdasarkan dokumen ini. Dan omong-omong, peluang seperti itu membuat keputusan kami untuk melarang nilai standar bahkan lebih bermakna.
Variabel malas harus diinisialisasi menggunakan ekspresi inisialisasi mereka sendiri. Kadang-kadang ini tampak seperti batasan yang sangat tidak menyenangkan yang membuat kita kembali ke masa penemuan variabel akhir yang kosong. Ingat bahwa variabel-variabel akhir yang kosong ini dapat diinisialisasi dengan blok kode yang sewenang-wenang, termasuk logika try-akhirnya, dan mereka dapat diinisialisasi dalam kelompok daripada secara bersamaan. Di masa depan, akan mungkin untuk mencoba menerapkan kemungkinan yang sama ke variabel akhir yang malas. Mungkin satu atau lebih variabel malas dapat dikaitkan dengan blok privat kode inisialisasi yang tugasnya untuk menetapkan setiap variabel tepat satu kali, seperti yang terjadi dengan penginisialisasi kelas atau konstruktor objek. Arsitektur fitur semacam itu dapat menjadi lebih jelas setelah munculnya dekonstruktor, karena tugas-tugas yang mereka selesaikan berpotongan dalam beberapa hal.
Menit periklanan. Konferensi Joker 2018 akan diadakan segera, di mana akan ada banyak spesialis terkemuka di Jawa dan JVM. Lihat daftar lengkap pembicara dan laporan di situs web resmi .
Penulis
John Rose adalah insinyur dan arsitek JVM di Oracle. Insinyur Utama Proyek Mesin Da Vinci (bagian dari OpenJDK). Engineer Utama JSR 292 (Mendukung Bahasa yang Diketik Secara Dinamis pada Platform Java), mengkhususkan diri dalam panggilan dinamis dan topik terkait seperti tipe profil dan optimalisasi kompiler tingkat lanjut. Sebelumnya, ia bekerja di kelas dalam, membuat port HotSpot asli pada SPARC, Unsafe API, dan juga mengembangkan banyak bahasa dinamis, paralel, dan hibrid, termasuk Common Lisp, Scheme ("esh"), pengikat dinamis untuk C ++.
Penerjemah
Oleg Chirukhin - pada saat menulis teks ini dia bekerja sebagai manajer komunitas di perusahaan JUG.ru Group, dia terlibat dalam mempopulerkan platform Java. Sebelum bergabung dengan JRG, ia ikut serta dalam pengembangan sistem informasi perbankan dan pemerintah, ekosistem bahasa pemrograman yang ditulis sendiri, dan permainan online. Minat penelitian saat ini termasuk mesin virtual, kompiler, dan bahasa pemrograman.