Sizeof untuk Java

26 Disember 2003

Q: Adakah Java mempunyai operator seperti sizeof () di C?

A: Satu jawapan yang dangkal bahawa Java tidak memberikan apa-apa seperti itu C sizeof(). Namun, mari kita pertimbangkan mengapa pengaturcara Java kadang-kadang menginginkannya.

Pengaturcara AC menguruskan kebanyakan peruntukan memori infrastruktur sendiri, dan sizeof()sangat diperlukan untuk mengetahui saiz blok memori yang akan diperuntukkan. Selain itu, pengedar memori C seperti malloc()tidak melakukan apa-apa mengenai inisialisasi objek: pengaturcara mesti menetapkan semua bidang objek yang menjadi penunjuk kepada objek yang lebih jauh. Tetapi apabila semua dikatakan dan dikodkan, peruntukan memori C / C ++ cukup efisien.

Sebagai perbandingan, peruntukan dan pembinaan objek Java diikat bersama (mustahil untuk menggunakan contoh objek yang diperuntukkan tetapi belum dimulakan). Sekiranya kelas Java menentukan bidang yang menjadi rujukan ke objek yang lebih jauh, biasanya juga menetapkannya pada waktu pembinaan. Oleh itu, memperuntukkan objek Java sering memperuntukkan banyak contoh objek yang saling berkaitan: grafik objek. Ditambah dengan pengumpulan sampah automatik, ini terlalu mudah dan dapat membuat anda merasa tidak perlu bimbang tentang perincian memori Java.

Sudah tentu, ini hanya berfungsi untuk aplikasi Java yang mudah. Berbanding dengan C / C ++, infrastruktur data Java yang setara cenderung menempati lebih banyak memori fizikal. Dalam pengembangan perisian perusahaan, mendekati memori maya maksimum yang tersedia pada JVM 32-bit hari ini adalah kekangan skalabiliti yang biasa. Oleh itu, pengaturcara Java dapat memperoleh keuntungan sizeof()atau sesuatu yang serupa untuk mengawasi apakah infrastruktur datanya terlalu besar atau berisi masalah memori. Nasib baik, pantulan Java membolehkan anda menulis alat sedemikian dengan mudah.

Sebelum meneruskan, saya akan membuang beberapa jawapan yang kerap tetapi tidak betul untuk soalan artikel ini.

Kekeliruan: Sizeof () tidak diperlukan kerana ukuran jenis asas Java tetap

Ya, Java intadalah 32 bit di semua JVM dan di semua platform, tetapi ini hanya syarat spesifikasi bahasa untuk lebar yang dapat dilihat oleh programmer dari jenis data ini. Seperti intitu pada dasarnya adalah jenis data abstrak dan dapat disokong oleh, katakanlah, kata memori fizikal 64-bit pada mesin 64-bit. Perkara yang sama berlaku untuk jenis nonprimitive: spesifikasi bahasa Java tidak mengatakan apa-apa tentang bagaimana medan kelas harus diselaraskan dalam memori fizikal atau bahawa array booleans tidak dapat dilaksanakan sebagai bitvector padat di dalam JVM.

Kekeliruan: Anda dapat mengukur ukuran objek dengan menyusunnya menjadi aliran bait dan melihat panjang aliran yang dihasilkan

Sebab ini tidak berfungsi adalah kerana susunatur bersiri hanyalah gambaran jauh dari susun atur memori yang sebenarnya. Salah satu cara mudah untuk melihatnya adalah dengan melihat bagaimana cara Stringbersiri: dalam memori setiap charsekurang-kurangnya 2 bait, tetapi dalam bentuk bersiri Strings dikodkan UTF-8 dan kandungan ASCII mana pun memerlukan separuh ruang.

Pendekatan kerja lain

Anda mungkin ingat "Petua Java 130: Adakah Anda Tahu Ukuran Data Anda?" yang menerangkan teknik berdasarkan membuat sebilangan besar contoh kelas yang sama dan mengukur dengan teliti peningkatan yang dihasilkan dalam ukuran timbunan JVM yang digunakan. Sekiranya berlaku, idea ini berfungsi dengan baik, dan saya sebenarnya akan menggunakannya untuk memacu pendekatan alternatif dalam artikel ini.

Perhatikan bahawa kelas Java Tip 130 Sizeofmemerlukan JVM yang tenang (supaya aktiviti timbunan hanya disebabkan oleh peruntukan objek dan pengumpulan sampah yang diminta oleh benang pengukur) dan memerlukan sebilangan besar kejadian objek yang serupa. Ini tidak berfungsi apabila anda ingin mengukur satu objek besar (mungkin sebagai sebahagian daripada output jejak debug) dan terutama ketika anda ingin memeriksa apa yang sebenarnya membuatnya begitu besar.

Berapakah ukuran objek?

Perbincangan di atas menyoroti titik falsafah: memandangkan anda biasanya berurusan dengan grafik objek, apakah definisi ukuran objek? Adakah hanya ukuran contoh objek yang anda periksa atau ukuran keseluruhan grafik data yang berakar pada contoh objek? Yang terakhir adalah perkara yang biasanya lebih penting dalam praktik. Seperti yang anda akan lihat, perkara-perkara tidak selalu begitu jelas, tetapi untuk permulaan anda boleh mengikuti pendekatan ini:

  • Contoh objek boleh (kira-kira) berukuran dengan menjumlahkan semua bidang data tidak statiknya (termasuk bidang yang ditentukan dalam kacamata super)
  • Tidak seperti, katakanlah, C ++, kaedah kelas dan kebajikannya tidak mempengaruhi ukuran objek
  • Antarmuka kelas tidak mempunyai kesan pada ukuran objek (lihat nota di akhir senarai ini)
  • Ukuran objek penuh dapat diperoleh sebagai penutup pada keseluruhan grafik objek yang berakar pada objek permulaan
Catatan: Melaksanakan sebarang antara muka Java hanya menandakan kelas yang dimaksudkan dan tidak menambahkan data pada definisinya. Sebenarnya, JVM bahkan tidak mengesahkan bahawa pelaksanaan antara muka menyediakan semua kaedah yang diperlukan oleh antara muka: ini adalah tanggungjawab penyusun secara ketat dalam spesifikasi semasa.

Untuk melakukan proses bootstrap, untuk jenis data primitif saya menggunakan ukuran fizikal seperti yang diukur oleh kelas Java Tip 130's Sizeof. Ternyata, untuk JVM 32-bit biasa, dataran biasa java.lang.Objectmengambil 8 bait, dan jenis data asas biasanya berukuran fizikal yang paling sedikit yang dapat menampung keperluan bahasa (kecuali booleanmengambil keseluruhan bait):

// java.lang.Saiz shell objek dalam bait: int akhir statik awam OBJECT_SHELL_SIZE = 8; final statik awam int OBJREF_SIZE = 4; final statik awam LONG_FIELD_SIZE = 8; int akhir statik awam INT_FIELD_SIZE = 4; int akhir statik awam SHORT_FIELD_SIZE = 2; akhir statik awam CHAR_FIELD_SIZE = 2; akhir statik awam BYTE_FIELD_SIZE = 1; akhir statik awam BOOLEAN_FIELD_SIZE = 1; akhir statik awam DOUBLE_FIELD_SIZE = 8; final statik awam FLOAT_FIELD_SIZE = 4;

(Penting untuk menyedari bahawa pemalar ini tidak dikodkan selamanya dan mesti diukur secara bebas untuk JVM tertentu.) Sudah tentu, jumlah ukuran medan objek yang naif mengabaikan masalah penjajaran memori di JVM. Penjajaran memori tidak penting (seperti yang ditunjukkan, misalnya, untuk jenis array primitif di Tip Java 130), tetapi saya fikir tidak menguntungkan untuk mengejar perincian tahap rendah seperti itu. Maklumat tersebut tidak hanya bergantung pada vendor JVM, tetapi tidak berada di bawah kawalan pengaturcara. Objektif kami adalah untuk meneka ukuran objek dengan baik dan mudah-mudahan mendapat petunjuk apabila medan kelas mungkin berlebihan; atau ketika ladang harus dihuni dengan malas; atau apabila infrastruktur bersarang yang lebih padat diperlukan, dan sebagainya. Untuk ketepatan fizikal yang mutlak, anda selalu boleh kembali ke Sizeofkelas di Java Tip 130.

Untuk membantu profil apa yang membentuk contoh objek, alat kami tidak hanya akan menghitung ukurannya tetapi juga akan membangun infrastruktur data yang berguna sebagai produk sampingan: grafik yang terdiri daripada IObjectProfileNode:

antara muka IObjectProfileNode {Objek objek (); Nama rentetan (); saiz int (); int refcount (); Ibu bapa IObjectProfileNode (); IObjectProfileNode [] kanak-kanak (); Shell IObjectProfileNode (); Laluan IObjectProfileNode [] (); Akar IObjectProfileNode (); panjang jalur int (); boolean traverse (penapis INodeFilter, pelawat INodeVisitor); Rentetan tali (); } // Akhir antara muka

IObjectProfileNodes saling berkaitan dengan cara yang hampir sama dengan grafik objek asal, dengan IObjectProfileNode.object()mengembalikan objek sebenar yang ditunjukkan oleh setiap nod. IObjectProfileNode.size()mengembalikan jumlah ukuran (dalam bait) subtree objek yang berakar pada contoh objek nod itu. Sekiranya contoh objek menghubungkan ke objek lain melalui medan contoh yang tidak kosong atau melalui rujukan yang terdapat di dalam medan array, maka IObjectProfileNode.children()akan menjadi senarai node grafik turunan yang sesuai, diurutkan dalam urutan ukuran yang semakin berkurang. Sebaliknya, untuk setiap simpul selain yang memulakan, IObjectProfileNode.parent()mengembalikan induknya. Oleh itu, keseluruhan koleksi IObjectProfileNodememisahkan objek asal dan menunjukkan bagaimana penyimpanan data dibahagi di dalamnya. Selanjutnya, nama nod grafik berasal dari medan kelas dan memeriksa jalan nod dalam grafik (IObjectProfileNode.path()) membolehkan anda mengesan pautan pemilikan dari contoh objek asal ke sebarang data dalaman.

Anda mungkin telah memperhatikan ketika membaca perenggan sebelumnya bahawa idea sejauh ini masih mempunyai kekaburan. Sekiranya, semasa melintasi grafik objek, anda menemui contoh objek yang sama lebih dari sekali (iaitu, lebih daripada satu medan di suatu tempat di dalam grafik menunjukkannya), bagaimana anda menetapkan pemilikannya (penunjuk induk)? Pertimbangkan coretan kod ini:

 Objek obj = String baru [] {String baru ("JavaWorld"), String baru ("JavaWorld")}; 

Each java.lang.String instance has an internal field of type char[] that is the actual string content. The way the String copy constructor works in Java 2 Platform, Standard Edition (J2SE) 1.4, both String instances inside the above array will share the same char[] array containing the {'J', 'a', 'v', 'a', 'W', 'o', 'r', 'l', 'd'} character sequence. Both strings own this array equally, so what should you do in cases like this?

If I always want to assign a single parent to a graph node, then this problem has no universally perfect answer. However, in practice, many such object instances could be traced back to a single "natural" parent. Such a natural sequence of links is usually shorter than the other, more circuitous routes. Think about data pointed to by instance fields as belonging more to that instance than to anything else. Think about entries in an array as belonging more to that array itself. Thus, if an internal object instance can be reached via several paths, we choose the shortest path. If we have several paths of equal lengths, well, we just pick the first discovered one. In the worst case, this is as good a generic strategy as any.

Berpikir tentang lintasan grafik dan jalan terpendek harus membunyikan lonceng pada tahap ini: carian luas pertama adalah algoritma melintasi grafik yang menjamin untuk mencari jalan terpendek dari simpul permulaan ke nod nod lain yang dapat dicapai.

Setelah semua pendahuluan ini, berikut adalah pelaksanaan buku teks grafik melintang. (Beberapa perincian dan kaedah tambahan telah dihilangkan; lihat muat turun artikel ini untuk maklumat lengkap.):