Hapus C ++ vs Java GC


8

Pengumpulan sampah Jawa menangani benda mati di tumpukan, tetapi terkadang membekukan dunia. Dalam C ++ saya harus memanggil deleteuntuk membuang objek yang dibuat pada akhir siklus hidup itu.

Ini deletesepertinya harga yang sangat rendah untuk membayar untuk lingkungan yang tidak beku. Menempatkan semua deletekata kunci yang relevan adalah tugas mekanis. Orang dapat menulis skrip yang akan melakukan perjalanan melalui kode dan menghapus tempat setelah tidak ada cabang baru menggunakan objek tertentu.

Jadi, apa pro dan kontra dari Java build in vs C ++ model diy pengumpulan sampah.


Saya tidak ingin memulai utas C ++ vs Java. Pertanyaan saya berbeda.
Semua hal GC ini - apakah itu bermuara pada "hanya menjadi rapi, jangan lupa untuk menghapus objek yang telah Anda buat - dan Anda tidak akan memerlukan GC khusus? Atau apakah lebih seperti" membuang objek di C ++ benar-benar rumit - saya menghabiskan 20% dari waktu saya untuk itu, namun, kebocoran memori adalah tempat yang umum "?


15
Saya merasa sulit untuk percaya bahwa naskah ajaib Anda bisa ada. Terlepas dari apa pun itu tidak harus mengandung solusi untuk masalah yang terhenti. Atau sebagai alternatif (dan lebih mungkin) hanya bekerja untuk program yang sangat sederhana
Richard Tingle

1
Juga java modern tidak membeku kecuali dalam kondisi ekstrem di mana sejumlah besar sampah dibuat
Richard Tingle

1
@ThomasCarlisle juga bisa - dan kadang-kadang tidak - berdampak pada kinerja. Tetapi ada banyak parameter yang harus di-tweak dalam kasus ini, dan kadang-kadang solusinya adalah beralih ke gc yang berbeda sama sekali. Itu semua tergantung pada jumlah sumber daya yang tersedia dan beban khas.
Hulk

4
" Menempatkan semua kata kunci penghapusan yang relevan adalah tugas mekanis " - Itulah sebabnya ada alat untuk mendeteksi kebocoran memori. Karena itu sangat sederhana dan tidak rawan kesalahan sama sekali.
JensG

2
@Doval, jika Anda menggunakan RAII dengan benar, yang cukup sederhana, hampir tidak ada pembukuan sama sekali. newpergi di konstruktor kelas Anda yang mengelola memori, deleteberjalan di destruktor. Dari sana, semuanya otomatis (penyimpanan). NB bahwa ini bekerja untuk semua jenis sumber daya, bukan hanya memori, tidak seperti pengumpulan sampah. Mutex diambil dalam konstruktor, dirilis dalam destruktor. File dibuka di konstruktor, ditutup di destruktor.
Rob K

Jawaban:


18

Siklus hidup objek C ++

Jika Anda membuat objek lokal, Anda tidak perlu menghapusnya: kompiler menghasilkan kode untuk menghapusnya secara otomatis ketika objek keluar dari cakupan

Jika Anda menggunakan pointer objek dan membuat objek di toko gratis, maka Anda harus berhati-hati menghapus objek ketika itu tidak lagi diperlukan (seperti yang telah Anda jelaskan). Sayangnya, dalam perangkat lunak yang kompleks, ini mungkin jauh lebih menantang daripada yang terlihat (misalnya bagaimana jika pengecualian muncul dan bagian penghapusan yang diharapkan tidak pernah tercapai?)

Untungnya, dalam C ++ modern (alias C ++ 11 dan yang lebih baru), Anda memiliki pointer cerdas, seperti misalnya shared_ptr. Mereka referensi-menghitung objek yang dibuat dengan cara yang sangat efisien, sedikit seperti yang dilakukan pengumpul sampah di Jawa. Dan segera setelah objek tidak lagi direferensikan, aktif terakhir shared_ptrmenghapus objek untuk Anda. Secara otomatis. Seperti pengumpul sampah, tetapi satu objek pada satu waktu dan tanpa penundaan (Ok: Anda perlu perhatian ekstra dan weak_ptruntuk mengatasi referensi melingkar).

Kesimpulan: Anda saat ini dapat menulis kode C ++ tanpa harus khawatir tentang alokasi memori, dan yang sama bebasnya dengan GC, tetapi tanpa efek pembekuan.

Siklus hidup objek Java

Yang menyenangkan adalah Anda tidak perlu khawatir tentang siklus hidup objek. Anda cukup membuatnya dan java akan mengurus sisanya. Sebuah GC yang modern akan mengidentifikasi dan menghancurkan benda-benda yang tidak lagi diperlukan (termasuk jika ada referensi melingkar antara objek mati).

Sayangnya, karena kenyamanan ini, Anda tidak dapat mengontrol kapan objek benar-benar dihapus . Secara semantik, penghapusan / penghancuran bertepatan dengan pengumpulan sampah .

Ini sangat bagus jika melihat objek hanya dari segi memori. Kecuali untuk pembekuan, tetapi ini bukan kematian (orang-orang mengerjakan ini). Saya bukan ahli Java, tapi saya pikir penghancuran yang tertunda membuatnya lebih sulit untuk mengidentifikasi kebocoran di java karena referensi sengaja disimpan meskipun objek tidak lagi diperlukan (yaitu Anda tidak dapat benar-benar memantau penghapusan objek).

Tetapi bagaimana jika objek harus mengendalikan sumber daya selain memori, misalnya file terbuka, semafor, layanan sistem? Kelas Anda harus menyediakan metode untuk melepaskan sumber daya ini. Dan Anda akan memiliki tanggung jawab untuk memastikan bahwa metode ini dipanggil ketika sumber daya tidak lagi dibutuhkan. Di setiap jalur percabangan yang mungkin melalui kode Anda, memastikan itu juga dipanggil jika ada pengecualian. Tantangannya sangat mirip dengan penghapusan eksplisit di C ++.

Kesimpulan: GC memecahkan masalah manajemen memori. Tetapi tidak membahas pengelolaan sumber daya sistem lainnya. Tidak adanya penghapusan "tepat waktu" mungkin membuat manajemen sumber daya sangat menantang.

Penghapusan, pengumpulan sampah, dan RAII

Saat Anda bisa mengontrol penghapusan suatu objek dan destruktor yang dipanggil pada penghapusan, Anda bisa memanfaatkan RAII . Pendekatan ini memandang memori hanya sebagai kasus khusus alokasi sumber daya dan menautkan pengelolaan sumber daya lebih aman ke siklus hidup objek, sehingga memastikan penggunaan sumber daya yang dikontrol dengan ketat.


3
Keindahan pengumpul sampah (modern) adalah Anda tidak perlu memikirkan referensi siklik. Kelompok-kelompok Objek yang tidak dapat dijangkau kecuali dari satu sama lain dapat dideteksi dan dikumpulkan. Itu adalah keuntungan besar dibandingkan penghitungan referensi sederhana / petunjuk pintar.
Hulk

6
+1 Bagian "pengecualian" tidak dapat cukup ditekankan. Kehadiran pengecualian membuat tugas manajemen memori manual yang sulit dan tidak ada artinya tidak mungkin, dan dengan demikian manajemen memori manual tidak digunakan dalam C ++. Gunakan RAII. Jangan gunakan di newluar konstruktor / pointer pintar, dan jangan pernah gunakan di deleteluar destruktor.
Felix Dombek

@ Hulk Anda ada benarnya di sini! Meskipun sebagian besar argumen saya masih valid, GC membuat banyak kemajuan. Dan referensi melingkar memang sulit untuk ditangani dengan referensi menghitung pointer pintar saja. Jadi saya mengedit jawaban saya, untuk menjaga keseimbangan yang lebih baik. Saya juga menambahkan referensi ke artikel tentang kemungkinan strategi untuk mengurangi efek pembekuan.
Christophe

3
+1 Pengelolaan sumber daya selain memori memang membutuhkan upaya ekstra untuk bahasa GC karena RAII tidak berfungsi jika tidak ada kontrol yang lebih baik atas kapan (atau jika) penghapusan / finalisasi objek happpens. Konstruk khusus tersedia di sebagian besar bahasa ini (lihat coba-dengan-sumber daya java , misalnya), yang dapat, dalam kombinasi dengan peringatan kompiler, membantu memperbaiki hal-hal ini.
Hulk

1
Like garbage collector, but one object at a time and without delay (Ok: you need some extra care and weak_ptr to cope with circular references).Penghitungan referensi dapat mengalir. Misalnya referensi terakhir untuk Apergi, tetapi Ajuga memiliki referensi terakhir untuk B, yang memiliki referensi terakhir ke C... And you'll have the responsibility to make sure that this method is called when the resources are no longer needed. In every possible branching path through your code, ensuring it is also invoked in case of exceptions.Java dan C # memiliki pernyataan blok khusus untuk ini.
Doval

5

Penghapusan ini sepertinya harga yang sangat rendah untuk dibayar untuk lingkungan yang tidak beku. Menempatkan semua kata kunci penghapusan yang relevan adalah tugas mekanis. Orang dapat menulis skrip yang akan melakukan perjalanan melalui kode dan menghapus tempat setelah tidak ada cabang baru menggunakan objek tertentu.

Jika Anda dapat menulis skrip seperti itu, selamat. Anda adalah pengembang yang lebih baik daripada saya. Sejauh ini.

Satu-satunya cara Anda benar-benar dapat menghindari kebocoran memori dalam kasus-kasus praktis adalah standar pengkodean yang sangat ketat dengan aturan yang sangat ketat yang merupakan pemilik suatu objek, dan kapan benda itu dapat dan harus dilepaskan, atau alat seperti smart pointer yang menghitung referensi ke objek dan menghapus keberatan ketika referensi terakhir hilang.


4
Ya, melakukan manajemen sumber daya manual selalu rawan kesalahan, untungnya itu hanya diperlukan di Jawa (untuk sumber daya non-memori), karena C ++ memiliki RAII.
Deduplicator

Satu-satunya cara Anda benar-benar dapat menghindari kebocoran sumber daya APAPUN dalam kasus praktis adalah standar pengkodean yang sangat ketat dengan aturan yang sangat ketat yang merupakan pemilik objek ... memori bukan satu-satunya sumber daya, dan dalam kebanyakan kasus, bukan yang paling kritis antara.
curiousguy

2
Jika Anda menganggap RAII sebagai 'standar pengkodean yang sangat ketat'. Saya menganggapnya sebagai 'lubang kesuksesan', mudah digunakan.
Rob K

5

Jika Anda menulis kode C ++ yang benar dengan RAII, Anda biasanya tidak menulis yang baru atau menghapus. Satu-satunya "baru" yang Anda tulis ada di dalam pointer bersama sehingga Anda benar-benar tidak perlu menggunakan "hapus".


1
Anda seharusnya tidak menggunakan newsama sekali, bahkan dengan pointer bersama - Anda harus menggunakan std::make_sharedsebagai gantinya.
Jules

1
atau lebih baik make_unique,. Sangat jarang Anda benar-benar membutuhkan kepemilikan bersama.
Marc

4

Membuat hidup programmer lebih mudah dan mencegah kebocoran memori adalah keuntungan penting dari pengumpulan sampah tetapi tidak hanya satu. Lain adalah mencegah fragmentasi memori. Di C ++, setelah Anda mengalokasikan objek menggunakan newkata kunci, itu tetap dalam posisi tetap dalam memori. Ini berarti bahwa, ketika aplikasi berjalan, Anda pada akhirnya memiliki celah kehabisan memori di antara objek yang dialokasikan. Jadi mengalokasikan memori dalam C ++ harus menjadi proses yang lebih rumit, karena sistem operasi harus dapat menemukan blok yang tidak terisi dengan ukuran yang sesuai antara celah.

Pengumpulan sampah mengatasinya dengan mengambil semua objek yang tidak dihapus dan menggesernya dalam memori sehingga mereka membentuk blok yang berkelanjutan. Jika Anda merasa bahwa pengumpulan sampah memerlukan waktu, itu mungkin karena proses ini, bukan karena alokasi memori itu sendiri. Manfaatnya adalah bahwa ketika datang ke alokasi memori, itu hampir sama mudahnya dengan menggeser pointer ke ujung tumpukan.

Jadi, dalam C ++ menghapus objek cepat tetapi membuatnya bisa lambat. Di Jawa membuat objek tidak membutuhkan waktu sama sekali tetapi Anda perlu melakukan pembersihan rumah sesekali.


4
Ya, mengalokasikan dari toko gratis lebih lambat di C ++ daripada Java. Untungnya, ini jauh lebih jarang, dan Anda dapat dengan mudah menggunakan pengalokasi tujuan khusus atas kebijakan Anda sendiri di mana Anda memiliki pola yang tidak biasa. Juga, semua sumber daya sama dalam C ++, Java memiliki kasus khusus.
Deduplicator

3
Dalam 20 tahun pengkodean dalam C ++, saya tidak pernah melihat fragmentasi memori menjadi masalah. OS modern dengan manajemen memori virtual pada prosesor dengan beberapa tingkat cache sebagian besar telah menghilangkan ini sebagai masalah.
Rob K

@RobK - "OS modern dengan manajemen memori virtual pada prosesor dengan beberapa level cache sebagian besar telah menghilangkan [fragmentasi] sebagai masalah" - tidak. Anda mungkin tidak menyadarinya, tetapi itu masih terjadi, masih menyebabkan (1) memori terbuang dan (2) penggunaan cache yang kurang efisien, dan satu-satunya solusi yang dapat dilakukan adalah menyalin GC atau desain manajemen memori manual yang sangat hati-hati untuk memastikan tidak akan terjadi.
Jules

3

Janji-janji utama Jawa adalah

  1. Dapat dimengerti C menyukai sintaksis
  2. Tulis satu jalankan di mana-mana
  3. Kami membuat pekerjaan Anda lebih mudah - kami bahkan mengurus sampah.

Sepertinya Java menjamin Anda bahwa sampah akan dibuang (tidak harus dengan cara yang efisien). Jika Anda menggunakan C / C ++ Anda memiliki kebebasan dan tanggung jawab. Anda bisa melakukannya lebih baik daripada GC Java, atau Anda bisa jauh lebih buruk (lewati deletesemua bersama dan memiliki masalah kebocoran memori).

Jika Anda memerlukan kode yang "memenuhi standar kualitas tertentu" dan untuk mengoptimalkan "rasio harga / kualitas" gunakan Java. Jika Anda siap untuk berinvestasi sumber daya tambahan (waktu ahli Anda) untuk meningkatkan kinerja aplikasi kritis misi - gunakan C.


Ya, semua janji itu bisa dilihat sebagai rusak, dengan mudah.
Deduplicator

Kecuali bahwa satu - satunya sampah yang dikumpulkan Java adalah memori yang dialokasikan secara dinamis. Itu tidak melakukan apa-apa tentang sumber daya lain yang dialokasikan secara dinamis.
Rob K

@RobK - itu tidak sepenuhnya benar. Penggunaan finalizer objek dapat menangani deallokasi sumber daya lainnya. Ini sangat tidak dianjurkan karena dalam kebanyakan kasus Anda tidak menginginkan hal itu (tidak seperti memori, sebagian besar jenis sumber daya lainnya jauh lebih terbatas atau bahkan unik dan karena itu penempatan yang tepat sangat penting), tetapi hal itu dapat dilakukan. Java juga memiliki pernyataan coba-dengan-sumber daya yang dapat digunakan untuk mengotomatisasi pengelolaan sumber daya lain (memberikan manfaat yang serupa dengan RAII).
Jules

@ Jules: Satu perbedaan utama adalah bahwa dalam C ++ objek self-destruct. Dalam Java Closeable objek tidak dapat menutup sendiri. Pengembang Java yang menulis "Sesuatu baru (...)" atau (lebih buruk) Sesuatu s = SomeFactory.create (...) harus memeriksa untuk melihat apakah Sesuatu itu Dekat. Mengubah kelas dari tidak Closeable ke Closeable adalah jenis perubahan terburuk, dan praktis tidak pernah bisa dilakukan. Tidak terlalu buruk di tingkat kelas, tetapi masalah serius ketika seseorang mendefinisikan antarmuka Java.
kevin cline

2

Perbedaan besar yang dihasilkan pengumpulan sampah adalah Anda tidak perlu menghapus objek secara eksplisit. Perbedaan yang jauh lebih besar adalah Anda tidak perlu menyalin objek.

Ini memiliki efek yang meresap dalam merancang program dan antarmuka secara umum. Izinkan saya memberikan satu contoh kecil untuk menunjukkan seberapa jauh jangkauannya.

Di Jawa, ketika Anda mengeluarkan sesuatu dari tumpukan, nilai yang muncul dikembalikan, sehingga Anda mendapatkan kode seperti ini:

WhateverType value = myStack.Pop();

Di Jawa, ini adalah pengecualian aman, karena semua yang kita lakukan sebenarnya adalah menyalin referensi ke objek, yang dijamin terjadi tanpa pengecualian. Hal yang sama tidak benar dalam C ++. Dalam C ++, mengembalikan nilai berarti (atau setidaknya bisa berarti) menyalin nilai itu, dan dengan beberapa jenis yang bisa melempar pengecualian. Jika pengecualian dilemparkan setelah item telah dihapus dari tumpukan, tetapi sebelum salinan sampai ke penerima, item telah bocor. Untuk mencegahnya, tumpukan C ++ menggunakan pendekatan yang agak klumsier di mana mengambil item teratas dan menghapus item teratas adalah dua operasi terpisah:

WhateverType value = myStack.top();
myStack.pop();

Jika pernyataan pertama melempar pengecualian, yang kedua tidak akan dieksekusi, jadi jika pengecualian dilemparkan dalam penyalinan, item tersebut tetap berada di tumpukan seolah-olah tidak ada yang terjadi sama sekali.

Masalah yang jelas adalah bahwa ini hanya canggung dan (bagi orang yang belum menggunakannya) tidak terduga.

Hal yang sama berlaku di banyak bagian lain dari C ++: terutama dalam kode generik, keamanan pengecualian meliputi banyak bagian desain - dan ini sebagian besar disebabkan oleh fakta bahwa paling tidak paling berpotensi melibatkan penyalinan objek (yang mungkin dibuang), di mana Java hanya akan membuat referensi baru ke objek yang sudah ada (yang tidak bisa melempar, jadi kita tidak perlu khawatir tentang pengecualian).

Sejauh skrip sederhana untuk disisipkan di deletemana diperlukan: jika Anda dapat secara statis menentukan kapan harus menghapus item berdasarkan struktur kode sumber, mungkin seharusnya tidak menggunakan newdan deletedi tempat pertama.

Biarkan saya memberi Anda contoh program yang hampir pasti tidak mungkin: sistem untuk panggilan, pelacakan, penagihan (dll) panggilan telepon. Ketika Anda memanggil ponsel Anda, itu menciptakan objek "panggilan". Objek panggilan melacak siapa yang Anda panggil, berapa lama Anda berbicara dengan mereka, dll., Untuk menambahkan catatan yang sesuai ke log penagihan. Objek panggilan memonitor status perangkat keras, jadi ketika Anda menutup telepon, itu menghancurkan dirinya sendiri (menggunakan yang banyak dibahas delete this;). Hanya saja, itu tidak terlalu sepele seperti "ketika Anda menutup telepon". Misalnya, Anda dapat melakukan panggilan konferensi, menghubungkan dua orang, dan menutup telepon - tetapi panggilan tersebut berlanjut di antara kedua pihak bahkan setelah Anda menutup telepon (tetapi tagihan dapat berubah).


"Jika pengecualian dilemparkan setelah item telah dihapus dari tumpukan, tetapi sebelum salinan sampai ke penerima, item telah bocor" apakah Anda punya referensi untuk ini? Karena ini terdengar sangat aneh walaupun saya bukan ahli c ++.
Esben Skov Pedersen

@EsbenSkovPedersen: GoTW # 8 akan menjadi titik awal yang masuk akal. Jika Anda memiliki akses ke sana, C ++ Luar Biasa memiliki lebih banyak. Perhatikan bahwa keduanya mengharapkan setidaknya beberapa pengetahuan C ++ yang sudah ada sebelumnya.
Jerry Coffin

Itu tampaknya cukup lurus ke depan. Sebenarnya kalimat ini yang membingungkan saya "Di C ++, mengembalikan nilai berarti (atau setidaknya bisa berarti) menyalin nilai itu, dan dengan beberapa jenis yang bisa menimbulkan pengecualian" Apakah ini salinan pada heap atau stack?
Esben Skov Pedersen

Anda benar - benar tidak perlu menyalin objek di C ++, tetapi Anda bisa jika mau, tidak seperti bahasa sampah yang dikumpulkan (Java, C #) yang menjadikannya PITA untuk menyalin objek saat Anda mau. 90% dari objek yang saya buat harus dihancurkan dan sumber dayanya dibebaskan ketika mereka keluar dari ruang lingkup. Untuk memaksa semua objek ke penyimpanan dinamis karena 10% dari mereka harus tampak bodoh.
Rob K

2
Ini sebenarnya bukan perbedaan yang disebabkan oleh penggunaan pengumpulan sampah, tetapi oleh filosofi Jawa "semua objek adalah referensi" yang disederhanakan. Anggap C # sebagai contoh tandingan: ini adalah bahasa yang dikumpulkan sampah, tetapi juga memiliki objek bernilai ("struct" dalam terminologi lokal, yang berbeda dari C ++ struct) yang memiliki semantik menyalin. C # menghindari masalah dengan (1) memiliki pemisahan yang jelas antara tipe referensi dan nilai dan (2) selalu menyalin tipe nilai menggunakan penyalinan byte-untuk-byte, bukan kode pengguna, sehingga mencegah pengecualian selama penyalinan.
Jules

2

Sesuatu yang saya pikir tidak disebutkan di sini adalah bahwa ada efisiensi yang berasal dari pengumpulan sampah. Dalam kolektor Java yang paling umum digunakan, tempat utama objek dialokasikan adalah area yang disediakan untuk kolektor penyalinan. Ketika hal-hal mulai, ruang ini kosong. Ketika objek dibuat, mereka dialokasikan bersebelahan di ruang terbuka yang besar sampai tidak dapat mengalokasikan satu di ruang bersebelahan yang tersisa. GC menendang dan mencari benda di ruang ini yang tidak mati. Ini menyalin objek hidup ke daerah lain dan menyatukannya (yaitu tidak ada fragmentasi.) Ruang lama dianggap bersih. Kemudian terus mengalokasikan objek bersama-sama dan mengulangi proses ini sesuai kebutuhan.

Ada dua manfaatnya. Yang pertama adalah tidak ada waktu dihabiskan untuk menghapus objek yang tidak digunakan. Setelah benda hidup disalin, batu tulis dianggap bersih dan benda mati hanya dilupakan. Dalam banyak aplikasi, sebagian besar objek tidak berumur panjang sehingga biaya menyalin set hidup lebih murah dibandingkan dengan penghematan yang diperoleh dengan tidak perlu khawatir tentang set mati.

Manfaat kedua adalah ketika objek baru dialokasikan, tidak perlu mencari area yang berdekatan. VM selalu tahu di mana objek berikutnya akan ditempatkan (peringatan: concurrency diabaikan disederhanakan.)

Pengumpulan dan alokasi semacam ini sangat cepat. Dari perspektif throughput keseluruhan, sulit dikalahkan untuk banyak skenario. Masalahnya adalah beberapa objek akan hidup lebih lama dari yang Anda inginkan untuk terus menyalinnya dan pada akhirnya itu berarti kolektor mungkin perlu berhenti untuk waktu yang signifikan setiap sesekali dan kapan itu akan terjadi dapat diprediksi. Tergantung pada panjang jeda dan jenis aplikasi, ini mungkin atau mungkin tidak menjadi masalah. Setidaknya ada satu kolektor yang tidak ada jeda . Saya berharap ada beberapa tradeoff efisiensi yang lebih rendah untuk mendapatkan sifat yang tanpa jeda, tetapi salah satu orang yang mendirikan perusahaan itu (Gil Tene) adalah ahli di GC dan presentasinya adalah sumber informasi hebat tentang GC.


Azul didirikan oleh sekelompok orang-orang GC, orang-orang penyusun, orang-orang kinerja, dan mantan orang-orang CPU Jawa dari Sun. Mereka tahu apa yang mereka lakukan.
Jörg W Mittag

@ JörgWMittag diperbarui untuk mencerminkan ada lebih dari satu pendiri. terima kasih
JimmyJames

1

Atau lebih seperti "membuang benda di C ++ benar-benar rumit - saya menghabiskan 20% waktu saya untuk itu, namun, kebocoran memori adalah hal yang biasa"?

Dalam pengalaman pribadi saya di C ++ dan bahkan C, kebocoran memori tidak pernah sulit untuk dihindari. Dengan prosedur pengujian yang waras dan Valgrind, misalnya, setiap kebocoran fisik yang disebabkan oleh panggilan operator new/malloctanpa sambungan delete/freesering terdeteksi dan diperbaiki dengan cepat. Agar adil beberapa kode C besar atau sekolah tua C ++ mungkin sangat mungkin memiliki beberapa kasus tepi yang tidak jelas yang mungkin secara fisik membocorkan beberapa byte memori di sana-sini sebagai akibat dari tidak deleting/freeingdalam kasus tepi yang terbang di bawah radar pengujian.

Namun sejauh pengamatan praktis, aplikasi terlemah yang saya temui (seperti pada yang mengkonsumsi lebih banyak dan lebih banyak memori semakin lama Anda menjalankannya, meskipun jumlah data yang kami kerjakan tidak bertambah) biasanya tidak ditulis dalam C atau C ++. Saya tidak menemukan hal-hal seperti Linux Kernel atau Unreal Engine atau bahkan kode asli yang digunakan untuk mengimplementasikan Java di antara daftar perangkat lunak bocor yang saya temui.

Jenis perangkat lunak bocor yang paling menonjol yang cenderung saya temui adalah hal-hal seperti applet Flash, seperti game Flash, meskipun mereka menggunakan pengumpulan sampah. Dan itu bukan perbandingan yang adil jika seseorang menyimpulkan sesuatu dari ini karena banyak aplikasi Flash ditulis oleh pengembang pemula yang mungkin kurang memiliki prinsip-prinsip rekayasa suara dan prosedur pengujian (dan juga saya yakin ada profesional yang terampil di luar sana yang bekerja dengan GC yang jangan berjuang dengan perangkat lunak yang bocor), tetapi saya ingin mengatakan banyak hal kepada siapa pun yang berpikir GC mencegah perangkat lunak yang bocor ditulis.

Pointer Menggantung

Sekarang datang dari domain khusus saya, pengalaman, dan sebagai salah satu yang sebagian besar menggunakan C dan C ++ (dan saya berharap manfaat GC akan bervariasi tergantung pada pengalaman dan kebutuhan kami), hal yang paling langsung diselesaikan GC bagi saya bukanlah masalah kebocoran memori praktis tetapi Menggantung akses pointer, dan itu benar-benar bisa menjadi penyelamat dalam skenario misi-kritis.

Sayangnya dalam banyak kasus di mana GC memecahkan apa yang seharusnya menjadi akses pointer menggantung, itu menggantikan kesalahan programmer yang sama dengan kebocoran memori logis.

Jika Anda membayangkan game Flash yang ditulis oleh pembuat kode pemula, ia mungkin menyimpan referensi ke elemen-elemen game dalam beberapa struktur data, membuatnya berbagi kepemilikan atas sumber daya game ini. Sayangnya, katakanlah dia membuat kesalahan di mana dia lupa untuk menghapus elemen-elemen game dari salah satu struktur data setelah maju ke tahap berikutnya, mencegah mereka dibebaskan sampai seluruh game ditutup. Namun, gim ini tetap berfungsi dengan baik karena elemen-elemennya tidak digambar atau memengaruhi interaksi pengguna. Namun demikian, gim ini mulai menggunakan lebih banyak dan lebih banyak memori sementara laju bingkai bekerja sendiri untuk tayangan slide, sementara pemrosesan tersembunyi masih berulang melalui kumpulan elemen tersembunyi dalam gim ini (yang kini telah menjadi sangat besar ukurannya). Ini adalah jenis masalah yang sering saya temui dalam game Flash tersebut.

  • Saya telah menemukan orang mengatakan ini tidak dihitung sebagai "kebocoran memori" karena memori masih dibebaskan setelah menutup aplikasi, dan sebagai gantinya mungkin disebut 'kebocoran ruang' atau sesuatu untuk efek ini. Sementara perbedaan seperti itu mungkin berguna untuk mengidentifikasi dan berbicara tentang masalah, saya tidak menemukan perbedaan seperti itu sangat berguna dalam konteks ini jika kita berbicara tentang itu seperti itu tidak bermasalah seperti "kebocoran memori" ketika kita berurusan tujuan praktis untuk memastikan perangkat lunak tidak memonopoli jumlah memori yang konyol semakin lama kita menjalankannya (kecuali kita berbicara sistem operasi yang tidak membebaskan memori proses ketika dihentikan).

Sekarang katakanlah pengembang pemula yang sama menulis game di C ++. Dalam hal itu biasanya hanya akan ada satu struktur data pusat dalam game yang "memiliki" memori sementara yang lain menunjuk ke memori itu. Jika dia membuat kesalahan yang sama, kemungkinannya adalah bahwa, setelah maju ke tahap berikutnya, permainan akan crash sebagai akibat dari mengakses pointer menggantung (atau lebih buruk, melakukan sesuatu selain crash).

Ini adalah jenis trade-off paling cepat yang cenderung saya temui di domain saya paling sering antara GC dan tidak ada GC. Dan saya sebenarnya tidak terlalu peduli dengan GC di domain saya, yang tidak terlalu kritis terhadap misi, karena pergulatan terbesar yang pernah saya miliki dengan perangkat lunak bocor melibatkan penggunaan GC secara serampangan di sebuah tim sebelumnya yang menyebabkan kebocoran yang dijelaskan di atas. .

Dalam domain khusus saya, saya sebenarnya lebih suka perangkat lunak crash atau glitching dalam banyak kasus karena itu setidaknya jauh lebih mudah untuk dideteksi daripada mencoba melacak mengapa perangkat lunak secara misterius mengkonsumsi jumlah memori eksplosif setelah menjalankannya selama setengah jam sementara semua dari kami tes unit dan integrasi lulus tanpa keluhan (bahkan dari Valgrind, karena memori dibebaskan oleh GC saat dimatikan). Namun itu bukan slam pada GC di pihak saya atau upaya untuk mengatakan bahwa itu tidak berguna atau semacamnya, tapi itu belum ada peluru perak, bahkan tidak dekat, dalam tim saya bekerja dengan perangkat lunak bocor (untuk sebaliknya saya memiliki pengalaman yang berlawanan dengan satu basis kode yang menggunakan GC menjadi yang paling lemah yang pernah saya temui). Agar adil, banyak anggota di tim itu bahkan tidak tahu apa itu referensi yang lemah,

Kepemilikan dan Psikologi Bersama

Masalah yang saya temukan dengan pengumpulan sampah yang membuatnya sangat rentan terhadap "kebocoran memori" (dan saya akan bersikeras menyebutnya sebagai 'kebocoran ruang' berperilaku dengan cara yang sama persis dari perspektif pengguna-akhir) di tangan mereka yang tidak menggunakannya dengan hati-hati berhubungan dengan "kecenderungan manusia" sampai tingkat tertentu dalam pengalaman saya. Masalah dengan tim itu dan basis kode paling sedikit yang pernah saya temui adalah bahwa mereka tampaknya berada di bawah kesan bahwa GC akan memungkinkan mereka untuk berhenti memikirkan siapa yang memiliki sumber daya.

Dalam kasus kami, kami memiliki begitu banyak objek yang saling rujukan. Model akan merujuk bahan bersama dengan perpustakaan bahan dan sistem shader. Bahan akan referensi tekstur bersama dengan pustaka tekstur dan shader tertentu. Kamera akan menyimpan referensi ke semua jenis entitas adegan yang harus dikecualikan dari rendering. Daftar itu kelihatannya berlanjut tanpa batas. Itu membuat hampir semua sumber daya yang lumayan dalam sistem yang dimiliki dan diperpanjang seumur hidup di 10+ tempat lain di negara aplikasi sekaligus, dan itu sangat, sangat rentan terhadap kesalahan manusia dari jenis yang akan diterjemahkan menjadi kebocoran (dan tidak yang minor, saya berbicara gigabyte dalam hitungan menit dengan masalah kegunaan yang serius). Secara konseptual semua sumber daya ini tidak perlu dibagi dalam kepemilikan, mereka semua secara konseptual memiliki satu pemilik,

Jika kita berhenti berpikir tentang siapa yang memiliki memori apa, dan dengan senang hati hanya menyimpan referensi seumur hidup untuk objek di seluruh tempat tanpa memikirkan hal ini, maka perangkat lunak tidak akan crash sebagai akibat dari pointer yang menggantung tetapi hampir pasti, di bawah suatu pola pikir yang ceroboh, mulailah membocorkan ingatan seperti orang gila dengan cara yang sangat sulit dilacak dan akan lolos dari ujian.

Jika ada satu manfaat praktis untuk penunjuk menggantung di domain saya, itu menyebabkan gangguan dan crash yang sangat buruk. Dan itu cenderung setidaknya memberikan pengembang insentif, jika mereka ingin mengirimkan sesuatu yang dapat diandalkan, untuk mulai berpikir tentang manajemen sumber daya dan melakukan hal-hal yang diperlukan yang diperlukan untuk menghapus semua referensi tambahan / pointer ke objek yang tidak lagi dibutuhkan secara konseptual.

Manajemen Sumber Daya Aplikasi

Manajemen sumber daya yang tepat adalah nama permainan jika kita berbicara tentang menghindari kebocoran dalam aplikasi yang berumur panjang dengan keadaan terus-menerus disimpan di mana kebocoran akan menimbulkan frame rate yang serius dan masalah kegunaan. Dan mengelola sumber daya dengan benar di sini tidak kalah sulit dengan atau tanpa GC. Karya ini tidak kurang manual baik cara untuk menghapus referensi yang sesuai untuk objek tidak lagi diperlukan apakah itu pointer atau referensi yang memperpanjang seumur hidup.

Itulah tantangan di domain saya, tidak lupa dengan deleteapa yang kami new(kecuali kami berbicara jam amatir dengan pengujian, praktik, dan alat jelek). Dan itu membutuhkan pemikiran dan kepedulian apakah kita menggunakan GC atau tidak.

Multithreading

Satu masalah lain yang saya temukan sangat berguna dengan GC, jika bisa digunakan dengan sangat hati-hati dalam domain saya, adalah menyederhanakan manajemen sumber daya dalam konteks multithreading. Jika kami berhati-hati untuk tidak menyimpan referensi perpanjangan sumber daya seumur hidup di lebih dari satu tempat dalam status aplikasi, maka sifat referensi GC yang diperluas seumur hidup bisa sangat berguna sebagai cara untuk utas untuk sementara memperluas sumber daya yang sedang diakses untuk memperluas masa pakainya hanya untuk durasi yang singkat seperti yang diperlukan agar utas selesai memprosesnya.

Saya pikir sangat hati-hati menggunakan GC dengan cara ini dapat menghasilkan sangat, perangkat lunak yang tidak bocor, sementara secara bersamaan menyederhanakan multithreading.

Ada beberapa cara untuk mengatasi hal ini meskipun tidak ada GC. Dalam kasus saya, kami menyatukan representasi entitas adegan perangkat lunak, dengan utas yang sementara menyebabkan sumber daya adegan diperpanjang untuk jangka waktu singkat dengan cara yang agak umum sebelum fase pembersihan. Ini mungkin sedikit berbau seperti GC tetapi perbedaannya adalah bahwa tidak ada "kepemilikan bersama" yang terlibat, hanya desain pemrosesan adegan yang seragam dalam utas yang menunda penghancuran sumber daya tersebut. Masih akan jauh lebih mudah untuk hanya mengandalkan GC di sini jika itu dapat digunakan dengan sangat hati-hati dengan pengembang yang teliti, hati-hati untuk menggunakan referensi yang lemah di area persisten yang relevan, untuk kasus multithreading tersebut.

C ++

Akhirnya:

Dalam C ++ saya harus memanggil delete untuk membuang objek yang dibuat pada akhir siklus hidup itu.

Di Modern C ++, ini umumnya bukan sesuatu yang harus Anda lakukan secara manual. Bahkan tidak banyak tentang lupa melakukannya. Ketika Anda melibatkan penanganan pengecualian ke dalam gambar, maka bahkan jika Anda menulis korespondensi di deletebawah beberapa panggilan untuk new, sesuatu bisa melempar di tengah dan tidak pernah mencapai deletepanggilan jika Anda tidak bergantung pada panggilan destruktor otomatis yang dimasukkan oleh kompiler untuk melakukan ini untuk kamu.

Dengan C ++ yang secara praktis Anda perlukan, kecuali jika Anda bekerja seperti konteks yang disematkan dengan pengecualian dan pustaka khusus yang sengaja diprogram untuk tidak membuang, hindari pembersihan sumber daya manual seperti itu (yang termasuk menghindari panggilan manual untuk membuka kunci di luar di luar dokumen. , misalnya, dan bukan hanya alokasi memori). Penanganan pengecualian cukup banyak menuntutnya, sehingga semua pembersihan sumber daya harus otomatis melalui destruktor untuk sebagian besar.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.