Mengapa sun.misc.Unsafe ada, dan bagaimana bisa digunakan di dunia nyata? [Tutup]

267

Saya menemukan paket sun.misc.Unsafe hari yang lalu dan kagum pada apa yang bisa dilakukan.

Tentu saja, kelas tidak berdokumen, tetapi saya bertanya-tanya apakah ada alasan yang bagus untuk menggunakannya. Skenario apa yang mungkin muncul di mana Anda perlu menggunakannya? Bagaimana itu bisa digunakan dalam skenario dunia nyata?

Lebih jauh, jika Anda benar- benar membutuhkannya, apakah itu tidak mengindikasikan bahwa ada sesuatu yang salah dengan desain Anda?

Mengapa Java bahkan memasukkan kelas ini?

pdeva
sumber
7
JDK devs saat ini sedang meninjau API ini untuk kemungkinan transformasi menjadi API publik di Java 9. Jika Anda menggunakannya, perlu waktu 5 menit untuk mengisi survei: surveymonkey.com/s/sun-misc-Unsafe .
Andy Lynch
2
Posting ini sedang dibahas di meta: meta.stackoverflow.com/questions/299139/…
Jon Clements

Jawaban:

159

contoh

  1. VM "intrinsifikasi." yaitu CAS (Compare-And-Swap) yang digunakan dalam Lock-Free Hash Tables misalnya: sun.misc.Unsafe.compareAndSwapInt dapat membuat panggilan JNI nyata ke dalam kode asli yang berisi instruksi khusus untuk CAS

    baca lebih lanjut tentang CAS di sini http://en.wikipedia.org/wiki/Compare-and-swap

  2. Fungsionalitas sun.misc.Unsafe dari host VM dapat digunakan untuk mengalokasikan objek yang tidak diinisialisasi dan kemudian menginterpretasikan permintaan konstruktor sebagai panggilan metode lainnya.

  3. Seseorang dapat melacak data dari alamat asli. Dimungkinkan untuk mengambil alamat memori objek menggunakan kelas java.lang.Unsafe, dan beroperasi pada bidangnya langsung melalui metode get / put yang tidak aman!

  4. Kompilasi optimasi waktu untuk JVM. VM kinerja tinggi yang menggunakan "sihir", membutuhkan operasi tingkat rendah. misalnya: http://en.wikipedia.org/wiki/Jikes_RVM

  5. Mengalokasikan memori, sun.misc.Unsafe.allocateMemory misalnya: - DirectByteBuffer constructor secara internal menyebutnya ketika ByteBuffer.allocateDirect dipanggil

  6. Menelusuri tumpukan panggilan dan memutar ulang dengan nilai yang digunakan oleh sun.misc.Unsafe, berguna untuk instrumentasi

  7. sun.misc.Unsafe.arrayBaseOffset dan arrayIndexScale dapat digunakan untuk mengembangkan arraylets, sebuah teknik untuk memecah array besar menjadi objek yang lebih kecil untuk membatasi biaya real-time pemindaian, memperbarui atau memindahkan operasi pada objek besar

  8. http://robaustin.wikidot.com/how-to-write-to-direct-memory-locations-in-java

lebih lanjut tentang referensi di sini - http://bytescrolls.blogspot.com/2011/04/tarik

zudokod
sumber
1
jika Anda mendapatkan alamat bidang menggunakan Tidak Aman, itu selalu dapat diubah oleh GC, jadi bukankah operasi itu tidak berguna?
pdeva
dapatkan alamat untuk yang sudah Anda alokasikan
zudokod
apa yang Anda maksud dengan yang saya alokasikan. ini tampaknya digunakan di tempat-tempat di mana objek dibuat menggunakan operator 'baru', jadi pertanyaan saya.
pdeva
1
unsafe.allocateMemory dan masukkan nilainya
zudokod
1
Mengenai poin 2, saya ingin tahu bagaimana Anda bisa memanggil konstruktor sebagai panggilan metode lain? Karena saya tidak menemukan cara untuk melakukan itu kecuali dalam bytecodes.
Miguel Gamboa
31

Hanya dari menjalankan pencarian di beberapa mesin pencari kode saya mendapatkan contoh-contoh berikut:

Kelas sederhana untuk mendapatkan akses ke objek {@link Tidak Aman}. {@link Tidak Aman} * diperlukan untuk memungkinkan operasi CAS efisien pada array. Perhatikan bahwa versi dalam {@link java.util.concurrent.atomic}, seperti {@link java.util.concurrent.atomic.AtomicLongArray}, memerlukan jaminan pemesanan memori tambahan yang umumnya tidak diperlukan dalam algoritma ini dan juga mahal pada sebagian besar prosesor.

  • SoyLatte - java 6 untuk kutipan osx javadoc

/ ** Kelas dasar untuk FieldAccessors berbasis sun.misc.Unsafe untuk bidang statis. Pengamatan adalah bahwa hanya ada sembilan jenis bidang dari sudut pandang kode refleksi: delapan jenis primitif dan Obyek. Menggunakan kelas Unsafe alih-alih bytecodes yang dihasilkan menghemat memori dan waktu pemuatan untuk FieldAccessor yang dihasilkan secara dinamis. * /

  • SpikeSource

/ * FinalFields yang dikirim melintasi kawat .. bagaimana cara menghapus dan menciptakan kembali objek di sisi penerima? Kami tidak ingin memanggil konstruktor karena akan menetapkan nilai untuk bidang terakhir. Kami harus membuat ulang bidang terakhir persis seperti di sisi pengirim. The sun.misc.Unsafe melakukan ini untuk kita. * /

Ada banyak contoh lain, cukup ikuti tautan di atas ...

Asaf
sumber
25

Menarik, saya bahkan belum pernah mendengar tentang kelas ini (yang mungkin merupakan hal yang baik, sungguh).

Satu hal yang terlintas dalam pikiran adalah menggunakan Unsafe # setMemory untuk memusatkan buffer yang berisi informasi sensitif pada satu titik (kata sandi, kunci, ...). Anda bahkan dapat melakukan ini pada bidang objek "yang tidak dapat diubah" (sekali lagi saya kira pantulan lama yang polos dapat melakukan trik di sini juga). Saya bukan ahli keamanan jadi bawa ini dengan sebutir garam.

Mike Daniels
sumber
4
I'd never even heard of this class... Aku sudah berkali-kali memberitahumu tentang itu! sigh + :(
Tim Bender
7
Tidak akan ada gunanya, karena Java menggunakan pengumpul sampah generasi yang menyalin dan informasi sensitif Anda mungkin sudah berada di tempat lain dalam memori 'bebas' yang menunggu untuk ditimpa.
Daniel Cassidy
39
Tidak pernah mendengarnya juga, tapi saya suka park()dokumentasi mereka : "Blokir utas saat ini, kembali ketika balancing unpark terjadi, atau balancing unpark telah terjadi, atau utas terputus, atau, jika tidak absolut dan waktu tidak nol, waktu nanodetik telah berlalu, atau jika absolut, batas waktu yang diberikan dalam milidetik sejak Epoch telah berlalu, atau palsu (yaitu, kembali tanpa 'alasan') ". Hampir sebagus "memori dibebaskan ketika program keluar, atau, pada interval acak, mana yang lebih dulu".
Agustus
1
@Aniel, menarik, saya tidak mempertimbangkan itu. Sekarang Anda bisa melihat mengapa saya bukan ahli keamanan. :)
Mike Daniels
22

Berdasarkan analisis yang sangat singkat dari pustaka Java 1.6.12 menggunakan eclipse untuk pelacakan referensi, tampaknya seolah-olah setiap fungsi yang berguna Unsafeterpapar dengan cara yang bermanfaat.

Operasi CAS terpapar melalui kelas Atom *. Fungsi manipulasi memori diekspos melalui instruksi DirectByteBuffer Sync (park, unpark) diekspos melalui AbstractQueuedSynchronizer yang pada gilirannya digunakan oleh implementasi Lock.

Tim Bender
sumber
AtomicXXXUpdaters terlalu lambat dan ketika Anda benar-benar membutuhkannya: CAS - Anda tidak dapat menggunakannya sebenarnya. Jika Anda akan melakukan logam Anda tidak akan menggunakan level abstraksi dan banyak cek. Gagal CAS adalah buruk dalam satu loop esp. ketika perangkat keras memutuskan untuk salah menduga cabang (karena pertengkaran tinggi) tetapi memiliki beberapa perbandingan / cabang hanya menyakitkan. Park / Unpark terekspos melalui LockSupportbukan AQS (yang terakhir lebih merupakan alat kunci dari pada park / unpark)
bestsss
21

Unsafe.throwException - memungkinkan untuk melempar pengecualian yang diperiksa tanpa mendeklarasikannya.

Ini berguna dalam beberapa kasus di mana Anda berurusan dengan refleksi atau AOP.

Asumsikan Anda Membangun proxy generik untuk Antarmuka yang ditentukan pengguna. Dan pengguna dapat menentukan pengecualian mana yang dilemparkan oleh implementasi dalam kasus khusus hanya dengan menyatakan pengecualian di antarmuka. Maka ini adalah satu-satunya cara saya tahu, untuk meningkatkan pengecualian diperiksa dalam Implementasi Antarmuka Dinamis.

import org.junit.Test;
/** need to allow forbidden references! */ import sun.misc.Unsafe;

/**
 * Demonstrate how to throw an undeclared checked exception.
 * This is a hack, because it uses the forbidden Class {@link sun.misc.Unsafe}.
 */
public class ExceptionTest {

    /**
     * A checked exception.
     */
    public static class MyException extends Exception {
        private static final long serialVersionUID = 5960664994726581924L;
    }

    /**
     * Throw the Exception.
     */
    @SuppressWarnings("restriction")
    public static void throwUndeclared() {
        getUnsafe().throwException(new MyException());
    }

    /**
     * Return an instance of {@link sun.misc.Unsafe}.
     * @return THE instance
     */
    @SuppressWarnings("restriction")
    private static Unsafe getUnsafe() {
        try {

            Field singleoneInstanceField = Unsafe.class.getDeclaredField("theUnsafe");
            singleoneInstanceField.setAccessible(true);
            return (Unsafe) singleoneInstanceField.get(null);

        } catch (IllegalArgumentException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (SecurityException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (NoSuchFieldException e) {
            throw createExceptionForObtainingUnsafe(e);
        } catch (IllegalAccessException e) {
            throw createExceptionForObtainingUnsafe(e);
        }
    }

    private static RuntimeException createExceptionForObtainingUnsafe(final Throwable cause) {
        return new RuntimeException("error while obtaining sun.misc.Unsafe", cause);
    }


    /**
     * scenario: test that an CheckedException {@link MyException} can be thrown
     * from an method that not declare it.
     */
    @Test(expected = MyException.class)
    public void testUnsingUnsaveToThrowCheckedException() {
        throwUndeclared();
    }
}
Muntah
sumber
14
Anda dapat melakukan hal yang sama Thread.stop(Throwable)tanpa perlu tidak aman, di utas yang sama Anda dapat membuang apa saja (tidak ada pemeriksaan kompilasi)
bestsss
Anda dapat melakukan ini murni melalui bytecode (Atau gunakan Lomboc untuk melakukannya untuk Anda)
Antimony
1
@bestsss Metode itu telah dihapus dan melempar UnsupportedOperationExceptiondi utas saat ini pada Java 8. Namun, versi tanpa argumen yang melempar ThreadDeathmasih berfungsi.
gparyani
@damryfbfnetsi, saya belum mengikuti diskusi inti jdk untuk beberapa waktu dan tidak ada rencana untuk pindah ke java 8. Namun, ini ide yang cukup membingungkan karena sepele untuk dilaksanakan oleh bytecode generation, kecuali sekarang verifier benar-benar memeriksa apakah mereka Metode menyatakan throwables ... tapi itu mungkin mundur tidak kompatibel karena metadata tentang pengecualian yang dilemparkan bebas untuk dibuang.
bestsss
10

Kelas Tidak Aman

Kumpulan metode untuk melakukan operasi tingkat rendah dan tidak aman. Meskipun kelas dan semua metode bersifat publik, penggunaan kelas ini terbatas karena hanya kode tepercaya yang dapat memperoleh turunannya.

Salah satu penggunaannya adalah di java.util.concurrent.atomickelas:

Margus
sumber
6

Untuk penyalinan memori yang efisien (lebih cepat untuk menyalin daripada System.arraycopy () setidaknya untuk blok pendek); seperti yang digunakan oleh Java LZF dan Snappy codec. Mereka menggunakan 'getLong' dan 'putLong', yang lebih cepat daripada melakukan salinan byte-by-byte; sangat efisien saat menyalin hal-hal seperti blok 16/32/64 byte.

StaxMan
sumber
1
Doh, arraycopy menggunakan loop SSE pada x86-64 yang lebih baik daripada getLong/putLong(dan Anda harus menghitung alamatnya juga)
bestsss
Sudahkah Anda mengukur ini? Untuk blok yang lebih pendek saya melihat secara konsisten kinerja yang lebih baik pada x86-64 saat menggunakan kombinasi getLong/ putLong: idealnya saya lebih suka System.arraycopy()untuk kesederhanaan dan semuanya; tetapi pengujian aktual menunjukkan sebaliknya untuk kasus yang telah saya uji.
StaxMan
ya menggunakan tidak aman, saya tidak bisa melakukan kinerja yang berarti dari mengempiskan impl. Untuk beberapa byte, salinan panjang pada array besar, get / putLong mungkin memang berfungsi ketika kompiler harus memeriksa panjangnya. Beberapa impl. tambahkan pagar memori melewati System.arrayCopy (dapat dinonaktifkan / diaktifkan meskipun), sehingga bisa menjadi penyebab sebenarnya.
bestsss
Baik. Ada kemungkinan bahwa JDK baru telah mengubah ini; awalnya ketika saya mengamati operasi lebih cepat (dengan JDK 1.6) saya terkejut juga. Atau mungkin saya lupa beberapa perbedaan spesifik dalam penggunaan. Ini adalah optimasi yang rumit (dan mungkin tidak stabil), bahkan ketika mereka berhasil, dan penting untuk mengukur efeknya.
StaxMan
5

Saya baru-baru ini sedang mengerjakan mengimplementasikan JVM dan menemukan bahwa sejumlah kelas yang mengejutkan diimplementasikan dalam hal Unsafe. Kelas ini sebagian besar dirancang untuk pelaksana perpustakaan Java dan berisi fitur yang pada dasarnya tidak aman tetapi diperlukan untuk membangun primitif cepat. Misalnya, ada metode untuk mendapatkan dan menulis offset bidang mentah, menggunakan sinkronisasi tingkat perangkat keras, mengalokasikan dan membebaskan memori, dll. Ini tidak dimaksudkan untuk digunakan oleh programmer Java biasa; itu tidak berdokumen, khusus implementasi, dan secara inheren tidak aman (karenanya namanya!). Selain itu, saya pikir bahwa SecurityManagerakan melarang akses ke sana di hampir semua kasus.

Singkatnya, ini terutama ada untuk memungkinkan pelaksana perpustakaan akses ke mesin yang mendasarinya tanpa harus mendeklarasikan setiap metode di kelas tertentu seperti AtomicIntegerasli. Anda tidak perlu menggunakan atau mengkhawatirkannya dalam pemrograman Java rutin, karena intinya adalah membuat seluruh perpustakaan cukup cepat sehingga Anda tidak memerlukan akses semacam itu.

templatetypedef
sumber
sebenarnya, SecurityManager melarang akses ke sana hanya jika refleksi dinonaktifkan
amara
@ sparkleshy- Bisakah Anda menguraikan ini?
templatetypedef
saat mendapatkan instance dari getUnsafe memang memiliki persyaratan yang agak ketat, Unsafe.class.getDeclaredField("theUnsafe")dengan .setAccessible(true)dan kemudian .get(null)akan mendapatkannya juga
amara
@ sparkleshy- Saya terkejut bahwa bekerja - manajer keamanan harus menandai itu
templatetypedef
5

Gunakan untuk mengakses dan mengalokasikan sejumlah besar memori secara efisien, seperti di mesin voxel Anda sendiri! (Yaitu permainan bergaya Minecraft.)

Dalam pengalaman saya, JVM sering tidak dapat menghilangkan batas memeriksa di tempat Anda benar-benar membutuhkannya. Misalnya, jika Anda mengulangi array yang besar, tetapi akses memori yang sebenarnya tersimpan di bawah panggilan metode non-virtual * dalam loop, JVM mungkin masih melakukan pemeriksaan batas dengan setiap akses array, daripada hanya sekali sebelum putaran. Dengan demikian, untuk keuntungan kinerja yang berpotensi besar, Anda dapat menghilangkan pemeriksaan batas JVM di dalam loop melalui metode yang menggunakan sun.misc. Tidak aman untuk mengakses memori secara langsung, memastikan untuk melakukan batasan-memeriksa diri Anda di tempat yang benar. (Anda yang akan batas-batas memeriksa pada tingkat tertentu, kan?)
* oleh non-virtual, maksud saya JVM tidak harus secara dinamis menyelesaikan apa pun metode khusus Anda, karena Anda telah dengan benar menjamin bahwa kelas / metode / instance adalah beberapa kombinasi dari static / final / what-have-you.

Untuk mesin voxel rumahan saya, ini menghasilkan peningkatan kinerja yang dramatis selama generasi chunk dan serialisasi (sekarang tempat di mana saya membaca / menulis ke seluruh array sekaligus). Hasil mungkin bervariasi, tetapi jika kurangnya batasan-eliminasi adalah masalah Anda, maka ini akan memperbaikinya.

Ada beberapa masalah yang berpotensi besar dengan ini: khususnya, ketika Anda memberikan kemampuan untuk mengakses memori tanpa batas memeriksa ke klien dari antarmuka Anda, mereka mungkin akan menyalahgunakannya. (Jangan lupa bahwa peretas juga bisa menjadi klien dari antarmuka Anda ... terutama dalam kasus mesin voxel yang ditulis dalam Java.) Jadi, Anda harus mendesain antarmuka Anda sedemikian rupa sehingga akses memori tidak dapat disalahgunakan, atau Anda harus sangat berhati-hati untuk memvalidasi user-data sebelum itu pernah dapat, pernah berbaur dengan antarmuka yang berbahaya Anda. Mempertimbangkan hal-hal bencana yang dapat dilakukan oleh seorang hacker dengan akses memori yang tidak diperiksa, mungkin lebih baik mengambil kedua pendekatan tersebut.

Philip Guin
sumber
4

Koleksi di luar tumpukan mungkin berguna untuk mengalokasikan sejumlah besar memori dan membatalkan alokasi segera setelah digunakan tanpa gangguan GC. Saya menulis perpustakaan untuk bekerja dengan susunan / daftar off-heap berdasarkan sun.misc.Unsafe.

alexkasko
sumber
4

Kami telah mengimplementasikan koleksi besar seperti Array, HashMaps, TreeMaps menggunakan Tidak Aman.
Dan untuk menghindari / meminimalkan fragmentasi, kami menerapkan pengalokasi memori menggunakan konsep dlmalloc lebih tidak aman.
Ini membantu kami untuk mendapatkan kinerja dalam konkurensi.

pradipmw
sumber
3

Unsafe.park()dan Unsafe.unpark()untuk konstruksi struktur kontrol konkurensi khusus dan mekanisme penjadwalan kerja sama.

andersoj
sumber
24
tersedia untuk umumjava.util.concurrent.locks.LockSupport
bestsss
1

Belum pernah menggunakannya sendiri, tapi saya kira jika Anda memiliki variabel yang hanya sesekali dibaca oleh lebih dari satu utas (sehingga Anda tidak benar-benar ingin membuatnya mudah berubah) Anda dapat menggunakannya putObjectVolatilesaat menulisnya di utas utama dan readObjectVolatileketika melakukan membaca langka dari utas lainnya.

Matt Crinklaw-Vogt
sumber
1
tetapi menurut diskusi pada utas di bawah ini, volatile yang tidak
terkontrak
Anda tidak dapat mengganti semantik yang mudah menguap dengan tulisan biasa dan bacaan yang mudah menguap ... ini adalah resep untuk bencana karena dapat bekerja di satu pengaturan tetapi tidak di yang lain. Jika Anda ingin memiliki semantik volatile dengan utas penulis tunggal, Anda dapat menggunakan AtomicReference.lazySet pada utas penulisan dan mendapatkan () pada pembaca (lihat posting ini untuk diskusi tentang topik). Bacaan yang mudah menguap relatif murah, tetapi tidak gratis, lihat di sini .
Nitsan Wakart
"... kamu bisa menggunakan putObjectVolatile saat menulisnya ..." Aku tidak menyarankan tulisan biasa.
Matt Crinklaw-Vogt
1

Anda memerlukannya jika Anda perlu mengganti fungsionalitas yang disediakan oleh salah satu kelas yang menggunakannya saat ini.

Ini bisa berupa kustomisasi / deserialisasi yang lebih cepat / lebih ringkas, versi lebih cepat / lebih besar dari buffer / resizable ByteBuffer, atau menambahkan variabel atom misalnya yang tidak didukung saat ini.

Saya telah menggunakannya untuk semua ini pada suatu waktu.

Peter Lawrey
sumber
0

Objek tampaknya menjadi ketersediaan untuk bekerja di tingkat yang lebih rendah dari apa yang biasanya diizinkan oleh kode Java. Jika Anda sedang mengkode aplikasi tingkat tinggi maka JVM akan mengabstraksi penanganan memori dan operasi lainnya dari tingkat kode sehingga lebih mudah diprogram. Dengan menggunakan pustaka Tidak Aman, Anda secara efektif menyelesaikan operasi tingkat rendah yang biasanya dilakukan untuk Anda.

Seperti woliveirajr menyatakan "random ()" menggunakan Unsafe untuk seed seperti halnya banyak operasi lain akan menggunakan fungsi alokasiateMemory () yang termasuk dalam Unsafe.

Sebagai seorang programmer Anda mungkin bisa lolos dengan tidak perlu perpustakaan ini tetapi memiliki kontrol ketat atas elemen-elemen tingkat rendah memang berguna (itu sebabnya masih ada Majelis dan (pada tingkat lebih rendah) kode C melayang-layang di dalam produk-produk utama)

Grambot
sumber