Adakah saat ini (Java 6) hal yang dapat Anda lakukan dalam bytecode Java yang tidak dapat Anda lakukan dari dalam bahasa Java?
Saya tahu keduanya Turing lengkap, jadi baca "dapat melakukan" karena "dapat melakukan secara signifikan lebih cepat / lebih baik, atau hanya dengan cara yang berbeda".
Saya berpikir tentang bytecodes tambahan invokedynamic
, yang tidak dapat dibuat menggunakan Java, kecuali yang spesifik untuk versi masa depan.
rol
assembler, yang tidak bisa Anda tulis dalam C ++.(x<<n)|(x>>(32-n))
denganrol
instruksi.Jawaban:
Sejauh yang saya tahu tidak ada fitur utama dalam bytecodes yang didukung oleh Java 6 yang juga tidak dapat diakses dari kode sumber Java. Alasan utama untuk ini jelas bahwa bytecode Java dirancang dengan mempertimbangkan bahasa Jawa.
Namun, ada beberapa fitur yang tidak diproduksi oleh kompiler Java modern:
The
ACC_SUPER
flag :Ini adalah flag yang dapat diatur pada kelas dan menentukan bagaimana kasus sudut spesifik
invokespecial
bytecode ditangani untuk kelas ini. Ini diatur oleh semua kompiler Java modern (di mana "modern" adalah> = Java 1.1, jika saya ingat dengan benar) dan hanya kompiler Java kuno yang menghasilkan file kelas di mana ini tidak disetel. Bendera ini hanya ada untuk alasan kompatibilitas ke belakang. Perhatikan bahwa dimulai dengan Java 7u51, ACC_SUPER diabaikan sepenuhnya karena alasan keamanan.The
jsr
/ret
bytecodes.Bytecode ini digunakan untuk mengimplementasikan sub-rutin (kebanyakan untuk mengimplementasikan
finally
blok). Mereka tidak lagi diproduksi sejak Java 6 . Alasan penghentian mereka adalah bahwa mereka banyak mempersulit verifikasi statis tanpa hasil yang besar (yaitu kode yang menggunakan hampir selalu dapat diimplementasikan kembali dengan lompatan normal dengan overhead yang sangat sedikit).Memiliki dua metode dalam kelas yang hanya berbeda dalam tipe pengembalian.
Spesifikasi bahasa Java tidak memungkinkan dua metode di kelas yang sama ketika mereka hanya berbeda dalam jenis kembali (yaitu nama yang sama, daftar argumen yang sama, ...). Namun spesifikasi JVM, tidak memiliki batasan seperti itu, sehingga file kelas dapat berisi dua metode seperti itu, tidak ada cara untuk menghasilkan file kelas seperti itu menggunakan kompiler Java normal. Ada contoh / penjelasan yang bagus dalam jawaban ini .
sumber
a
lain keA
dalam file JAR. Butuh sekitar setengah jam unzip di mesin Windows sebelum saya menyadari di mana kelas yang hilang. :)'.'
,';'
,'['
, atau'/'
. Nama metode sama, tetapi mereka juga tidak bisa mengandung'<'
atau'>'
. (Dengan pengecualian dari<init>
dan<clinit>
sebagai contoh dan konstruktor statis.) Saya harus menunjukkan bahwa jika Anda mengikuti spesifikasi secara ketat, nama-nama kelas sebenarnya jauh lebih terbatas, tetapi kendala tidak ditegakkan."throws ex1, ex2, ..., exn"
sebagai bagian dari tanda tangan metode; Anda tidak bisa menambahkan pengecualian melemparkan klausa ke metode yang diganti. TETAPI, JVM tidak peduli. Jadi, hanyafinal
metode yang benar-benar dijamin oleh JVM sebagai pengecualian-bebas - selain dariRuntimeException
s danError
s, tentu saja. Begitu banyak untuk penanganan pengecualian yang diperiksa: DSetelah bekerja dengan kode byte Java cukup lama dan melakukan beberapa penelitian tambahan tentang masalah ini, berikut adalah ringkasan dari temuan saya:
Jalankan kode dalam konstruktor sebelum memanggil konstruktor super atau konstruktor tambahan
Dalam bahasa pemrograman Java (JPL), pernyataan pertama konstruktor harus berupa doa dari konstruktor super atau konstruktor lain dari kelas yang sama. Ini tidak benar untuk kode byte Java (JBC). Dalam kode byte, sangat sah untuk mengeksekusi kode apa pun sebelum konstruktor, selama:
Setel bidang contoh sebelum memanggil konstruktor super atau konstruktor bantu
Seperti disebutkan sebelumnya, sangat sah untuk menetapkan nilai bidang instance sebelum memanggil konstruktor lain. Bahkan ada hack warisan yang membuatnya dapat mengeksploitasi "fitur" ini dalam versi Java sebelum 6:
Dengan cara ini, bidang dapat diatur sebelum konstruktor super dipanggil yang bagaimanapun tidak mungkin lagi. Di JBC, perilaku ini masih bisa diimplementasikan.
Cabang panggilan konstruktor super
Di Jawa, tidak mungkin mendefinisikan seperti panggilan konstruktor
Hingga Java 7u23, verifier HotSpot VM tidak melewatkan pemeriksaan ini, itulah mengapa itu mungkin. Ini digunakan oleh beberapa alat pembuat kode sebagai semacam peretasan tetapi tidak lagi sah untuk mengimplementasikan kelas seperti ini.Yang terakhir hanyalah bug dalam versi kompiler ini. Dalam versi kompiler yang lebih baru, ini dimungkinkan lagi.
Tentukan kelas tanpa konstruktor
Kompiler Java akan selalu menerapkan setidaknya satu konstruktor untuk kelas apa pun. Dalam kode byte Java, ini tidak diperlukan. Ini memungkinkan pembuatan kelas yang tidak dapat dibangun bahkan ketika menggunakan refleksi. Namun, menggunakan
sun.misc.Unsafe
masih memungkinkan untuk pembuatan instance seperti itu.Tetapkan metode dengan tanda tangan yang identik tetapi dengan tipe pengembalian yang berbeda
Dalam JPL, metode diidentifikasi sebagai unik dengan namanya dan tipe parameter mentahnya. Di JBC, jenis pengembalian mentah juga dipertimbangkan.
Tetapkan bidang yang tidak berbeda dengan nama tetapi hanya berdasarkan jenis
File kelas dapat berisi beberapa bidang dengan nama yang sama selama mereka menyatakan jenis bidang yang berbeda. JVM selalu merujuk ke bidang sebagai tupel nama dan tipe.
Lemparkan pengecualian yang tidak dideklarasikan tanpa menangkapnya
Java runtime dan kode byte Java tidak mengetahui konsep pengecualian yang diperiksa. Hanya kompiler Java yang memverifikasi bahwa pengecualian yang diperiksa selalu ditangkap atau dideklarasikan jika dilempar.
Gunakan doa metode dinamis di luar ekspresi lambda
Apa yang disebut pemanggilan metode dinamis dapat digunakan untuk apa saja, tidak hanya untuk ekspresi lambda Java. Menggunakan fitur ini memungkinkan misalnya untuk menonaktifkan logika eksekusi saat runtime. Banyak bahasa pemrograman dinamis yang bermuara pada JBC meningkatkan kinerja mereka dengan menggunakan instruksi ini. Dalam kode byte Java, Anda juga bisa meniru ekspresi lambda di Java 7 di mana kompiler belum memungkinkan untuk penggunaan doa metode dinamis sementara JVM sudah memahami instruksi.
Gunakan pengidentifikasi yang biasanya tidak dianggap sah
Pernah membayangkan menggunakan spasi dan pemisah garis dalam nama metode Anda? Buat JBC Anda sendiri dan semoga sukses untuk review kode. Satu-satunya karakter ilegal untuk pengidentifikasi adalah
.
,;
,[
dan/
. Selain itu, metode yang tidak disebutkan namanya<init>
atau<clinit>
tidak dapat berisi<
dan>
.Tetapkan ulang
final
parameter atauthis
referensifinal
parameter tidak ada di JBC dan akibatnya dapat dipindahkan. Setiap parameter, termasukthis
referensi hanya disimpan dalam array sederhana dalam JVM yang memungkinkan untuk menetapkan kembalithis
referensi pada indeks0
dalam kerangka metode tunggal.Tetapkan ulang
final
bidangSelama bidang terakhir diberikan dalam konstruktor, adalah sah untuk menetapkan kembali nilai ini atau bahkan tidak memberikan nilai sama sekali. Oleh karena itu, dua konstruktor berikut ini legal:
Untuk
static final
bidang, bahkan diizinkan untuk menetapkan kembali bidang di luar penginisialisasi kelas.Perlakukan konstruktor dan inisialisasi kelas seolah-olah mereka adalah metode
Ini lebih merupakan fitur konseptual tetapi konstruktor tidak diperlakukan secara berbeda dalam JBC daripada metode normal. Hanya verifikasi JVM yang memastikan bahwa konstruktor memanggil konstruktor hukum lain. Selain itu, itu hanya konvensi penamaan Java bahwa konstruktor harus dipanggil
<init>
dan bahwa initializer kelas dipanggil<clinit>
. Selain perbedaan ini, representasi metode dan konstruktor identik. Seperti yang ditunjukkan Holger dalam komentar, Anda bahkan dapat mendefinisikan konstruktor dengan tipe kembali selainvoid
atau penginisialisasi kelas dengan argumen, meskipun tidak mungkin untuk memanggil metode ini.Buat catatan asimetris * .
Saat membuat catatan
javac akan menghasilkan file kelas dengan bidang tunggal bernama
bar
, metode accessor bernamabar()
dan konstruktor mengambil satuObject
. Selain itu, atribut catatan untukbar
ditambahkan. Dengan membuat catatan secara manual, dimungkinkan untuk membuat, bentuk konstruktor yang berbeda, untuk melewati bidang dan mengimplementasikan accessor secara berbeda. Pada saat yang sama, masih dimungkinkan untuk membuat API refleksi percaya bahwa kelas mewakili catatan aktual.Panggil metode super apa pun (hingga Java 1.1)
Namun, ini hanya mungkin untuk Java versi 1 dan 1.1. Di JBC, metode selalu dikirim pada tipe target eksplisit. Ini berarti untuk
itu mungkin untuk diterapkan
Qux#baz
untuk memanggilFoo#baz
sambil melompatiBar#baz
. Meskipun masih mungkin untuk menetapkan permintaan eksplisit untuk memanggil implementasi metode super lain dari kelas super langsung, ini tidak lagi memiliki efek dalam versi Java setelah 1.1. Di Java 1.1, perilaku ini dikontrol dengan menetapkanACC_SUPER
bendera yang akan memungkinkan perilaku yang sama yang hanya memanggil implementasi kelas super langsung.Tentukan panggilan non-virtual dari metode yang dideklarasikan di kelas yang sama
Di Jawa, tidak mungkin untuk mendefinisikan kelas
Kode di atas akan selalu menghasilkan
RuntimeException
saatfoo
dipanggil pada instance dariBar
. Tidak mungkin mendefinisikanFoo::foo
metode untuk memanggil metode sendiribar
yang didefinisikan dalamFoo
. Sepertibar
metode contoh non-pribadi, panggilan selalu virtual. Namun, dengan kode byte, seseorang dapat menentukan permohonan untuk menggunakanINVOKESPECIAL
opcode yang secara langsung menautkanbar
metode panggilanFoo::foo
keFoo
versi. Opcode ini biasanya digunakan untuk menerapkan pemanggilan metode super tetapi Anda dapat menggunakan kembali opcode untuk menerapkan perilaku yang dijelaskan.Anotasi jenis butiran halus
Di Jawa, anotasi diterapkan sesuai dengan
@Target
anotasi yang dideklarasikan. Menggunakan manipulasi kode byte, dimungkinkan untuk menentukan anotasi secara independen dari kontrol ini. Juga, misalnya dimungkinkan untuk membuat anotasi tipe parameter tanpa membuat anotasi parameter bahkan jika@Target
anotasi berlaku untuk kedua elemen.Tetapkan atribut apa pun untuk suatu tipe atau anggotanya
Di dalam bahasa Java, hanya dimungkinkan untuk mendefinisikan anotasi untuk bidang, metode atau kelas. Di JBC, Anda pada dasarnya dapat menanamkan informasi apa pun ke dalam kelas Java. Untuk memanfaatkan informasi ini, Anda tidak dapat lagi mengandalkan mekanisme pemuatan kelas Java tetapi Anda perlu mengekstrak informasi meta sendiri.
Overflow dan secara implisit menetapkan
byte
,short
,char
danboolean
nilai-nilaiTipe primitif terakhir biasanya tidak dikenal di JBC tetapi hanya didefinisikan untuk tipe array atau untuk deskriptor bidang dan metode. Dalam instruksi kode byte, semua jenis yang disebutkan mengambil ruang 32 bit yang memungkinkan untuk mewakili mereka sebagai
int
. Secara resmi, hanyaint
,float
,long
dandouble
jenis ada dalam kode byte mana semua kebutuhan konversi eksplisit oleh aturan verifier JVM.Tidak melepaskan monitor
Sebuah
synchronized
blok sebenarnya terdiri dari dua pernyataan, satu untuk memperoleh dan satu untuk melepaskan monitor. Di JBC, Anda dapat memperoleh satu tanpa melepaskannya.Catatan : Dalam implementasi terbaru dari HotSpot, ini malah mengarah
IllegalMonitorStateException
pada akhir metode atau rilis implisit jika metode tersebut diakhiri oleh pengecualian itu sendiri.Tambahkan lebih dari satu
return
pernyataan ke inisialisasi tipeDi Jawa, bahkan initializer jenis sepele seperti
itu ilegal. Dalam kode byte, jenis penginisialisasi diperlakukan sama seperti metode lain, yaitu pernyataan pengembalian dapat didefinisikan di mana saja.
Buat loop tereduksi
Kompiler Java mengubah loop ke pernyataan goto dalam kode byte Java. Pernyataan seperti itu dapat digunakan untuk membuat loop tereduksi, yang tidak pernah dilakukan oleh kompiler Java.
Tentukan blok tangkapan rekursif
Dalam kode byte Java, Anda dapat mendefinisikan sebuah blok:
Pernyataan serupa dibuat secara implisit saat menggunakan
synchronized
blok di Jawa di mana pengecualian saat melepaskan monitor kembali ke instruksi untuk melepaskan monitor ini. Biasanya, tidak ada pengecualian yang terjadi pada instruksi seperti itu tetapi jika itu akan (mis. UsangThreadDeath
), monitor masih akan dirilis.Panggil metode default apa saja
Kompiler Java memerlukan beberapa kondisi yang harus dipenuhi untuk memungkinkan pemanggilan metode default:
B
memperluas antarmukaA
tetapi tidak mengesampingkan metodeA
, metode tersebut masih dapat dipanggil.Untuk kode byte Java, hanya kondisi kedua yang diperhitungkan. Namun yang pertama tidak relevan.
Meminta metode super pada contoh yang tidak
this
Kompiler Java hanya memungkinkan untuk memanggil metode super (atau antarmuka standar) pada contoh
this
. Dalam kode byte, bagaimanapun juga dimungkinkan untuk memanggil metode super pada instance dengan tipe yang sama seperti berikut ini:Akses anggota sintetis
Dalam kode byte Java, dimungkinkan untuk mengakses anggota sintetis secara langsung. Misalnya, perhatikan bagaimana dalam contoh berikut ini instance luar dari
Bar
instance lain diakses:Ini umumnya berlaku untuk bidang, kelas, atau metode sintetis apa pun.
Tetapkan informasi jenis umum tidak sinkron
Sementara Java runtime tidak memproses tipe generik (setelah compiler Java menerapkan tipe erasure), informasi ini masih dikaitkan dengan kelas yang dikompilasi sebagai informasi meta dan dapat diakses melalui API refleksi.
Verifier tidak memeriksa konsistensi dari
String
nilai-nilai meta data yang disandikan ini. Oleh karena itu dimungkinkan untuk mendefinisikan informasi tentang tipe generik yang tidak cocok dengan penghapusan. Sebagai rahasia, pernyataan berikut ini bisa benar:Juga, tanda tangan dapat didefinisikan sebagai tidak valid sehingga pengecualian runtime dilemparkan. Pengecualian ini dilemparkan ketika informasi diakses untuk pertama kalinya karena dievaluasi dengan malas. (Mirip dengan nilai anotasi dengan kesalahan.)
Tambahkan informasi meta parameter hanya untuk metode tertentu
Kompiler Java memungkinkan untuk menyematkan nama parameter dan informasi pengubah ketika mengkompilasi kelas dengan
parameter
flag diaktifkan. Dalam format file kelas Java, informasi ini disimpan per-metode yang memungkinkan untuk hanya menyertakan informasi metode tersebut untuk metode tertentu.Mengacaukan segalanya dan menghancurkan JVM Anda
Sebagai contoh, dalam kode byte Java, Anda dapat menentukan untuk memanggil metode apa pun pada jenis apa pun. Biasanya, pemverifikasi akan mengeluh jika suatu jenis tidak diketahui dari metode semacam itu. Namun, jika Anda memanggil metode yang tidak dikenal pada array, saya menemukan bug di beberapa versi JVM di mana verifier akan melewatkan ini dan JVM Anda akan selesai setelah instruksi dipanggil. Ini bukan fitur meskipun, tetapi secara teknis sesuatu yang tidak mungkin dengan javac dikompilasi Java. Java memiliki semacam validasi ganda. Validasi pertama diterapkan oleh kompiler Java, yang kedua oleh JVM ketika sebuah kelas dimuat. Dengan melewatkan kompiler, Anda mungkin menemukan titik lemah dalam validasi verifikasi. Ini lebih merupakan pernyataan umum daripada fitur.
Beri anotasi jenis penerima konstruktor ketika tidak ada kelas luar
Sejak Java 8, metode dan konstruktor non-statis kelas dalam dapat mendeklarasikan tipe penerima dan membubuhi keterangan jenis ini. Konstruktor dari kelas tingkat atas tidak dapat membubuhi keterangan jenis penerima mereka karena mereka paling tidak menyatakan satu.
Sejak
Foo.class.getDeclaredConstructor().getAnnotatedReceiverType()
Namun tidak mengembalikanAnnotatedType
mewakiliFoo
, adalah mungkin untuk menyertakan anotasi tipe untukFoo
's konstruktor langsung dalam file kelas di mana penjelasan ini kemudian dibaca oleh refleksi API.Gunakan instruksi kode byte yang tidak digunakan / lawas
Karena orang lain menamainya, saya akan memasukkannya juga. Java sebelumnya menggunakan subrutin oleh
JSR
danRET
pernyataan. JBC bahkan tahu jenis alamat pengirimnya sendiri untuk tujuan ini. Namun, penggunaan subrutin melakukan overcomplicate analisis kode statis yang mengapa instruksi ini tidak lagi digunakan. Sebaliknya, kompiler Java akan menduplikasi kode yang dikompilasinya. Namun, ini pada dasarnya menciptakan logika yang identik itulah sebabnya saya tidak benar-benar menganggapnya untuk mencapai sesuatu yang berbeda. Demikian pula, misalnya Anda dapat menambahkanNOOP
instruksi kode byte yang tidak digunakan oleh kompiler Java juga tetapi ini tidak akan benar-benar memungkinkan Anda untuk mencapai sesuatu yang baru juga. Seperti yang ditunjukkan dalam konteks, "petunjuk fitur" yang disebutkan ini sekarang dihapus dari himpunan opcode legal yang membuatnya lebih sedikit fitur.sumber
<clinit>
metode dengan mendefinisikan metode dengan nama<clinit>
tetapi menerima parameter atau memiliki jenis yang tidakvoid
kembali. Tetapi metode ini tidak terlalu berguna, JVM akan mengabaikannya dan kode byte tidak dapat memohonnya. Satu-satunya penggunaan akan membingungkan pembaca.IllegalMonitorStateException
jika Anda menghilangkanmonitorexit
instruksi. Dan jika ada metode keluar yang luar biasa yang gagal dilakukanmonitorexit
, ini akan me-reset monitor secara diam-diam.Berikut adalah beberapa fitur yang dapat dilakukan dalam bytecode Java tetapi tidak dalam kode sumber Java:
Melempar pengecualian yang diperiksa dari suatu metode tanpa menyatakan bahwa metode tersebut melemparkannya. Pengecualian yang dicentang dan tidak dicentang adalah hal yang hanya diperiksa oleh kompiler Java, bukan JVM. Karena itu misalnya Scala dapat membuang pengecualian yang diperiksa dari metode tanpa mendeklarasikannya. Meskipun dengan obat generik Java ada solusi yang disebut lemparan licik .
Memiliki dua metode dalam kelas yang hanya berbeda dalam jenis kembali, seperti yang telah disebutkan dalam jawaban Joachim : Spesifikasi bahasa Java tidak memungkinkan dua metode di kelas yang sama ketika mereka hanya berbeda dalam jenis kembali (yaitu nama yang sama, daftar argumen yang sama, ...). Namun spesifikasi JVM, tidak memiliki batasan seperti itu, sehingga file kelas dapat berisi dua metode seperti itu, tidak ada cara untuk menghasilkan file kelas seperti itu menggunakan kompiler Java normal. Ada contoh / penjelasan yang bagus dalam jawaban ini .
sumber
Thread.stop(Throwable)
lemparan licik. Saya menganggap yang sudah ditautkan lebih cepat.GOTO
dapat digunakan dengan label untuk membuat struktur kontrol Anda sendiri (selainfor
while
dll)this
variabel lokal di dalam suatu metodeSebagai titik terkait Anda bisa mendapatkan nama parameter untuk metode jika dikompilasi dengan debug ( Paranamer melakukan ini dengan membaca bytecode
sumber
override
variabel lokal ini?this
variabel memiliki indeks nol, tapi selain menjadi pra-diinisialisasi denganthis
referensi ketika memasuki metode contoh, itu hanya variabel lokal. Jadi, Anda dapat menulis nilai yang berbeda untuk itu, yang dapat bertindak seperti mengakhirithis
lingkup atau mengubahthis
variabel, tergantung pada bagaimana Anda menggunakannya.this
bisa dipindahkan? Saya pikir itu hanya kata ganti yang membuat saya bertanya-tanya apa artinya sebenarnya.Mungkin bagian 7A dalam dokumen ini menarik, meskipun ini tentang jebakan bytecode daripada fitur bytecode .
sumber
Dalam bahasa Jawa pernyataan pertama dalam konstruktor harus berupa panggilan ke konstruktor kelas super. Bytecode tidak memiliki batasan ini, sebaliknya aturannya adalah konstruktor kelas super atau konstruktor lain di kelas yang sama harus dipanggil untuk objek sebelum mengakses anggota. Ini harus memungkinkan lebih banyak kebebasan seperti:
Saya belum menguji ini, jadi tolong perbaiki saya jika saya salah.
sumber
Sesuatu yang dapat Anda lakukan dengan kode byte, bukan kode Java biasa, adalah menghasilkan kode yang dapat dimuat dan dijalankan tanpa kompiler. Banyak sistem memiliki JRE daripada JDK dan jika Anda ingin menghasilkan kode secara dinamis mungkin lebih baik, jika tidak lebih mudah, untuk menghasilkan kode byte daripada kode Java harus dikompilasi sebelum dapat digunakan.
sumber
Saya menulis pengoptimal bytecode ketika saya masih seorang I-Play, (itu dirancang untuk mengurangi ukuran kode untuk aplikasi J2ME). Salah satu fitur yang saya tambahkan adalah kemampuan untuk menggunakan bytecode inline (mirip dengan bahasa assembly inline di C ++). Saya berhasil mengurangi ukuran fungsi yang merupakan bagian dari metode perpustakaan dengan menggunakan instruksi DUP, karena saya membutuhkan nilai dua kali. Saya juga memiliki instruksi nol byte (jika Anda memanggil metode yang membutuhkan char dan Anda ingin melewatkan int, yang Anda tahu tidak perlu dilemparkan, saya menambahkan int2char (var) untuk mengganti char (var) dan itu akan menghapus instruksi i2c untuk mengurangi ukuran kode. Saya juga membuatnya melakukan float a = 2.3; float b = 3.4; float c = a + b; dan itu akan dikonversi ke titik tetap (lebih cepat, dan juga beberapa J2ME tidak mendukung floating point).
sumber
Di Jawa, jika Anda mencoba untuk menimpa metode publik dengan metode yang dilindungi (atau pengurangan akses lainnya), Anda mendapatkan kesalahan: "berusaha untuk menetapkan hak akses yang lebih lemah". Jika Anda melakukannya dengan bytecode JVM, verifikasi baik-baik saja dengan itu, dan Anda dapat memanggil metode ini melalui kelas induk seolah-olah mereka publik.
sumber