Kata pengantar
Kelas yang tidak aman digunakan di beberapa kelas kode sumber JDK. Kelas ini menyediakan beberapa fungsi yang mendasari untuk memotong JVM, dan implementasinya dapat meningkatkan efisiensi. Namun, itu adalah pedang bermata dua: seperti namanya yang diramalkan, tidak aman, dan ingatan yang dialokasikan harus dibebaskan secara manual (tidak didaur ulang oleh GC). Kelas yang tidak aman, memberikan alternatif sederhana untuk fitur -fitur tertentu dari JNI: memastikan efisiensi sambil membuat segalanya lebih mudah.
Kelas ini milik kelas di bawah sinar matahari.* API, dan itu bukan bagian nyata dari J2SE, jadi Anda mungkin tidak menemukan dokumentasi resmi, dan sayangnya, itu juga tidak memiliki dokumentasi kode yang lebih baik.
Artikel ini terutama tentang kompilasi dan terjemahan artikel berikut.
http://mishadoff.com/blog/java-magic-part-4-sun-dot-misc-dot-unsafe/
1. Sebagian besar metode API yang tidak aman adalah implementasi asli, yang terdiri dari 105 metode, terutama termasuk kategori berikut:
(1) Info Terkait. Terutama mengembalikan beberapa informasi memori tingkat rendah: ALamat (), pagesize ()
(2) Objek Terkait. Terutama Menyediakan Objek dan Metode Manipulasi Domainnya: AllocateInstance (), ObjectFieldOffset ()
(3) Kelas Terkait. Terutama menyediakan kelas dan metode manipulasi domain statisnya: staticfieldoffset (), defineclass (), defineanonymousclass (), ensureclassinitialized ()
(4) Array terkait. Metode Manipulasi Array: ArrayBaseOffset (), ArrayIndexScale ()
(5) terkait sinkronisasi. Terutama memberikan primitif sinkronisasi tingkat rendah (seperti primitif CAS berbasis CPU (perbandingan-dan-swap)): monitorenter (), trymonitorenter (), monitorexit (), compareangdswapint (), putorderedInt ()
(6) Terkait memori. Metode akses memori langsung (memotong tumpukan JVM dan memanipulasi memori lokal secara langsung): alocatememory (), copymemory (), freememory (), getAddress (), getInt (), putint ()
2. Mendapatkan contoh kelas yang tidak aman
Desain kelas yang tidak aman hanya disediakan untuk loader kelas startup tepercaya JVM, dan merupakan kelas pola singleton yang khas. Metode akuisisi instannya adalah sebagai berikut:
public static tidak aman getunsafe () {class cc = sun.reflect.reflection.getCallerClass (2); if (cc.getClassLoader ()! = null) lempar SecurityException baru ("tidak aman"); kembalikan theunsafe;}Loader kelas non-start akan secara langsung memanggil metode uncafe.getunsafe () dan akan melempar Exception Security (alasan spesifik melibatkan mekanisme pemuatan induk dari kelas JVM).
Ada dua solusi. Salah satunya adalah menentukan kelas yang akan digunakan sebagai kelas startup melalui parameter JVM - XBOOTClassPath. Metode lainnya adalah refleksi Java.
Bidang f = uncafe.class.getDeclaredfield ("theUnsafe"); f.setAccessible (true); uncafe tidak aman = (tidak aman) f.get (null);Dengan mengatur secara brutal dapat diakses ke true untuk instance singleton pribadi, dan kemudian secara langsung mendapatkan cast objek untuk tidak aman melalui metode GET Field. Dalam IDE, metode ini akan ditandai sebagai kesalahan dan dapat diselesaikan dengan pengaturan berikut:
Preferensi -> Java -> Compiler -> Kesalahan/Peringatan -> API yang sudah usang dan terbatas -> Referensi Terlarang -> Peringatan
3. Skenario aplikasi "menarik" dari kelas yang tidak aman
(1) Bypass Metode Inisialisasi Kelas. Metode allocateInstance () menjadi sangat berguna ketika Anda ingin mem -bypass konstruktor objek, pemeriksa keamanan, atau konstruktor tanpa publik.
kelas A {private long a; // tidak diinisialisasi nilai publik a () {this.a = 1; // inisialisasi} public long a () {return this.a; }}Berikut ini adalah perbandingan metode konstruksi, metode refleksi dan alokasiinstance ()
A o1 = a a () baru; // constructoro1.a (); // Cetakan 1 A O2 = A.Class.NewInstance (); // reflectiono2.a (); // mencetak 1 a o3 = (a) tidak aman.allocateInstance (a.class); // uncafeo3.a (); // Cetakan 0
AlocatInstance () sama sekali tidak memasukkan metode konstruktor, dan dalam mode singleton kita tampaknya melihat krisis.
(2) Modifikasi memori
Modifikasi memori relatif umum dalam bahasa C. Di Java, dapat digunakan untuk memotong pemeriksa keamanan.
Pertimbangkan aturan pemeriksaan akses sederhana berikut:
class guard {private int access_allowed = 1; public boolean giveAccess () {return 42 == access_allowed; }}Dalam keadaan normal, hadiah selalu kembali salah, tetapi itu tidak selalu terjadi
Penjaga penjaga = penjaga baru (); guard.giveAccess (); // false, tidak ada akses // bypassunsafe tidak aman = getunsafe (); field f = guard.getClass (). getDeclaredfield ("access_allowed"); uncafe.putint (guard, uncafe.objectfieldoffset (f), 42); // memori corruption guard.giveAccess (); // Benar, akses diberikanDengan menghitung offset memori dan menggunakan metode putinT (), akses_ yang diturunkan dari kelas dimodifikasi. Ketika struktur kelas diketahui, offset data selalu dapat dihitung (konsisten dengan perhitungan data offset di kelas dalam C ++).
(3) Menerapkan fungsi sizeof () mirip dengan bahasa C
Menerapkan fungsi sizeof () C-like dengan menggabungkan fungsi Refleksi Java dan ObjectFieldOffset ().
ukuran public static long (objek o) {tidak aman u = getunsafe (); Bidang hashset = hashset baru (); Kelas C = o.getClass (); while (c! = Object.class) {for (field f: c.getDecledFields ()) {if ((f.getModifiers () & Modifier.static) == 0) {fields.add (f); }} c = c.getSuperclass (); } // Dapatkan offset Long MaxSize = 0; untuk (bidang f: bidang) {long offset = U.ObjectFieldOffset (f); if (offset> maxSize) {maxSize = offset; }} return ((MaxSize/8) + 1) * 8; // padding}Gagasan algoritma sangat jelas: mulai dari subkelas yang mendasarinya, mengeluarkan domain non-statis dari dirinya sendiri dan semua superclass pada gilirannya, menempatkannya dalam hashset (perhitungan berulang hanya sekali, Java adalah warisan tunggal), dan kemudian menggunakan Objectfieldoffset () untuk mendapatkan offset maksimum, dan akhirnya mempertimbangkan ivignment.
Dalam JVM 32-bit, ukuran dapat diperoleh dengan membaca lama dengan file class offset 12.
ukuran public static long (objek objek) {return getunsafe (). getAddress (normalisasi (getunsafe (). getInt (objek, 4l)) + 12l);}Fungsi normalisasi () adalah metode yang dikonversi int yang ditandatangani menjadi tidak ditandatangani lama
private static long normize (value int) {if (value> = 0) nilai pengembalian; return (0l >>> 32) & value;}Ukuran dua sizeof () yang dihitung adalah sama. Implementasi sizeof () yang paling standar adalah dengan menggunakan java.lang.instrument, namun, ini memerlukan menentukan parameter baris perintah -javaagent.
(4) Menerapkan replikasi Java yang dangkal
Skema replikasi standar dangkal adalah untuk mengimplementasikan antarmuka yang dapat dikloning atau fungsi replikasi yang diimplementasikan dengan sendirinya, dan mereka bukan fungsi serba guna. Dengan menggabungkan metode sizeof (), penyalinan dangkal dapat dicapai.
objek statis dangkal (objek obj) {ukuran panjang = sizeof (obj); Long Start = ToAddress (OBJ); alamat panjang = getunsafe (). alocatememory (size); getunsafe (). Copymemory (Start, Alamat, Ukuran); kembalikan dariDdress (alamat);}ToAddress berikut () dan fromAddress () mengonversi objek ke alamatnya dan operasi terbalik masing -masing.
static long toaddress (objek obj) {objek [] array = objek baru [] {obj}; long basisofsset = getunsafe (). arraybaseoffset (objek []. kelas); return normalize (getUnsafe (). getInt (array, basiseoffset));} objek statis dariDdress (alamat panjang) {objek [] array = objek baru [] {null}; long basisofsset = getunsafe (). arraybaseoffset (objek []. kelas); getunsafe (). putlong (array, basis basis, alamat); return array [0];}Fungsi salinan dangkal di atas dapat diterapkan pada objek Java apa pun, dan ukurannya dihitung secara dinamis.
(5) menghilangkan kata sandi dalam memori
Bidang kata sandi disimpan dalam string, namun, daur ulang string dikelola oleh JVM. Cara teraman adalah menimpa bidang kata sandi setelah digunakan.
Field stringValue = string.class.getDecLaredField ("value"); stringValue.setAccessible (true); char [] mem = (char []) stringValue.get (kata sandi); untuk (int i = 0; i <mem.length; i ++) {memm [i] = '?(6) Pemuatan kelas dinamis
Metode standar kelas pemuatan dinamis adalah class.forname () (saat menulis program JDBC, saya ingat secara mendalam). Tidak aman juga dapat secara dinamis memuat file kelas java.
byte [] classcontents = getClassContent (); class c = getunsafe (). Defineclass (null, classcontents, 0, classcontents.length); C.GetMethod ("a"). Invoke (c.newinstance (), null); // 1GetClassContent () Metode membaca file kelas ke array byte. private static byte [] getClassContent () melempar Exception {file f = file baru ("/home/mishadoff/tmp/a.class"); FileInputStream input = new FileInputStream (f); byte [] content = byte baru [(int) f.length ()]; input.read (konten); input.close (); mengembalikan konten;}Ini dapat diterapkan dalam pemuatan dinamis, proksi, mengiris dan fungsi lainnya.
(7) Pengecualian deteksi paket adalah pengecualian runtime.
getunsafe (). ThrowException (ioException baru ());
Ini dapat dilakukan ketika Anda tidak ingin menangkap pengecualian yang diperiksa (tidak disarankan).
(8) Serialisasi cepat
Serializable Java standar sangat lambat, dan juga membatasi bahwa kelas harus memiliki konstruktor tanpa parameter publik. Eksternalisasi lebih baik, perlu menentukan skema agar kelas diserialkan. Perpustakaan serialisasi yang populer efisien, seperti Kryo, yang mengandalkan perpustakaan pihak ketiga, akan meningkatkan konsumsi memori. Anda dapat memperoleh nilai aktual dari domain di kelas melalui getInt (), getLong (), getObject () dan metode lainnya, dan bertahan informasi seperti nama kelas ke file bersama. Kryo telah berusaha menggunakan tidak aman, tetapi tidak ada data peningkatan kinerja yang spesifik. (http://code.google.com/p/kryo/issues/detail?id=75)
(9) mengalokasikan memori dalam tumpukan non-java
Baru menggunakan Java akan mengalokasikan memori untuk objek di tumpukan, dan siklus hidup objek akan dikelola oleh JVM GC.
kelas superarray {private final static int byte = 1; Ukuran panjang pribadi; alamat panjang pribadi; superarray publik (ukuran panjang) {this.size = size; alamat = getunsafe (). allocatememory (ukuran * byte); } public void set (long i, value byte) {getunsafe (). putbyte (alamat + i * byte, value); } public int get (long idx) {return getunsafe (). getByte (alamat + idx * byte); } Ukuran Panjang Publik () {ukuran pengembalian; }}Memori yang dialokasikan oleh tidak aman tidak dibatasi oleh integer.max_value, dan dialokasikan pada memori non-heap. Saat menggunakannya, Anda harus sangat berhati -hati: jika Anda lupa mendaur ulangnya secara manual, kebocoran memori akan terjadi; Jika Anda akses alamat ilegal, itu akan menyebabkan JVM macet. Ini dapat digunakan ketika Anda perlu mengalokasikan area kontinu yang besar, pemrograman waktu-nyata (tidak mentolerir latensi JVM). Java.nio menggunakan teknologi ini.
(10) Aplikasi dalam konkurensi Java
Dengan menggunakan Unsafe.ComeEndsWap (), dapat digunakan untuk mengimplementasikan struktur data bebas kunci yang efisien.
kelas cascounter mengimplementasikan penghitung {private volatile long counter = 0; pribadi tidak aman; offset panjang pribadi; cascounter publik () melempar Exception {unsafe = getunsafe (); offset = uncafe.objectFieldOffset (cascounter.class.getDeclaredfield ("counter")); } @Override public void increment () {jauh sebelum = counter; while (! unsafe.comppeeandswaplong (ini, offset, sebelum, sebelum + 1)) {sebelum = counter; }} @Override public long getCounter () {return counter; }}Melalui pengujian, struktur data di atas pada dasarnya sama dengan efisiensi variabel atom Java. Variabel atom Java juga menggunakan metode compareandswap () yang tidak aman, dan metode ini pada akhirnya akan sesuai dengan primitif CPU yang sesuai, sehingga sangat efisien. Berikut adalah solusi untuk mengimplementasikan hashmap bebas kunci (http://www.azulsystems.com/about_us/presentations/lock-free-hash. Gagasan solusi ini adalah: menganalisis setiap negara bagian, membuat salinan, memodifikasi salinan, menggunakan primitif CAS, pemintalan kunci). Di mesin server biasa (Core <32), menggunakan ConcurrenthashMap (sebelum JDK8, kunci pemisahan 16-channel default diimplementasikan, dan ConcurrenthashMap telah diimplementasikan menggunakan bebas kunci) jelas cukup.
Meringkaskan
Di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.