Saya mencoba memahami proses aktual di balik kreasi objek di Jawa - dan saya kira bahasa pemrograman lain.
Apakah salah untuk menganggap bahwa inisialisasi objek di Jawa sama dengan ketika Anda menggunakan malloc untuk struktur di C?
Contoh:
Foo f = new Foo(10);
typedef struct foo Foo;
Foo *f = malloc(sizeof(Foo));
Apakah ini sebabnya objek dikatakan berada di heap daripada stack? Karena mereka pada dasarnya hanya petunjuk data?
scalar-replacement
) menjadi bidang-bidang yang hanya tinggal di stack saja; tapi itu adalah sesuatu yangJIT
tidak, tidakjavac
.Jawaban:
Di C,
malloc()
mengalokasikan wilayah memori di heap dan mengembalikan pointer ke sana. Hanya itu yang Anda dapatkan. Memori tidak diinisialisasi dan Anda tidak memiliki jaminan bahwa semuanya nol atau apa pun.Di Jawa, panggilan
new
tidak seperti alokasi heapmalloc()
, tetapi Anda juga mendapatkan banyak kenyamanan tambahan (atau overhead, jika Anda mau). Misalnya, Anda tidak harus secara eksplisit menentukan jumlah byte yang akan dialokasikan. Compiler menghitungnya untuk Anda berdasarkan pada jenis objek yang Anda coba alokasikan. Selain itu, konstruktor objek dipanggil (yang dapat Anda berikan argumen jika Anda ingin mengontrol bagaimana inisialisasi terjadi). Ketikanew
kembali, Anda dijamin memiliki objek yang diinisialisasi.Tapi ya, pada akhir panggilan baik hasil
malloc()
dannew
hanya petunjuk ke beberapa data berbasis heap.Bagian kedua dari pertanyaan Anda menanyakan tentang perbedaan antara tumpukan dan tumpukan. Jawaban yang jauh lebih komprehensif dapat ditemukan dengan mengikuti kursus tentang (atau membaca buku tentang) desain kompiler. Kursus tentang sistem operasi juga akan sangat membantu. Ada juga banyak pertanyaan dan jawaban di SO tentang tumpukan dan tumpukan.
Karena itu, saya akan memberikan gambaran umum saya harap tidak terlalu bertele-tele dan bertujuan untuk menjelaskan perbedaan pada tingkat yang cukup tinggi.
Pada dasarnya, alasan utama untuk memiliki dua sistem manajemen memori, yaitu tumpukan dan tumpukan, adalah untuk efisiensi . Alasan kedua adalah bahwa masing-masing lebih baik pada jenis masalah tertentu daripada yang lain.
Tumpukan agak lebih mudah bagi saya untuk dipahami sebagai konsep, jadi saya mulai dengan tumpukan. Mari kita pertimbangkan fungsi ini di C ...
Di atas tampaknya cukup mudah. Kami mendefinisikan fungsi bernama
add()
dan meneruskan di addend kiri dan kanan. Fungsi menambahkannya dan mengembalikan hasilnya. Harap abaikan semua hal tepi-kasus seperti luapan yang mungkin terjadi, pada titik ini tidak berhubungan dengan diskusi.Tujuan
add()
fungsi ini tampaknya cukup mudah, tetapi apa yang bisa kita katakan tentang siklus hidupnya? Terutama kebutuhan pemanfaatan memorinya?Yang paling penting, kompiler mengetahui apriori (yaitu pada waktu kompilasi) seberapa besar tipe data dan berapa banyak yang akan digunakan. The
lhs
danrhs
argumen yangsizeof(int)
, 4 byte masing-masing. Variabelnyaresult
jugasizeof(int)
. Kompiler dapat mengetahui bahwaadd()
fungsi tersebut menggunakan4 bytes * 3 ints
atau total 12 byte memori.Ketika
add()
fungsi dipanggil, register perangkat keras yang disebut penunjuk tumpukan akan memiliki alamat di dalamnya yang menunjuk ke bagian atas tumpukan. Untuk mengalokasikan memori yangadd()
harus dijalankan fungsi, semua kode fungsi-entri perlu lakukan adalah mengeluarkan satu instruksi bahasa rakitan tunggal untuk mengurangi nilai register penunjuk tumpukan dengan 12. Dengan demikian, ia menciptakan penyimpanan pada tumpukan selama tigaints
, masing-masing untuklhs
,rhs
, danresult
. Mendapatkan ruang memori yang Anda butuhkan dengan mengeksekusi instruksi tunggal adalah kemenangan besar dalam hal kecepatan karena instruksi tunggal cenderung dieksekusi dalam satu clock tick (1 milyar detik, 1 CPU 1 GHz).Selain itu, dari tampilan kompiler, ia dapat membuat peta ke variabel yang terlihat sangat buruk seperti mengindeks array:
Sekali lagi, semua ini sangat cepat.
Ketika
add()
fungsi keluar harus dibersihkan. Ini dilakukan dengan mengurangi 12 byte dari register penunjuk tumpukan. Ini mirip dengan panggilan untukfree()
tetapi hanya menggunakan satu instruksi CPU dan hanya membutuhkan satu centang. Ini sangat, sangat cepat.Sekarang pertimbangkan alokasi berbasis heap. Ini berlaku ketika kita tidak tahu apriori berapa banyak memori yang akan kita butuhkan (yaitu kita hanya akan mempelajarinya saat runtime).
Pertimbangkan fungsi ini:
Perhatikan bahwa
addRandom()
fungsi tidak tahu pada waktu kompilasi berapa nilaicount
argumen itu. Karena itu, tidak masuk akal untuk mencoba mendefinisikanarray
seperti yang kita lakukan jika kita meletakkannya di tumpukan, seperti ini:Jika
count
besar, ini dapat menyebabkan tumpukan kami tumbuh terlalu besar dan menimpa segmen program lainnya. Ketika stack overflow ini terjadi, program Anda mogok (atau lebih buruk).Jadi, dalam kasus di mana kita tidak tahu berapa banyak memori yang akan kita butuhkan sampai runtime, kita gunakan
malloc()
. Kemudian kita bisa menanyakan jumlah byte yang kita butuhkan saat kita membutuhkannya, danmalloc()
akan memeriksa apakah bisa byte sebanyak itu. Jika bisa, bagus, kami mendapatkannya kembali, jika tidak, kami mendapatkan pointer NULL yang memberi tahu kami bahwa panggilanmalloc()
gagal. Khususnya, program ini tidak macet! Tentu saja Anda sebagai programmer dapat memutuskan bahwa program Anda tidak diizinkan berjalan jika alokasi sumber daya gagal, tetapi pemutusan yang diprogram oleh programmer berbeda dari crash palsu.Jadi sekarang kita harus kembali untuk melihat efisiensi. Penyusun stack sangat cepat - satu instruksi untuk dialokasikan, satu instruksi untuk membatalkan alokasi, dan itu dilakukan oleh kompiler, tetapi ingat stack dimaksudkan untuk hal-hal seperti variabel lokal dari ukuran yang diketahui sehingga cenderung cukup kecil.
Pengalokasi tumpukan di sisi lain adalah beberapa pesanan lebih lambat lebih besar. Itu harus melakukan pencarian di tabel untuk melihat apakah ia memiliki cukup memori bebas untuk dapat memv jumlah memori yang diinginkan pengguna. Itu harus memperbarui tabel-tabel setelah vending memori untuk memastikan tidak ada orang lain yang dapat menggunakan blok itu (pembukuan ini mungkin memerlukan pengalokasi untuk mencadangkan memori untuk dirinya sendiri di samping apa yang ia rencanakan untuk dijual). Pengalokasi harus menggunakan strategi penguncian untuk memastikannya mengosongkan memori dengan cara yang aman. Dan ketika ingatan akhirnya
free()
d, yang terjadi pada waktu yang berbeda dan tanpa urutan yang dapat diprediksi biasanya, pengalokasi harus menemukan blok yang berdekatan dan menyatukannya kembali untuk memperbaiki fragmentasi timbunan. Jika kedengarannya seperti itu akan membutuhkan lebih dari satu instruksi CPU tunggal untuk menyelesaikan semua itu, Anda benar! Ini sangat rumit dan butuh beberapa saat.Tapi tumpukan itu besar. Jauh lebih besar dari tumpukan. Kita bisa mendapatkan banyak memori dari mereka dan mereka hebat ketika kita tidak tahu pada waktu kompilasi berapa banyak memori yang kita butuhkan. Jadi, kami menukar kecepatan dengan sistem memori terkelola yang menolak kami dengan sopan alih-alih menabrak ketika kami mencoba mengalokasikan sesuatu yang terlalu besar.
Saya harap itu membantu menjawab beberapa pertanyaan Anda. Harap beri tahu saya jika Anda ingin klarifikasi pada salah satu di atas.
sumber
int
bukan 8 byte pada platform 64-bit. Masih 4. Seiring dengan itu, kompiler sangat mungkin untuk mengoptimalkan ketigaint
dari tumpukan ke register kembali. Faktanya, dua argumen juga kemungkinan ada dalam register pada platform 64-bit.int
pada platform 64-bit. Anda benar yangint
tetap 4-byte di Jawa. Saya telah meninggalkan sisa jawaban saya namun karena saya percaya masuk ke optimasi kompiler menempatkan kereta di depan kuda. Ya, Anda juga benar pada poin-poin ini, tetapi pertanyaannya meminta klarifikasi tentang tumpukan vs tumpukan. RVO, argumen lewat register, kode elision, dll membebani konsep dasar dan menghalangi pemahaman dasar-dasar.