
Artikel ini akan membahas implementasi kompresi pointer di Java Virtual Machine 64-bit , yang dikendalikan oleh opsi UseCompressedOops dan diaktifkan secara default untuk sistem 64-bit yang dimulai dengan Java SE 6u23.
Deskripsi masalah
Dalam JVM 64-bit, pointer menempati ruang memori 2 kali lebih banyak (kejutan-kejutan) daripada 32-bit. Ini dapat meningkatkan ukuran data sebesar 1,5 kali dibandingkan dengan kode yang sama untuk arsitektur 32-bit. Pada saat yang sama, dalam arsitektur 32-bit, hanya 2 ^ 32 byte (4 GB) yang dapat diatasi, yang cukup kecil di dunia modern.
Mari kita menulis sebuah program kecil dan melihat berapa banyak byte yang menempati objek Integer:
import java.util.stream.IntStream; import java.util.stream.Stream; class HeapTest { public static void main(String ... args) throws Exception { Integer[] x = IntStream.range(0, 1_000_000).boxed().toArray(Integer[]::new); Thread.sleep(6000000); Stream.of(x).forEach(System.out::println); } }
Di sini kita menyoroti satu juta objek dari kelas Integer dan tertidur untuk waktu yang lama. Baris terakhir diperlukan agar kompiler tidak tiba-tiba mengabaikan pembuatan array (meskipun pada mesin saya, objek dibuat secara normal tanpa baris ini).
Kami mengkompilasi dan menjalankan program dengan kompresi pointer yang dinonaktifkan:
> javac HeapTest.java > java -XX:-UseCompressedOops HeapTest
Menggunakan utilitas jcmd , kami melihat alokasi memori:
> jps 45236 HeapTest ... > jcmd 45236 GC.class_histogram

Gambar menunjukkan bahwa jumlah total objek adalah 1000128 , dan ukuran memori yang ditempati benda-benda ini adalah 24003072 byte . Yaitu 24 byte per objek (mengapa tepatnya 24 akan ditulis di bawah).
Dan ini adalah memori dari program yang sama, tetapi dengan flag UseCompressedOops dihidupkan :

Sekarang setiap objek menempati 16 byte .
Keuntungan kompresi jelas =)
Solusi
Bagaimana JVM kompres pointer? Teknik ini disebut Ups Terkompresi . Oop singkatan dari penunjuk objek biasa atau penunjuk objek biasa .
Kuncinya adalah bahwa dalam sistem 64-bit, data dalam memori selaras dengan kata mesin, mis. Masing-masing 8 byte. Dan alamat selalu memiliki tiga bit nol di akhir.
Jika Anda menyimpan pointer dengan menggeser alamat dengan 3 bit ke kanan (operasi disebut encode ), dan sebelum menggunakan, menggeser oleh 3 bit ke kiri (masing-masing, decode ), maka Anda dapat memasukkan pointer 32-bit dengan ukuran 35 bit , mis. Alamat hingga 32 GB (2 ^ 35 byte).
Jika ukuran heap untuk program Anda lebih dari 32GB, maka kompresi berhenti bekerja dan semua pointer menjadi ukuran 8 byte.
Ketika opsi UseCompressedOops diaktifkan, tipe pointer berikut dikompresi:
- Bidang kelas untuk setiap objek
- Objek Lapangan Kelas
- Elemen array benda.
Objek JVM itu sendiri tidak pernah dikompresi. Dalam hal ini, kompresi terjadi pada tingkat mesin virtual, dan bukan bytecode.
Baca lebih lanjut tentang menempatkan objek dalam memori
Sekarang, mari kita gunakan utilitas jol (Layout Objek Java) untuk melihat lebih dekat pada berapa banyak memori yang dibutuhkan Integer kami dalam JVM yang berbeda:
> java -jar jol-cli-0.9-full.jar estimates java.lang.Integer ***** 32-bit VM: ********************************************************** java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 8 (object header) N/A 8 4 int Integer.value N/A 12 4 (loss due to the next object alignment) Instance size: 16 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ***** 64-bit VM: ********************************************************** java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 16 (object header) N/A 16 4 int Integer.value N/A 20 4 (loss due to the next object alignment) Instance size: 24 bytes Space losses: 0 bytes internal + 4 bytes external = 4 bytes total ***** 64-bit VM, compressed references enabled: *************************** java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Integer.value N/A Instance size: 16 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total ***** 64-bit VM, compressed references enabled, 16-byte align: ************ java.lang.Integer object internals: OFFSET SIZE TYPE DESCRIPTION VALUE 0 12 (object header) N/A 12 4 int Integer.value N/A Instance size: 16 bytes Space losses: 0 bytes internal + 0 bytes external = 0 bytes total
Perbedaan antara "VM 64-bit" dan "VM 64-bit, referensi terkompresi diaktifkan" adalah untuk mengurangi header objek sebesar 4 byte. Plus, dalam kasus tanpa kompresi, menjadi perlu untuk menambahkan 4 byte lebih banyak untuk menyelaraskan data dalam memori.
Apa header objek ini? Mengapa itu berkurang 4 byte?

Gambar menunjukkan header objek sebesar 12 byte, mis. dengan opsi UseCompressedOops diaktifkan. Header terdiri dari beberapa flag JVM internal, serta pointer ke kelas objek ini. Dapat dilihat bahwa pointer ke kelas membutuhkan 32 bit. Tanpa kompresi, itu akan menempati 64 bit dan ukuran header objek sudah 16 byte.
Omong-omong, Anda dapat melihat bahwa ada opsi lain untuk penyelarasan 16 byte. Dalam hal ini, Anda dapat menambah memori hingga 64 GB.
Cons Kompresi Pointer
Pointer yang dikompresi, tentu saja, memiliki minus yang jelas - biaya operasi encode dan decode setiap kali pointer diakses. Angka pastinya akan bervariasi berdasarkan aplikasi.
Sebagai contoh, berikut ini adalah grafik dari pause collector pause untuk pointer terkompresi dan non-terkompresi, diambil dari sini Java GC in Numbers - Compressed OOPs

Dapat dilihat bahwa dengan kompresi dihidupkan, GC berhenti lebih lama. Anda dapat membaca lebih lanjut tentang ini di artikel itu sendiri (artikel ini cukup tua - 2013).
Referensi:
Ups terkompresi di JVM Hotspot
Bagaimana JVM mengalokasikan objek
CompressedOops: Pengantar referensi terkompresi di Jawa
Trik di belakang Ups JVM terkompresi
Java HotSpot Peningkatan Kinerja Mesin Virtual