Mengapa pengumpulan sampah hanya mencakup memori dan bukan tipe sumber daya lainnya?


12

Sepertinya orang bosan dengan manajemen memori manual, jadi mereka menemukan pengumpulan sampah, dan hidup itu cukup baik. Tetapi bagaimana dengan setiap jenis sumber daya lainnya? Deskriptor file, soket, atau bahkan data yang dibuat pengguna seperti koneksi basis data?

Ini terasa seperti pertanyaan yang naif tetapi saya tidak dapat menemukan tempat di mana orang bertanya. Mari kita pertimbangkan deskriptor file. Katakanlah suatu program tahu bahwa itu hanya akan diizinkan memiliki 4000 fds tersedia saat dimulai. Setiap kali ia melakukan operasi yang akan membuka deskriptor file, bagaimana jika itu mau

  1. Periksa untuk memastikan bahwa itu tidak akan habis.
  2. Jika ya, memicu pemulung, yang akan membebaskan banyak memori.
  3. Jika sebagian memori dibebaskan menyimpan referensi ke deskriptor file, tutup segera. Ia tahu memori milik suatu sumber daya karena memori yang terikat pada sumber daya itu terdaftar ke dalam 'file descriptor registry', karena kurangnya istilah yang lebih baik, ketika pertama kali dibuka.
  4. Buka deskriptor file baru, salin ke memori baru, daftarkan lokasi memori itu ke dalam 'file deskriptor registry' dan kembalikan ke pengguna.

Jadi sumber daya tidak akan dibebaskan segera, tetapi akan dibebaskan setiap kali gc berlari yang paling tidak mencakup, tepat sebelum sumber daya akan habis, dengan asumsi itu tidak sepenuhnya digunakan.

Dan sepertinya itu akan cukup untuk banyak masalah pembersihan sumber daya yang ditentukan pengguna. Saya berhasil menemukan satu komentar di sini bahwa referensi melakukan pembersihan mirip dengan ini di C ++ dengan utas yang berisi referensi ke sumber daya dan membersihkannya ketika hanya memiliki satu referensi yang tersisa (dari utas pembersihan), tetapi saya bisa ' t menemukan bukti bahwa ini adalah perpustakaan atau bagian dari bahasa apa pun yang ada.

Jawaban:


4

GC berkaitan dengan sumber daya yang dapat diprediksi dan dicadangkan . VM memiliki kontrol total terhadapnya dan memiliki kontrol total atas instance apa yang dibuat dan kapan. Kata kunci di sini adalah "milik" dan "kontrol total". Pegangan dialokasikan oleh OS, dan pointer ... pointer juga ke sumber daya yang dialokasikan di luar ruang yang dikelola. Karena itu, pegangan dan pointer tidak terbatas untuk digunakan di dalam kode yang dikelola. Mereka dapat digunakan - dan seringkali - dengan kode yang dikelola dan tidak dikelola yang berjalan pada proses yang sama.

"Resource Collector" akan dapat memverifikasi apakah pegangan / penunjuk sedang digunakan dalam ruang yang dikelola atau tidak, tetapi menurut definisi tidak mengetahui apa yang terjadi di luar ruang memori itu (dan, untuk membuat keadaan lebih buruk beberapa pegangan dapat digunakan melintasi batas proses).

Contoh praktis adalah .NET CLR. Seseorang dapat menggunakan C ++ yang diberi aroma untuk menulis kode yang bekerja dengan ruang memori yang dikelola dan tidak dikelola; pegangan, penunjuk dan referensi dapat diedarkan antara kode yang dikelola dan tidak dikelola. Kode yang tidak dikelola harus menggunakan konstruksi / tipe khusus untuk memungkinkan CLR untuk terus melacak referensi yang dibuat untuk sumber daya yang dikelola itu. Tapi itu yang terbaik yang bisa dilakukan. Ia tidak dapat melakukan hal yang sama dengan handle dan pointer, dan karena itu Resource Collector tidak akan tahu apakah boleh melepaskan handel atau pointer tertentu.

sunting: Mengenai. NET CLR, saya tidak berpengalaman dengan pengembangan C ++ dengan platform .NET. Mungkin ada mekanisme khusus di tempat yang memungkinkan CLR untuk terus melacak referensi untuk menangani / petunjuk antara kode yang dikelola dan tidak dikelola. Jika itu yang terjadi maka CLR dapat mengurus masa pakai sumber daya tersebut dan melepaskannya ketika semua referensi tentangnya telah dihapus (yah, setidaknya dalam beberapa skenario hal itu bisa). Either way, praktik terbaik menentukan yang menangani (terutama yang menunjuk ke file) dan pointer harus dirilis segera setelah mereka tidak diperlukan. Seorang Resource Collector tidak akan menaatinya, itu alasan lain untuk tidak memilikinya.

sunting 2: Ini relatif sepele pada CLR / JVM / VMs-in-general untuk menulis beberapa kode untuk membebaskan pegangan tertentu jika hanya digunakan di dalam ruang yang dikelola. Dalam. NET akan menjadi sesuatu seperti:

// This class offends many best practices, but it would do the job.
public class AutoReleaseFileHandle {
    // keeps track of how many instances of this class is in memory
    private static int _toBeReleased = 0;

    // the threshold when a garbage collection should be forced
    private const int MAX_FILES = 100;

    public AutoReleaseFileHandle(FileStream fileStream) {
       // Force garbage collection if max files are reached.
       if (_toBeReleased >= MAX_FILES) {
          GC.Collect();
       }
       // increment counter
       Interlocked.Increment(ref _toBeReleased);
       FileStream = fileStream;
    }

    public FileStream { get; private set; }

    private void ReleaseFileStream(FileStream fs) {
       // decrement counter
       Interlocked.Decrement(ref _toBeReleased);
       FileStream.Close();
       FileStream.Dispose();
       FileStream = null;
    }

    // Close and Dispose the Stream when this class is collected by the GC.
    ~AutoReleaseFileHandle() {
       ReleaseFileStream(FileStream);
    }

    // because it's .NET this class should also implement IDisposable
    // to allow the user to dispose the resources imperatively if s/he wants 
    // to.
    private bool _disposed = false;
    public void Dispose() {
      if (_disposed) {
        return;
      }
      _disposed = true;
      // tells GC to not call the finalizer for this instance.
      GC.SupressFinalizer(this);

      ReleaseFileStream(FileStream);
    }
}

// use it
// for it to work, fs.Dispose() should not be called directly,
var fs = File.Open("path/to/file"); 
var autoRelease = new AutoReleaseFileHandle(fs);

3

Ini tampaknya menjadi salah satu alasan bahasa dengan pemulung menerapkan finalizer. Finalizers dimaksudkan untuk memungkinkan seorang programmer untuk membersihkan sumber daya suatu objek selama pengumpulan sampah. Masalah besar dengan finalizer adalah bahwa mereka tidak dijamin berjalan.

Ada artikel yang cukup bagus tentang penggunaan finalizer di sini:

Finalisasi dan pembersihan objek

Bahkan secara khusus menggunakan deskriptor file sebagai contoh. Anda harus memastikan untuk membersihkan sendiri sumber daya tersebut, tetapi ada mekanisme yang MUNGKIN mengembalikan sumber daya yang tidak dirilis dengan benar.


Saya tidak yakin apakah ini menjawab pertanyaan saya. Itu kehilangan bagian dari proposal saya di mana sistem tahu bahwa itu akan kehabisan sumber daya. Satu-satunya cara untuk memalu bagian itu adalah untuk memastikan bahwa Anda menjalankan gc secara manual sebelum Anda mengalokasikan file deskriptor baru, tetapi itu sangat tidak efisien, dan saya tidak tahu apakah Anda dapat menyebabkan gc berjalan di java.
mindreader

OK, tetapi deskriptor file biasanya mewakili File Terbuka di sistem operasi yang menyiratkan (tergantung pada OS) menggunakan sumber daya tingkat sistem seperti kunci, kumpulan buffer, kumpulan struktur, dll. Terus terang, saya tidak melihat manfaat dari membiarkan struktur ini terbuka untuk pengumpulan sampah di kemudian hari dan saya melihat banyak kerugian karena membiarkannya dialokasikan lebih lama dari yang diperlukan. Metode Finalize () dimaksudkan untuk memungkinkan pembersihan parit terakhir jika programmer mengabaikan panggilan untuk membersihkan sumber daya, tetapi tidak dapat diandalkan.
Brian Hibbert

Pemahaman saya adalah bahwa alasan mereka tidak dapat diandalkan adalah bahwa jika Anda mengalokasikan satu ton sumber daya ini, seperti mungkin Anda menurunkan hierarki file yang membuka setiap file, Anda dapat membuka terlalu banyak file sebelum gc terjadi lari, menyebabkan ledakan. Hal yang sama akan terjadi dengan memori, kecuali bahwa runtime memeriksa untuk memastikan itu tidak kehabisan memori. Saya ingin tahu mengapa suatu sistem tidak dapat diimplementasikan untuk mendapatkan kembali sumber daya yang sewenang-wenang sebelum meledak, dengan cara yang hampir sama persis seperti memori dilakukan.
mindreader

Suatu sistem BISA dituliskan ke sumber daya GC selain dari memori, tetapi Anda harus melacak jumlah referensi atau memiliki beberapa metode lain untuk menentukan kapan suatu sumber daya tidak lagi digunakan. Anda TIDAK ingin mendelokasi dan merealokasi sumber daya yang masih digunakan. Semua rumah kekacauan dapat terjadi jika utas memiliki file yang terbuka untuk ditulis, OS "merebut kembali" pegangan file dan utas lainnya membuka file yang berbeda untuk menulis menggunakan pegangan yang sama. Dan saya juga masih menyarankan bahwa itu adalah pemborosan sumber daya yang signifikan untuk membiarkan mereka terbuka sampai thread seperti GC berhasil melepaskan mereka.
Brian Hibbert

3

Ada banyak teknik pemrograman untuk membantu mengelola sumber daya semacam ini.

  • Pemrogram C ++ sering menggunakan pola yang disebut Resource Acquisition is Inisialisasi , atau singkatnya RAII. Pola ini memastikan bahwa ketika suatu benda yang memegang sumber daya keluar dari ruang lingkup, itu akan menutup sumber daya yang dipegangnya. Ini membantu ketika masa hidup objek sesuai dengan cakupan tertentu dalam program (misalnya, ketika cocok dengan waktu ketika bingkai tumpukan tertentu hadir pada tumpukan), sehingga sangat membantu untuk objek yang ditunjuk oleh variabel lokal (pointer variabel disimpan di stack), tetapi tidak begitu membantu untuk objek yang ditunjuk oleh pointer yang disimpan di heap.

  • Java, C #, dan banyak bahasa lainnya menyediakan cara untuk menentukan metode yang akan dipanggil ketika sebuah objek tidak lagi hidup dan akan dikumpulkan oleh pengumpul sampah. Lihat, misalnya, finalizer dispose(),, dan lainnya. Idenya adalah bahwa pemrogram dapat menerapkan metode seperti itu sehingga akan secara eksplisit menutup sumber daya sebelum objek dibebaskan oleh pengumpul sampah. Namun, pendekatan ini memiliki beberapa masalah, yang dapat Anda baca di tempat lain; misalnya, pengumpul sampah mungkin tidak mengumpulkan objek sampai jauh lebih lama dari yang Anda inginkan.

  • C # dan bahasa lainnya menyediakan usingkata kunci yang membantu memastikan bahwa sumber daya ditutup setelah tidak lagi diperlukan (jadi Anda jangan lupa untuk menutup file descriptor atau sumber daya lainnya). Ini seringkali lebih baik daripada mengandalkan pengumpul sampah untuk menemukan bahwa objek tidak lagi hidup. Lihat, misalnya, /programming//q/75401/781723 . Istilah umum di sini adalah sumber daya yang dikelola . Gagasan ini dibangun di atas RAII dan penyelesai, meningkatkannya dengan beberapa cara.


Saya kurang tertarik pada alokasi sumber daya yang cepat, dan lebih tertarik pada gagasan alokasi waktu yang tepat. RIAA hebat, tetapi tidak super berlaku untuk bahasa pengumpulan sampah yang sangat banyak. Java kehilangan kemampuan untuk mengetahui kapan akan kehabisan sumber daya tertentu. Operasi menggunakan dan tipe braket berguna dan menangani kesalahan, tapi saya tidak tertarik pada mereka. Saya hanya ingin mengalokasikan sumber daya dan kemudian mereka akan membersihkan diri kapan saja nyaman atau perlu, dan ada sedikit cara untuk mengacaukannya. Saya kira tidak ada yang benar-benar melihat ini.
mindreader

2

Semua memori sama, jika saya meminta 1K, saya tidak peduli dari mana di ruang alamat 1K berasal.

Ketika saya meminta pegangan file, saya ingin pegangan ke file yang ingin saya buka. Memiliki pegangan file yang terbuka pada file, sering memblokir akses ke file oleh proses lain, atau mesin.

Karenanya pegangan file harus ditutup segera setelah tidak diperlukan, jika tidak mereka memblokir akses lain ke file tersebut, tetapi memori hanya perlu direklamasi ketika Anda mulai kehabisan.

Menjalankan pass GC mahal dan hanya dilakukan "bila diperlukan", tidak mungkin untuk memprediksi kapan proses anther akan membutuhkan file menangani bahwa proses Anda mungkin tidak lagi menggunakan, tetapi masih terbuka.


Jawaban Anda mengenai kunci sesungguhnya: memori itu sepadan, dan sebagian besar sistem sudah cukup sehingga tidak perlu direklamasi dengan cepat. Sebaliknya, jika suatu program memperoleh akses eksklusif ke suatu file, itu akan memblokir semua program lain di mana pun di jagat raya yang mungkin perlu menggunakan file itu, tidak peduli berapa banyak file lain yang mungkin ada.
supercat

0

Saya akan menebak alasan mengapa ini belum banyak didekati untuk sumber daya lain adalah karena sebagian besar sumber daya lain lebih disukai untuk dirilis sesegera mungkin bagi siapa saja untuk digunakan kembali.

Catatan, tentu saja, contoh Anda dapat diberikan sekarang menggunakan deskriptor file "lemah" dengan teknik GC yang ada.


0

Untuk memeriksa apakah memori tidak lagi dapat diakses (dan dengan demikian dijamin tidak akan digunakan lagi) agak mudah. Sebagian besar jenis sumber daya lainnya dapat ditangani dengan teknik yang kurang lebih sama (yaitu, akuisisi sumber daya adalah inisialisasi, RAII, dan mitra pembebasannya ketika pengguna dimusnahkan, yang menghubungkannya dengan administrasi memori). Melakukan semacam pembebasan "tepat waktu" tidak mungkin secara umum (periksa masalah penghentian, Anda harus mengetahui bahwa beberapa sumber daya digunakan untuk yang terakhir kalinya). Ya, kadang - kadang dapat dilakukan secara otomatis, tetapi ini merupakan kasus yang jauh lebih berantakan daripada memori. Jadi itu bergantung pada intervensi pengguna 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.