Invokedynamic 101

Rilis Java 7 dari Oracle memperkenalkan invokedynamicinstruksi bytecode baru ke Java Virtual Machine (JVM) dan java.lang.invokepakej API baru ke pustaka kelas standard. Catatan ini memperkenalkan anda kepada arahan dan API ini.

Apa dan bagaimana invokedynamic

Q: Apa itu invokedynamic?

A: invokedynamic adalah arahan bytecode yang memudahkan pelaksanaan bahasa dinamik (untuk JVM) melalui pemanggilan kaedah dinamik. Arahan ini dijelaskan dalam Java SE 7 Edisi Spesifikasi JVM.

Bahasa dinamik dan statik

A bahasa dinamik (juga dikenali sebagai bahasa dinamik ditaip ) ialah bahasa pengaturcaraan peringkat tinggi yang memeriksa jenis biasanya dilakukan pada masa berjalan, ciri yang dikenali sebagai menaip dinamik . Pemeriksaan jenis mengesahkan bahawa program adalah jenis selamat : semua argumen operasi mempunyai jenis yang betul. Groovy, Ruby, dan JavaScript adalah contoh bahasa dinamik. ( @groovy.transform.TypeCheckedAnotasi menyebabkan Groovy menaip cek pada waktu kompilasi.)

Sebaliknya, bahasa statik (juga dikenal sebagai bahasa yang ditaip secara statik ) melakukan pemeriksaan jenis pada waktu kompilasi, suatu ciri yang dikenali sebagai menaip statik . Pengkompilasi mengesahkan bahawa program adalah jenis yang betul, walaupun mungkin menangguhkan beberapa jenis pemeriksaan ke waktu proses (berfikir dan checkcastarahan). Java adalah contoh bahasa statik. Penyusun Java menggunakan maklumat jenis ini untuk menghasilkan kod bytek yang ditaip kuat, yang dapat dilaksanakan dengan cekap oleh JVM.

S: Bagaimana invokedynamicmemudahkan pelaksanaan bahasa yang dinamik?

J: Dalam bahasa yang dinamik, pemeriksaan jenis biasanya berlaku pada waktu runtime. Pembangun mesti lulus jenis yang sesuai atau berisiko mengalami kegagalan waktu operasi. Selalunya ini java.lang.Objectadalah jenis yang paling tepat untuk argumen kaedah. Keadaan ini menyukarkan pemeriksaan jenis, yang mempengaruhi prestasi.

Cabaran lain ialah bahasa dinamik biasanya menawarkan keupayaan untuk menambah medan / kaedah dan mengeluarkannya dari kelas yang ada. Akibatnya, perlu untuk menangguhkan resolusi kelas, kaedah, dan lapangan hingga waktu berjalan. Juga, selalunya perlu untuk menyesuaikan kaedah pemohon dengan sasaran yang mempunyai tanda tangan yang berbeza.

Cabaran ini secara tradisional memerlukan sokongan runtime ad hoc untuk dibangun di atas JVM. Sokongan ini merangkumi kelas jenis pembungkus, menggunakan jadual hash untuk memberikan resolusi simbol dinamik, dan sebagainya. Bytecode dihasilkan dengan titik masuk ke runtime dalam bentuk panggilan kaedah menggunakan salah satu daripada empat arahan kaedah-permohonan:

  • invokestaticdigunakan untuk menggunakan statickaedah.
  • invokevirtualdigunakan untuk memanggil publicdan protectedbukan statickaedah melalui pengiriman dinamik.
  • invokeinterfaceserupa dengan invokevirtualkecuali kaedah pengiriman berdasarkan jenis antara muka.
  • invokespecialdigunakan untuk menggunakan kaedah inisialisasi contoh (konstruktor) serta privatekaedah dan kaedah superclass kelas semasa.

Sokongan jangka masa ini mempengaruhi prestasi. Kod bytec yang dihasilkan selalunya memerlukan beberapa pemanggilan kaedah JVM sebenar untuk satu pemanggilan kaedah bahasa dinamik. Refleksi digunakan secara meluas dan menyumbang kepada penurunan prestasi. Juga, banyak jalan pelaksanaan yang berbeza menjadikan pengkomputer just-in-time (JIT) JVM tidak dapat menerapkan pengoptimuman.

Untuk mengatasi prestasi yang buruk, invokedynamicarahan tersebut tidak dapat disokong dengan sokongan jangka masa ad hoc. Sebagai gantinya, bootstraps panggilan pertama dengan menggunakan logik runtime yang dengan berkesan memilih kaedah sasaran, dan panggilan berikutnya biasanya memanggil kaedah sasaran tanpa harus melakukan boot semula.

invokedynamicjuga memberi manfaat kepada pelaksana bahasa dinamik dengan menyokong sasaran laman panggilan yang berubah secara dinamik - laman panggilan , lebih khusus lagi, laman panggilan dinamik adalah invokedynamicarahan. Selanjutnya, kerana JVM menyokong secara dalaman invokedynamic, arahan ini dapat dioptimumkan dengan lebih baik oleh penyusun JIT.

Kaedah mengendalikan

S: Saya faham bahawa invokedynamickaedah ini berfungsi dengan kaedah pengendalian untuk memudahkan pemanggilan kaedah dinamik. Apakah kaedah pengendalian?

J: Pegangan kaedah adalah "rujukan yang diketik dan dapat dilaksanakan secara langsung ke kaedah yang mendasari, konstruktor, medan, atau operasi tingkat rendah yang serupa, dengan transformasi argumen pilihan atau nilai kembali." Dengan kata lain, ini mirip dengan penunjuk fungsi gaya-C yang menunjuk ke kod yang dapat dieksekusi - sasaran - dan yang dapat ditangguhkan untuk memanggil kod ini. Pengendalian kaedah digambarkan oleh java.lang.invoke.MethodHandlekelas abstrak .

S: Bolehkah anda memberikan contoh kaedah mudah untuk membuat dan memohon?

J: Lihat Penyenaraian 1.

Penyenaraian 1. MHD.java(versi 1)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; public class MHD { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findStatic(MHD.class, "hello", MethodType.methodType(void.class)); mh.invokeExact(); } static void hello() { System.out.println("hello"); } }

Penyenaraian 1 menerangkan kaedah demonstrasi pengendalian kaedah yang terdiri daripada main()dan hello()kaedah kelas. Matlamat program ini adalah untuk menggunakan hello()kaedah pengendalian.

main()Tugas pertama adalah mendapatkan java.lang.invoke.MethodHandles.Lookupobjek. Objek ini adalah kilang untuk membuat pengendalian kaedah dan digunakan untuk mencari sasaran seperti kaedah maya, kaedah statik, kaedah khas, pembina, dan aksesori lapangan. Tambahan pula, ini bergantung pada konteks pemanggilan laman web panggilan dan menguatkuasakan sekatan akses pengendalian kaedah setiap kali kaedah pengendalian dibuat. Dengan kata lain, laman panggilan (seperti main()kaedah Penyenaraian 1 yang bertindak sebagai laman panggilan) yang memperoleh objek carian hanya dapat mengakses sasaran yang dapat diakses ke laman panggilan. Objek carian diperoleh dengan menggunakan kaedah java.lang.invoke.MethodHandleskelas MethodHandles.Lookup lookup().

publicLookup()

MethodHandlesjuga menyatakan MethodHandles.Lookup publicLookup()kaedah. Tidak seperti lookup(), yang dapat digunakan untuk mendapatkan pegangan kaedah ke mana-mana kaedah / konstruktor atau bidang yang publicLookup()dapat diakses, dapat digunakan untuk mendapatkan pegangan metode ke medan yang dapat diakses secara terbuka atau kaedah / konstruktor yang hanya dapat diakses oleh umum.

Setelah memperoleh objek pencarian, kaedah objek MethodHandle findStatic(Class refc, String name, MethodType type)ini dipanggil untuk mendapatkan kaedah menangani hello()kaedah tersebut. Argumen pertama yang disampaikan findStatic()adalah rujukan ke kelas ( MHD) dari mana kaedah ( hello()) diakses, dan argumen kedua adalah nama kaedah. Argumen ketiga adalah contoh jenis kaedah , yang "mewakili argumen dan jenis pengembalian yang diterima dan dikembalikan oleh pegangan kaedah, atau argumen dan jenis pengembalian yang dilalui dan diharapkan oleh pemanggil menangani kaedah." Ia diwakili oleh satu contoh daripada java.lang.invoke.MethodTypekelas, dan diperolehi (dalam contoh ini) dengan memanggil java.lang.invoke.MethodType's MethodType methodType(Class rtype)kaedah. Kaedah ini dipanggil kerana hello()hanya memberikan jenis pengembalian, yang kebetulanvoid. Jenis pengembalian ini disediakan untuk methodType()menggunakan void.classkaedah ini.

Pemegang kaedah yang dikembalikan ditugaskan untuk mh. Objek ini kemudiannya digunakan untuk panggilan MethodHandle's Object invokeExact(Object... args)kaedah, untuk menggunakan kaedah yang mengendalikan. Dengan kata lain, invokeExact()hasilnya hello()dipanggil, dan helloditulis ke aliran output standard. Kerana invokeExact()diisytiharkan untuk membuang Throwable, saya telah dilampirkan throws Throwablekepada main()header kaedah.

S: Dalam jawapan anda sebelumnya, anda menyebutkan bahawa objek carian hanya dapat mengakses sasaran yang dapat diakses ke laman web panggilan. Bolehkah anda memberikan contoh yang menunjukkan cuba mendapatkan kaedah menangani sasaran yang tidak dapat diakses?

J: Lihat Penyenaraian 2.

Penyenaraian 2. MHD.java(versi 2)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; class HW { public void hello1() { System.out.println("hello from hello1"); } private void hello2() { System.out.println("hello from hello2"); } } public class MHD { public static void main(String[] args) throws Throwable { HW hw = new HW(); MethodHandles.Lookup lookup = MethodHandles.lookup(); MethodHandle mh = lookup.findVirtual(HW.class, "hello1", MethodType.methodType(void.class)); mh.invoke(hw); mh = lookup.findVirtual(HW.class, "hello2", MethodType.methodType(void.class)); } }

Penyenaraian 2 menyatakan HW(Hello, World) dan MHDkelas. HWmenyatakan publichello1()kaedah contoh dan privatehello2()kaedah contoh. MHDmenyatakan main()kaedah yang akan cuba menggunakan kaedah ini.

main()Tugas pertama adalah untuk membuat HWpersiapan untuk memohon hello1()dan hello2(). Seterusnya, ia memperoleh objek pencarian dan menggunakan objek ini untuk mendapatkan pegangan kaedah untuk memohon hello1(). Kali ini, MethodHandles.Lookup's findVirtual()kaedah dipanggil dan hujah pertama diserahkan kepada kaedah ini ialah Classobjek menerangkan HWkelas.

Ternyata itu findVirtual()akan berjaya, dan mh.invoke(hw);ungkapan berikutnya akan dipanggil hello1(), menghasilkan hello from hello1output.

Kerana hello1()adalah public, ia boleh diakses kepada main()laman kaedah panggilan. Sebaliknya, hello2()tidak boleh diakses. Akibatnya, findVirtual()pemanggilan kedua akan gagal dengan IllegalAccessException.

Semasa anda menjalankan aplikasi ini, anda harus memerhatikan output berikut:

hello from hello1 Exception in thread "main" java.lang.IllegalAccessException: member is private: HW.hello2()void, from MHD at java.lang.invoke.MemberName.makeAccessException(MemberName.java:507) at java.lang.invoke.MethodHandles$Lookup.checkAccess(MethodHandles.java:1172) at java.lang.invoke.MethodHandles$Lookup.checkMethod(MethodHandles.java:1152) at java.lang.invoke.MethodHandles$Lookup.accessVirtual(MethodHandles.java:648) at java.lang.invoke.MethodHandles$Lookup.findVirtual(MethodHandles.java:641) at MHD.main(MHD.java:27)

S: Senarai 1 dan 2 menggunakan kaedah invokeExact()dan invoke()kaedah untuk menjalankan pengendalian kaedah. Apakah perbezaan antara kaedah ini?

J: Walaupun invokeExact()dan invoke()dirancang untuk melaksanakan pengendalian metode (sebenarnya, kod sasaran yang dimaksudkan oleh pengendali metode), mereka berbeza ketika melakukan penukaran jenis pada argumen dan nilai pengembalian. invokeExact()tidak melakukan penukaran jenis serasi automatik pada argumen. Argumennya (atau ungkapan argumen) mestilah sesuai dengan jenis tandatangan kaedah, dengan setiap argumen disediakan secara berasingan, atau semua argumen disediakan bersama sebagai array. invoke()memerlukan argumennya (atau ungkapan argumen) agar sesuai dengan jenis yang sesuai dengan tandatangan kaedah - penukaran jenis automatik dilakukan, dengan setiap argumen disediakan secara berasingan, atau semua argumen disediakan bersama sebagai array.

S: Bolehkah anda memberi saya contoh yang menunjukkan cara memanggil pengambil dan pengatur bidang contoh?

J: Lihat Penyenaraian 3.

Penyenaraian 3. MHD.java(versi 3)

import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; class Point { int x; int y; } public class MHD { public static void main(String[] args) throws Throwable { MethodHandles.Lookup lookup = MethodHandles.lookup(); Point point = new Point(); // Set the x and y fields. MethodHandle mh = lookup.findSetter(Point.class, "x", int.class); mh.invoke(point, 15); mh = lookup.findSetter(Point.class, "y", int.class); mh.invoke(point, 30); mh = lookup.findGetter(Point.class, "x", int.class); int x = (int) mh.invoke(point); System.out.printf("x = %d%n", x); mh = lookup.findGetter(Point.class, "y", int.class); int y = (int) mh.invoke(point); System.out.printf("y = %d%n", y); } }

Penyenaraian 3 memperkenalkan Pointkelas dengan sepasang bidang contoh bilangan bulat 32-bit bernama xdan y. Setter dan getter setiap bidang ini diakses dengan memanggil MethodHandles.Lookup's findSetter()dan findGetter()kaedah, dan terhasil yang MethodHandledikembalikan. Setiap findSetter()dan findGetter()memerlukan Classargumen yang mengenal pasti kelas bidang, nama bidang, dan Classobjek yang mengenal pasti tandatangan bidang.

The invoke()kaedah yang digunakan untuk melaksanakan penetap atau getter-- di belakang tabir, medan kejadian diakses melalui ini JVM putfielddan getfieldarahan. Kaedah ini mensyaratkan bahawa rujukan ke objek yang bidangnya diakses diteruskan sebagai argumen awal. Untuk pemanggil pemanggil, argumen kedua, yang terdiri dari nilai yang diberikan ke lapangan, juga harus dilalui.

Semasa anda menjalankan aplikasi ini, anda harus memerhatikan output berikut:

x = 15 y = 30

S: Definisi kaedah anda merangkumi frasa "dengan transformasi argumen pilihan atau nilai kembali". Bolehkah anda memberikan contoh transformasi hujah?

A: Saya telah membuat satu contoh berdasarkan Mathclass double pow(double a, double b)kaedah kelas. Dalam contoh ini, saya memperoleh pegangan kaedah ke pow()kaedah, dan mengubah pegangan kaedah ini supaya argumen kedua yang disampaikan pow()selalu 10. Lihat Penyenaraian 4.