Bagaimana cara kerja Java Garbage Collection dengan Referensi Lingkaran?

161

Dari pemahaman saya, pengumpulan sampah di Jawa membersihkan beberapa objek jika tidak ada yang 'menunjuk' ke objek itu.

Pertanyaan saya adalah, apa yang terjadi jika kita memiliki sesuatu seperti ini:

class Node {
    public object value;
    public Node next;
    public Node(object o, Node n) { value = 0; next = n;}
}

//...some code
{
    Node a = new Node("a", null), 
         b = new Node("b", a), 
         c = new Node("c", b);
    a.next = c;
} //end of scope
//...other code

a,, bdan charus dikumpulkan, tetapi semuanya dirujuk oleh objek lain.

Bagaimana cara pengumpulan sampah Jawa menangani ini? (Atau itu hanya menguras memori?)

AlexeyMK
sumber
1
Lihat: stackoverflow.com/questions/407855/… , khususnya jawaban kedua dari @gnud.
Seth

Jawaban:

161

GC Jawa menganggap objek "sampah" jika tidak dapat dijangkau melalui rantai mulai dari akar pengumpulan sampah, sehingga objek ini akan dikumpulkan. Meskipun objek dapat menunjuk satu sama lain untuk membentuk siklus, mereka tetap sampah jika terpotong dari akar.

Lihat bagian tentang benda-benda yang tidak dapat dijangkau di Lampiran A: Kebenaran Tentang Pengumpulan Sampah di Java Platform Performance: Strategi dan Taktik untuk detail berdarah.

Bill the Lizard
sumber
14
Apakah Anda punya referensi untuk itu? Sulit untuk mengujinya.
tangens
5
Saya menambahkan referensi. Anda juga bisa mengganti metode finalize () objek untuk mencari tahu kapan itu dikumpulkan (walaupun itu satu-satunya hal yang saya sarankan menggunakan finalize () for).
Bill the Lizard
1
Hanya untuk memperjelas komentar terakhir ... masukkan pernyataan cetak debug dalam metode final yang mencetak id unik untuk objek. Anda akan dapat melihat semua objek yang saling referensi dikumpulkan.
Bill the Lizard
4
"... cukup pintar untuk mengenali ..." terdengar membingungkan. GC tidak harus mengenali siklus - mereka hanya tidak terjangkau, karenanya sampah
Alexander Malakhov
86
@tensens "Apakah Anda punya referensi untuk itu?" dalam diskusi tentang pengumpulan sampah. Terbaik. Permainan kata-kata. Pernah.
Michał Kosmulski
139

ya pengumpul Sampah Jawa menangani referensi melingkar!

How?

Ada benda-benda khusus yang disebut akar pengumpulan sampah (GC root). Ini selalu dapat dijangkau dan begitu pula objek apa pun yang memiliki mereka pada akarnya sendiri.

Aplikasi Java sederhana memiliki akar GC berikut:

  1. Variabel lokal dalam metode utama
  2. Utas utama
  3. Variabel statis dari kelas utama

masukkan deskripsi gambar di sini

Untuk menentukan objek mana yang tidak lagi digunakan, JVM sebentar-sebentar menjalankan apa yang sangat tepat disebut algoritma mark-and-sweep . Ini berfungsi sebagai berikut

  1. Algoritme melintasi semua referensi objek, dimulai dengan akar GC, dan menandai setiap objek yang ditemukan hidup.
  2. Semua memori tumpukan yang tidak ditempati oleh objek yang ditandai direklamasi. Itu hanya ditandai sebagai bebas, pada dasarnya disapu bebas dari benda yang tidak digunakan.

Jadi, jika ada objek yang tidak dapat dijangkau dari akar GC (bahkan jika itu direferensikan sendiri atau dirujuk siklik) itu akan dikenakan pengumpulan sampah.

Ofcourse kadang-kadang hal ini dapat menyebabkan kebocoran memori jika programmer lupa untuk merujuk objek.

masukkan deskripsi gambar di sini

Sumber: Manajemen Memori Java

Aniket Thakur
sumber
3
Penjelasan sempurna! Terima kasih! :)
Jovan Perovic
Terima kasih telah menautkan buku itu. Ini penuh dengan info hebat tentang ini dan topik pengembangan Java lainnya!
Droj
14
Pada gambar terakhir, ada objek yang tidak dapat dijangkau tetapi di bagian objek yang dapat dijangkau.
La VloZ Merrill
13

Seorang pengumpul sampah dimulai dari beberapa tempat "root" yang selalu dianggap "dapat dijangkau", seperti register CPU, stack, dan variabel global. Ini bekerja dengan menemukan petunjuk di area tersebut, dan secara rekursif menemukan semua yang mereka tuju. Setelah itu ditemukan semua itu, segala sesuatu yang lain adalah sampah.

Tentu saja ada beberapa variasi, kebanyakan demi kecepatan. Sebagai contoh, sebagian besar pengumpul sampah modern adalah "generasi", yang berarti bahwa mereka membagi benda menjadi beberapa generasi, dan seiring bertambahnya usia, pengumpul sampah berjalan lebih lama dan lebih lama antara waktu yang mencoba untuk mencari tahu apakah benda itu masih valid atau tidak - itu hanya mulai berasumsi bahwa jika ia telah hidup lama, kemungkinannya cukup bagus bahwa ia akan terus hidup lebih lama lagi.

Meskipun demikian, ide dasarnya tetap sama: semuanya didasarkan pada mulai dari beberapa set root hal-hal yang diperlukan begitu saja masih dapat digunakan, dan kemudian mengejar semua petunjuk untuk menemukan apa lagi yang bisa digunakan.

Di samping menarik: semoga orang sering terkejut dengan tingkat kesamaan antara bagian pengumpul sampah dan kode untuk mengatur objek untuk hal-hal seperti panggilan prosedur jarak jauh. Dalam setiap kasus, Anda mulai dari beberapa root set objek, dan mengejar pointer untuk menemukan semua objek lain yang merujuk ...

Jerry Coffin
sumber
Apa yang Anda gambarkan adalah seorang tracing collector. Ada beberapa jenis kolektor lainnya. Yang menarik untuk diskusi ini adalah referensi menghitung kolektor, yang tidak cenderung memiliki masalah dengan siklus.
Jörg W Mittag
@ Jörg W Mittag: Tentu saja benar - walaupun saya tidak tahu tentang JVM (saat ini cukup) yang menggunakan penghitungan referensi, jadi sepertinya tidak mungkin (setidaknya bagi saya) bahwa itu membuat banyak perbedaan dengan pertanyaan awal.
Jerry Coffin
@ Jörg W Mittag: Setidaknya secara default, saya yakin Jikes RVM saat ini menggunakan kolektor Immix, yang merupakan kolektor penelusuran berbasis kawasan (meskipun juga menggunakan penghitungan referensi). Saya tidak yakin apakah Anda merujuk pada penghitungan referensi itu, atau kolektor lain yang menggunakan penghitungan referensi tanpa melacak (saya rasa yang terakhir, karena saya belum pernah mendengar Immix memanggil "pendaur ulang").
Jerry Coffin
Saya terlibat sedikit: Recycler diimplementasikan di Jalapeno, algoritma yang saya pikirkan, yang diimplementasikan di Jikes adalah Menghitung Referensi Tersembunyi . Lagipula, tentu saja, mengatakan bahwa Jikes menggunakan ini atau itu pengumpul sampah cukup sia-sia, mengingat bahwa Jikes dan terutama MMtk secara khusus dirancang untuk secara cepat mengembangkan dan menguji pengumpul sampah yang berbeda dalam JVM yang sama.
Jörg W Mittag
2
Penghitungan Referensi Ulterior dirancang pada tahun 2003 oleh orang yang sama yang merancang Immix pada tahun 2007, jadi saya kira yang terakhir mungkin menggantikan yang pertama. URC dirancang khusus sehingga dapat digabungkan dengan strategi lain, dan pada kenyataannya makalah URC secara eksplisit menyebutkan bahwa URC hanyalah batu loncatan menuju seorang kolektor yang menggabungkan keunggulan penelusuran dan penghitungan referensi. Saya kira Immix adalah kolektor itu. Bagaimanapun, Recycler adalah kolektor penghitungan referensi murni , yang tetap dapat mendeteksi dan mengumpulkan siklus: WWW.Research.IBM.Com/people/d/dfb/recycler.html
Jörg W Mittag
13

Anda benar. Bentuk spesifik pengumpulan sampah yang Anda gambarkan disebut " penghitungan referensi ". Cara kerjanya (secara konseptual, setidaknya, sebagian besar implementasi modern penghitungan referensi sebenarnya diimplementasikan dengan sangat berbeda) dalam kasus yang paling sederhana, terlihat seperti ini:

  • setiap kali referensi ke objek ditambahkan (misalnya ditugaskan ke variabel atau bidang, diteruskan ke metode, dan sebagainya), jumlah referensi meningkat sebesar 1
  • setiap kali referensi ke suatu objek dihapus (metode kembali, variabel keluar dari lingkup, bidang ditugaskan kembali ke objek yang berbeda atau objek yang berisi bidang itu sendiri mengumpulkan sampah), jumlah referensi berkurang 1
  • segera setelah jumlah referensi mencapai 0, tidak ada lagi referensi ke objek, yang berarti tidak ada yang dapat menggunakannya lagi, oleh karena itu adalah sampah dan dapat dikumpulkan

Dan strategi sederhana ini memiliki masalah yang Anda jelaskan: jika A referensi B dan B referensi A, maka kedua jumlah referensi mereka tidak akan pernah kurang dari 1, yang berarti mereka tidak akan pernah dikumpulkan.

Ada empat cara untuk mengatasi masalah ini:

  1. Abaikan itu. Jika Anda memiliki cukup memori, siklus Anda kecil dan jarang dan runtime Anda pendek, mungkin Anda bisa lolos dengan tidak mengumpulkan siklus. Pikirkan juru bahasa skrip shell: skrip shell biasanya hanya berjalan selama beberapa detik dan tidak mengalokasikan banyak memori.
  2. Gabungkan referensi Anda menghitung pengumpul sampah dengan pengumpul sampah lain yang tidak memiliki masalah dengan siklus. CPython melakukan ini, misalnya: pengumpul sampah utama di CPython adalah pengumpul penghitungan referensi, tetapi dari waktu ke waktu pengumpul sampah penelusuran dijalankan untuk mengumpulkan siklus.
  3. Deteksi siklusnya. Sayangnya, mendeteksi siklus dalam grafik adalah operasi yang agak mahal. Secara khusus, ini membutuhkan biaya overhead yang hampir sama dengan kolektor pelacak, jadi Anda bisa menggunakan salah satunya.
  4. Jangan menerapkan algoritme dengan cara naif seperti Anda dan saya: sejak tahun 1970-an, ada beberapa algoritma yang cukup menarik yang dikembangkan yang menggabungkan deteksi siklus dan penghitungan referensi dalam satu operasi dengan cara yang pintar yang secara signifikan lebih murah daripada melakukannya baik secara terpisah atau melakukan tracing collector.

Ngomong-ngomong, cara utama lainnya untuk mengimplementasikan pengumpul sampah (dan saya sudah mengisyaratkan bahwa beberapa kali di atas), sedang melacak . Seorang kolektor tracing didasarkan pada konsep reachability . Anda mulai dengan beberapa set root yang Anda tahu selalu dapat dijangkau (konstanta global, misalnya, atau Objectkelas, ruang lingkup leksikal saat ini, bingkai stack saat ini) dan dari sana Anda melacak semua objek yang dapat dijangkau dari set root, kemudian semua objek yang dapat dijangkau dari objek yang dapat dijangkau dari root set dan seterusnya, sampai Anda memiliki penutupan transitif. Segala sesuatu yang tidak ada dalam penutupan itu adalah sampah.

Karena sebuah siklus hanya dapat dicapai dalam dirinya sendiri, tetapi tidak dapat dicapai dari set root, itu akan dikumpulkan.

Jörg W Mittag
sumber
1
Karena pertanyaan khusus untuk Java, saya pikir perlu disebutkan bahwa Java tidak menggunakan penghitungan referensi dan karenanya masalah tidak ada. Juga tautan ke wikipedia akan bermanfaat sebagai "bacaan lebih lanjut". Jika tidak, ikhtisar hebat!
Alexander Malakhov
Saya baru saja membaca komentar Anda di pos Jerry Coffin, jadi sekarang saya tidak yakin :)
Alexander Malakhov
8

Java GCs sebenarnya tidak berperilaku seperti yang Anda gambarkan. Lebih akurat untuk mengatakan bahwa mereka mulai dari satu set objek dasar, sering disebut "akar GC", dan akan mengumpulkan objek apa pun yang tidak dapat dijangkau dari root.
Akar GC mencakup hal-hal seperti:

  • variabel statis
  • variabel lokal (termasuk semua referensi 'ini' yang berlaku) saat ini di tumpukan thread yang sedang berjalan

Jadi, dalam kasus Anda, setelah variabel lokal a, b, dan c keluar dari ruang lingkup di akhir metode Anda, tidak ada lagi akar GC yang berisi, secara langsung atau tidak langsung, referensi ke salah satu dari tiga node Anda, dan mereka akan memenuhi syarat untuk pengumpulan sampah.

Tautan TofuBeer memiliki detail lebih banyak jika Anda menginginkannya.

Sbodd
sumber
"... saat ini di tumpukan thread yang sedang berjalan ..." bukankah itu memindai tumpukan semua utas agar tidak merusak data utas lainnya?
Alexander Malakhov
6

Artikel ini (tidak lagi tersedia) membahas tentang pengumpul sampah (secara konseptual ... ada beberapa implementasi). Bagian yang relevan dengan posting Anda adalah "A.3.4 Tidak Dapat Dicapai":

A.3.4 Tidak Terjangkau Suatu objek memasuki kondisi tidak dapat dijangkau ketika tidak ada referensi yang lebih kuat untuk itu ada. Ketika sebuah objek tidak dapat dijangkau, itu adalah kandidat untuk koleksi. Perhatikan kata-katanya: Hanya karena suatu objek adalah kandidat untuk koleksi tidak berarti itu akan segera dikumpulkan. JVM bebas untuk menunda pengumpulan sampai ada kebutuhan mendesak untuk memori yang dikonsumsi oleh objek.

TofuBeer
sumber
1
tautan langsung ke bagian itu
Alexander Malakhov
1
tautannya
1

Pengumpulan sampah biasanya tidak berarti "membersihkan beberapa objek jika tidak ada yang lain 'menunjuk' ke objek itu" (itu penghitungan referensi). Pengumpulan sampah secara kasar berarti menemukan benda yang tidak dapat dijangkau dari program.

Jadi, dalam contoh Anda, setelah a, b, dan c keluar dari ruang lingkup, mereka dapat dikumpulkan oleh GC, karena Anda tidak dapat mengakses objek ini lagi.

Amnon
sumber
"Pengumpulan sampah secara kasar berarti menemukan benda yang tidak dapat dijangkau dari program". Dalam kebanyakan algoritma GC itu sebenarnya sebaliknya. Anda mulai dengan akar GC dan melihat apa yang dapat Anda temukan, sisanya dianggap sampah yang tidak direferensikan.
Fredrik
1
Penghitungan referensi adalah salah satu dari dua strategi implementasi utama untuk pengumpulan sampah. (Yang lainnya sedang melacak.)
Jörg W Mittag
3
@ Jörg: Sebagian besar hari ini, ketika orang berbicara tentang pemulung, mereka merujuk pada pengumpul berdasarkan beberapa jenis algoritma mark'n'sweep. Penghitungan ref biasanya tergantung pada Anda jika Anda tidak memiliki pengumpul sampah. Memang benar bahwa penghitungan ref dalam arti strategi pengumpulan sampah tetapi hampir tidak ada gc yang ada saat ini yang dibangun di atasnya sehingga mengatakan bahwa itu adalah strategi gc hanya akan membingungkan orang karena dalam praktiknya tidak lagi menjadi gc strategi tetapi cara alternatif untuk mengelola memori.
Fredrik
1

Bill menjawab pertanyaan Anda secara langsung. Seperti yang dikatakan Amnon, definisi Anda tentang pengumpulan sampah hanyalah penghitungan referensi. Saya hanya ingin menambahkan bahwa bahkan algoritma yang sangat sederhana seperti mark dan sweep serta koleksi salin dengan mudah menangani referensi melingkar. Jadi, tidak ada yang ajaib tentang itu!

Claudiu
sumber