Bagaimana cara membuat kebocoran memori di Java?

3223

Saya baru saja wawancara, dan saya diminta membuat kebocoran memori dengan Java.
Tak perlu dikatakan, saya merasa sangat bodoh karena tidak memiliki petunjuk tentang cara bahkan mulai membuat satu.

Apa yang akan menjadi contoh?

Mat B.
sumber
275
Aku akan memberitahu mereka bahwa Java menggunakan kolektor sampah, dan meminta mereka untuk menjadi sedikit lebih spesifik tentang definisi mereka "kebocoran memori", menjelaskan bahwa - pembatasan JVM bug - Java tidak bisa bocor memori cukup dengan cara yang sama C / C ++ bisa. Anda harus memiliki referensi ke objek di suatu tempat .
Darien
371
Saya merasa lucu bahwa pada sebagian besar jawaban orang mencari kasus tepi dan trik dan tampaknya benar-benar kehilangan intinya (IMO). Mereka hanya bisa menunjukkan kode yang menyimpan referensi tidak berguna ke objek yang tidak akan pernah digunakan lagi, dan pada saat yang sama tidak pernah menjatuhkan referensi tersebut; bisa dikatakan kasus-kasus itu bukan "benar" kebocoran memori karena masih ada referensi ke objek-objek di sekitar, tetapi jika program tidak pernah menggunakan referensi itu lagi dan juga tidak pernah menjatuhkannya, itu benar-benar setara dengan (dan seburuk) a " kebocoran memori ".
ehabkost
62
Jujur saya tidak percaya pertanyaan serupa yang saya tanyakan tentang "Go" diturunkan ke -1. Di sini: stackoverflow.com/questions/4400311/... Pada dasarnya kebocoran memori yang saya bicarakan adalah orang-orang yang mendapat +200 upvotes ke OP, namun saya diserang dan dihina karena bertanya apakah "Go" memiliki masalah yang sama. Entah bagaimana, saya tidak yakin semua hal wiki bekerja dengan baik.
SyntaxT3rr0r
74
@ SyntaxT3rr0r - jawaban darien bukan fanboyisme. dia secara eksplisit mengakui bahwa JVM tertentu dapat memiliki bug yang berarti memori bocor. ini berbeda dari spesifikasi bahasa itu sendiri yang memungkinkan kebocoran memori.
Peter Recore
31
@ehabkost: Tidak, mereka tidak setara. (1) Anda memiliki kemampuan untuk mendapatkan kembali memori, sedangkan dalam "kebocoran sebenarnya" program C / C ++ Anda lupa rentang yang dialokasikan, tidak ada cara aman untuk memulihkan. (2) Anda dapat dengan mudah mendeteksi masalah dengan pembuatan profil, karena Anda dapat melihat objek apa yang termasuk "mengasapi". (3) "kebocoran yang sebenarnya" adalah kesalahan nyata, sementara program yang menyimpan banyak objek sampai dihentikan dapat menjadi bagian yang disengaja dari bagaimana itu dimaksudkan untuk bekerja.
Darien

Jawaban:

2309

Berikut adalah cara yang baik untuk membuat kebocoran kehabisan memori (objek tidak dapat diakses dengan menjalankan kode tetapi masih tersimpan dalam memori) di Java murni:

  1. Aplikasi ini membuat utas yang sudah berjalan lama (atau menggunakan kumpulan utas untuk bocor lebih cepat).
  2. Utas memuat kelas melalui (kustom opsional) ClassLoader.
  3. Kelas mengalokasikan sejumlah besar memori (misalnya new byte[1000000]), menyimpan referensi yang kuat untuk itu di bidang statis, dan kemudian menyimpan referensi untuk dirinya sendiri di a ThreadLocal. Mengalokasikan memori tambahan adalah opsional (membocorkan instance kelas sudah cukup), tetapi itu akan membuat kebocoran bekerja lebih cepat.
  4. Aplikasi menghapus semua referensi ke kelas kustom atau dari ClassLoadermana itu diambil.
  5. Ulang.

Karena cara ThreadLocalini diterapkan di JDK Oracle, ini menciptakan kebocoran memori:

  • Masing-masing Threadmemiliki bidang pribadi threadLocals, yang sebenarnya menyimpan nilai-nilai lokal thread.
  • Setiap kunci dalam peta ini adalah referensi yang lemah ke suatu ThreadLocalobjek, jadi setelah ThreadLocalobjek tersebut dikumpulkan dari sampah, entri tersebut dihapus dari peta.
  • Tetapi setiap nilai adalah referensi yang kuat, jadi ketika nilai (langsung atau tidak langsung) menunjuk ke ThreadLocalobjek yang merupakan kuncinya , objek tersebut tidak akan dikumpulkan atau dibuang dari peta selama utasnya hidup.

Dalam contoh ini, rantai referensi kuat terlihat seperti ini:

Threadobjek → threadLocalspeta → contoh kelas contoh → kelas contoh → ThreadLocalbidang statis → ThreadLocalobjek.

(The ClassLoadertidak benar-benar berperan dalam menciptakan kebocoran, itu hanya membuat kebocoran lebih buruk karena rantai referensi tambahan ini: contoh kelas → ClassLoader→ semua kelas yang telah dimuat. Itu bahkan lebih buruk di banyak implementasi JVM, terutama sebelum Java 7, karena kelas dan kelas ClassLoaderdialokasikan langsung ke permgen dan tidak pernah dikumpulkan sama sekali.)

Variasi pada pola ini adalah mengapa wadah aplikasi (seperti Tomcat) dapat membocorkan memori seperti saringan jika Anda sering menggunakan kembali aplikasi yang kebetulan menggunakan ThreadLocals yang dengan cara tertentu kembali ke diri mereka sendiri. Ini dapat terjadi karena sejumlah alasan halus dan seringkali sulit untuk di-debug dan / atau diperbaiki.

Pembaruan : Karena banyak orang terus memintanya, inilah beberapa contoh kode yang menunjukkan perilaku ini dalam tindakan .

Daniel Pryden
sumber
186
Kebocoran ClassLoader +1 adalah beberapa kebocoran memori yang paling umum menyakitkan di dunia JEE, sering disebabkan oleh lib pihak ke-3 yang mengubah data (BeanUtils, XML / JSON codecs). Ini bisa terjadi ketika lib dimuat di luar classloader root aplikasi Anda tetapi memegang referensi ke kelas Anda (mis. Dengan caching). Ketika Anda menganggur / memindahkan aplikasi Anda, JVM tidak dapat membuang sampah mengumpulkan classloader aplikasi (dan karena itu semua kelas dimuat olehnya), jadi dengan pengulangan menyebarkan server aplikasi akhirnya borks. Jika beruntung Anda mendapatkan petunjuk dengan ClassCastException zxyAbc tidak dapat dilemparkan ke zxyAbc
earcam
7
tomcat menggunakan trik dan nils SEMUA variabel statis di SEMUA kelas yang dimuat, tomcat memiliki banyak kode datar dan pengkodean yang buruk (perlu mendapatkan waktu dan mengirimkan perbaikan), ditambah semua ConcurrentLinkedQueue yang mencengangkan sebagai cache untuk objek internal (kecil), begitu kecil sehingga ConcurrentLinkedQueue.Node membutuhkan lebih banyak memori.
bestsss
57
+1: Kebocoran Classloader adalah mimpi buruk. Saya menghabiskan berminggu-minggu mencoba mencari tahu mereka. Yang menyedihkan adalah, seperti yang dikatakan @earcam, mereka sebagian besar disebabkan oleh lib pihak ke-3 dan juga sebagian besar profiler tidak dapat mendeteksi kebocoran ini. Ada penjelasan yang bagus dan jelas di blog ini tentang kebocoran Classloader. blogs.oracle.com/fkieviet/entry/…
Adrian M
4
@ Nicolas: Apakah Anda yakin? JRockit melakukan objek Kelas GC secara default, dan HotSpot tidak, tetapi AFAIK JRockit masih tidak bisa GC Kelas atau ClassLoader yang direferensikan oleh ThreadLocal.
Daniel Pryden
6
Tomcat akan mencoba mendeteksi kebocoran ini untuk Anda, dan memperingatkannya: wiki.apache.org/tomcat/MemoryLeakProtection . Versi terbaru kadang-kadang bahkan akan memperbaiki kebocoran untuk Anda.
Matthijs Bierman
1212

Referensi objek memegang bidang statis [esp bidang terakhir]

class MemorableClass {
    static final ArrayList list = new ArrayList(100);
}

Memanggil String.intern()String yang panjang

String str=readString(); // read lengthy string any source db,textbox/jsp etc..
// This will place the string in memory pool from which you can't remove
str.intern();

(Tidak tertutup) aliran terbuka (file, jaringan dll ...)

try {
    BufferedReader br = new BufferedReader(new FileReader(inputFile));
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Koneksi tidak tertutup

try {
    Connection conn = ConnectionFactory.getConnection();
    ...
    ...
} catch (Exception e) {
    e.printStacktrace();
}

Area yang tidak dapat dijangkau dari pengumpul sampah JVM , seperti memori yang dialokasikan melalui metode asli

Dalam aplikasi web, beberapa objek disimpan dalam ruang lingkup aplikasi hingga aplikasi secara eksplisit dihentikan atau dihapus.

getServletContext().setAttribute("SOME_MAP", map);

Opsi JVM salah atau tidak tepat , seperti noclassgcopsi pada IBM JDK yang mencegah pengumpulan sampah kelas yang tidak digunakan

Lihat pengaturan IBM jdk .

Prashant Bhate
sumber
178
Saya tidak setuju bahwa atribut konteks dan sesi "bocor". Mereka hanya variabel berumur panjang. Dan bidang final statis kurang lebih hanya sebuah konstanta. Mungkin konstanta besar harus dihindari, tetapi saya pikir itu tidak adil menyebutnya kebocoran memori.
Ian McLaird
80
(Tidak tertutup) stream terbuka (file, jaringan dll ...) , tidak bocor nyata, selama finalisasi (yang akan setelah siklus GC berikutnya) close () akan dijadwalkan ( close()biasanya tidak dipanggil dalam finalizer utas karena mungkin operasi pemblokiran). Ini adalah praktik buruk untuk tidak menutup, tetapi tidak menyebabkan kebocoran. Java.sql.Connection tertutup adalah sama.
bestsss
33
Di sebagian besar JVM yang waras, tampaknya seolah-olah kelas String hanya memiliki referensi lemah pada internkonten hashtable -nya . Dengan demikian, ini adalah sampah yang dikumpulkan dengan benar dan bukan kebocoran. (but IANAJP) mindprod.com/jgloss/interned.html#GC
Matt B.
42
Bagaimana bidang statis memegang referensi objek [esp bidang terakhir] adalah kebocoran memori ??
Kanagavelu Sugumar
5
@ cHao Benar. Bahaya yang saya alami bukanlah masalah dari memori yang dibocorkan oleh Streams. Masalahnya adalah bahwa tidak cukup memori yang bocor oleh mereka. Anda dapat membocorkan banyak pegangan, tetapi masih memiliki banyak memori. Pengumpul Sampah mungkin kemudian memutuskan untuk tidak repot melakukan pengumpulan penuh karena masih memiliki banyak memori. Ini berarti finalizer tidak dipanggil, jadi Anda kehabisan pegangan. Masalahnya adalah, para finalis akan (biasanya) dijalankan sebelum Anda kehabisan memori karena bocornya arus, tetapi mungkin tidak dipanggil sebelum Anda kehabisan sesuatu selain memori.
Patrick M
460

Hal sederhana yang harus dilakukan adalah menggunakan HashSet dengan yang salah (atau tidak ada) hashCode()atau equals(), dan kemudian terus menambahkan "duplikat". Alih-alih mengabaikan duplikat sebagaimana mestinya, set hanya akan pernah tumbuh dan Anda tidak akan dapat menghapusnya.

Jika Anda ingin kunci / elemen buruk ini berkeliaran Anda dapat menggunakan bidang statis seperti

class BadKey {
   // no hashCode or equals();
   public final String key;
   public BadKey(String key) { this.key = key; }
}

Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
Peter Lawrey
sumber
68
Sebenarnya, Anda dapat menghapus elemen dari HashSet bahkan jika kelas elemen mendapat kode hash dan sama dengan yang salah; hanya mendapatkan iterator untuk set dan menggunakan metode hapusnya, karena iterator sebenarnya beroperasi pada entri yang mendasarinya sendiri dan bukan elemen. (Perhatikan bahwa kode hash yang diimplementasikan tidak sama dengan tidak cukup untuk memicu kebocoran; default menerapkan identitas objek sederhana dan sehingga Anda bisa mendapatkan elemen dan menghapusnya secara normal.)
Donal Fellows
12
@Donal apa yang ingin saya katakan, saya kira, adalah saya tidak setuju dengan definisi Anda tentang kebocoran memori. Saya akan mempertimbangkan (untuk melanjutkan analogi) teknik iterator-removal Anda menjadi panci-tetes di bawah kebocoran; kebocoran masih ada terlepas dari panci tetesan.
corsiKa
94
Saya setuju, ini bukan memori "kebocoran", karena Anda bisa menghapus referensi ke hashset dan menunggu GC untuk menendang, dan presto! memori kembali.
user541686
12
@ SyntaxT3rr0r, saya menafsirkan pertanyaan Anda sebagai menanyakan apakah ada sesuatu dalam bahasa yang secara alami mengarah ke kebocoran memori. Jawabannya adalah tidak. Pertanyaan ini menanyakan apakah mungkin untuk membuat situasi untuk membuat sesuatu seperti kebocoran memori. Tak satu pun dari contoh-contoh ini adalah kebocoran memori dengan cara yang bisa dipahami oleh programmer C / C ++.
Peter Lawrey
11
@Peter Lawrey: juga, apa pendapat Anda tentang ini: "Tidak ada apa-apa dalam bahasa C yang secara alami bocor ke kebocoran memori jika Anda tidak lupa untuk secara manual membebaskan memori yang Anda alokasikan" . Bagaimana itu untuk ketidakjujuran intelektual? Bagaimanapun, saya lelah: Anda dapat memiliki kata terakhir.
SyntaxT3rr0r
271

Di bawah ini akan ada kasus yang tidak jelas di mana Java bocor, di samping kasus standar pendengar yang dilupakan, referensi statis, kunci palsu / yang dapat dimodifikasi dalam hashmaps, atau hanya utas yang macet tanpa ada kesempatan untuk mengakhiri siklus hidup mereka.

  • File.deleteOnExit() - selalu membocorkan string, jika string adalah substring, kebocoran lebih buruk (char yang mendasarinya juga bocor)- di Java 7 substring juga menyalin char[], jadi nanti tidak berlaku ; @Daniel, tidak perlu suara, meskipun.

Saya akan berkonsentrasi pada utas untuk menunjukkan bahaya utas yang sebagian besar tidak dikelola, bahkan tidak ingin menyentuh ayunan.

  • Runtime.addShutdownHookdan tidak menghapus ... dan kemudian bahkan dengan removeShutdownHook karena bug di kelas ThreadGroup tentang utas yang belum dimulai itu mungkin tidak dikumpulkan, secara efektif bocor ThreadGroup. JGroup mengalami kebocoran di GossipRouter.

  • Menciptakan, tetapi tidak memulai, a Threadmasuk ke kategori yang sama seperti di atas.

  • Membuat utas mewarisi ContextClassLoaderdan AccessControlContext, ditambah dengan ThreadGroupdan apa saja InheritedThreadLocal, semua referensi tersebut adalah potensi kebocoran, bersama dengan seluruh kelas yang dimuat oleh classloader dan semua referensi statis, dan ja-ja. Efeknya terutama terlihat dengan seluruh kerangka kerja jucExecutor yang menampilkan ThreadFactoryantarmuka super sederhana , namun sebagian besar pengembang tidak memiliki petunjuk tentang bahaya yang mengintai. Juga banyak perpustakaan yang memulai utas atas permintaan (terlalu banyak perpustakaan populer industri).

  • ThreadLocalcache; mereka jahat dalam banyak kasus. Saya yakin semua orang telah melihat sedikit cache sederhana berdasarkan ThreadLocal, nah berita buruknya: jika utas terus berjalan lebih dari yang diharapkan dalam konteks ClassLoader, itu adalah kebocoran kecil murni yang bagus. Jangan gunakan cache ThreadLocal kecuali benar-benar diperlukan.

  • Memanggil ThreadGroup.destroy()ketika ThreadGroup tidak memiliki utas itu sendiri, tetapi ThreadGroup masih tetap anak. Kebocoran buruk yang akan mencegah ThreadGroup untuk menghapus dari orang tuanya, tetapi semua anak menjadi tidak dapat disebutkan.

  • Menggunakan WeakHashMap dan nilai (dalam) langsung merujuk kunci. Ini yang sulit ditemukan tanpa tumpukan sampah. Itu berlaku untuk semua perluasan Weak/SoftReferenceyang mungkin menyimpan referensi keras kembali ke objek yang dijaga.

  • Menggunakan java.net.URLdengan protokol HTTP (S) dan memuat sumber daya dari (!). Yang ini spesial, KeepAliveCachemembuat utas baru di sistem ThreadGroup yang bocor classloader konteks utas saat ini. Utas dibuat berdasarkan permintaan pertama saat tidak ada utas aktif, jadi Anda bisa beruntung atau bocor. Kebocoran sudah diperbaiki di Java 7 dan kode yang membuat utas dengan benar menghapus konteks classloader. Ada beberapa kasus lagi (seperti ImageFetcher, juga diperbaiki ) untuk membuat utas serupa.

  • Menggunakan InflaterInputStreampassing new java.util.zip.Inflater()dalam konstruktor ( PNGImageDecodermisalnya) dan tidak memanggil end()inflater. Nah, jika Anda meneruskan konstruktor dengan adil new, tidak ada kesempatan ... Dan ya, memanggil close()aliran tidak menutup inflater jika dilewatkan secara manual sebagai parameter konstruktor. Ini bukan kebocoran sebenarnya karena akan dirilis oleh finalizer ... ketika itu dianggap perlu. Sampai saat itu memakan memori asli sehingga buruk dapat menyebabkan Linux oom_killer untuk membunuh proses dengan impunitas. Masalah utama adalah bahwa finalisasi di Jawa sangat tidak dapat diandalkan dan G1 memperburuknya hingga 7.0.2. Moral dari cerita: lepaskan sumber daya asli sesegera mungkin; finalizer terlalu miskin.

  • Kasus yang sama dengan java.util.zip.Deflater. Yang satu ini jauh lebih buruk karena Deflater kehabisan memori di Jawa, yaitu selalu menggunakan 15 bit (maks) dan 8 level memori (9 maks) yang mengalokasikan beberapa ratus KB memori asli. Untungnya, Deflatertidak banyak digunakan dan setahu saya JDK tidak mengandung penyalahgunaan. Selalu panggil end()jika Anda secara manual membuat a Deflateratau Inflater. Bagian terbaik dari dua yang terakhir: Anda tidak dapat menemukannya melalui alat profil normal yang tersedia.

(Saya dapat menambahkan lebih banyak pemboros waktu yang saya temui atas permintaan.)

Semoga beruntung dan tetap jaga keselamatan; kebocoran itu jahat!

bestsss
sumber
23
Creating but not starting a Thread...Astaga, saya digigit oleh yang satu ini beberapa abad yang lalu! (Jawa 1.3)
leonbloy
@leonbloy, sebelum itu bahkan lebih buruk karena utas ditambahkan langsung ke utas grup, tidak memulai berarti kebocoran yang sangat keras. Bukan hanya meningkatkan unstartedjumlah tetapi itu mencegah kelompok utas dari menghancurkan (kejahatan yang lebih rendah tetapi masih bocor)
terlaris
Terima kasih! "Memanggil ThreadGroup.destroy()ketika ThreadGroup tidak memiliki utas sendiri ..." adalah bug yang sangat halus; Saya sudah mengejar ini selama berjam-jam, tersesat karena menghitung utas dalam kendali saya GUI tidak menunjukkan apa-apa, tetapi kelompok utas dan, mungkin, setidaknya satu kelompok anak tidak akan pergi.
Lawrence Dol
1
@bestsss: Saya ingin tahu, mengapa Anda ingin menghapus hook shutdown, mengingat itu berjalan pada, yah, shutdown JVM?
Lawrence Dol
203

Sebagian besar contoh di sini "terlalu rumit". Mereka adalah kasus tepi. Dengan contoh-contoh ini, programmer membuat kesalahan (seperti tidak mendefinisikan ulang sama dengan / kode hash), atau telah digigit oleh kasus sudut JVM / JAVA (memuat kelas dengan statis ...). Saya pikir itu bukan tipe contoh yang diinginkan pewawancara atau bahkan kasus yang paling umum.

Tetapi ada kasus yang benar-benar sederhana untuk kebocoran memori. Pengumpul sampah hanya membebaskan apa yang tidak lagi dirujuk. Kami sebagai pengembang Java tidak peduli dengan memori. Kami mengalokasikannya saat dibutuhkan dan membiarkannya dibebaskan secara otomatis. Baik.

Tetapi aplikasi yang berumur panjang cenderung memiliki status berbagi. Itu bisa apa saja, statika, lajang ... Seringkali aplikasi non-sepele cenderung membuat grafik objek yang kompleks. Hanya lupa untuk menetapkan referensi ke nol atau lebih sering lupa untuk menghapus satu objek dari koleksi sudah cukup untuk membuat memori bocor.

Tentu saja semua jenis pendengar (seperti pendengar UI), cache, atau keadaan berbagi yang berumur panjang cenderung menghasilkan kebocoran memori jika tidak ditangani dengan benar. Apa yang harus dipahami adalah bahwa ini bukan kasus sudut Jawa, atau masalah dengan pengumpul sampah. Ini adalah masalah desain. Kami merancang bahwa kami menambahkan pendengar ke objek yang berumur panjang, tetapi kami tidak menghapus pendengar saat tidak lagi diperlukan. Kami men-cache objek, tetapi kami tidak memiliki strategi untuk menghapusnya dari cache.

Kami mungkin memiliki grafik kompleks yang menyimpan keadaan sebelumnya yang diperlukan oleh perhitungan. Tetapi negara sebelumnya sendiri terkait dengan negara sebelum dan seterusnya.

Seperti kita harus menutup koneksi atau file SQL. Kita perlu mengatur referensi yang tepat ke null dan menghapus elemen dari koleksi. Kami akan memiliki strategi caching yang tepat (ukuran memori maksimum, jumlah elemen, atau timer). Semua objek yang memungkinkan pendengar diberi tahu harus menyediakan metode addListener dan removeListener. Dan ketika pemberi notifikasi ini tidak lagi digunakan, mereka harus menghapus daftar pendengar mereka.

Kebocoran memori memang benar-benar mungkin dan dapat diprediksi dengan sempurna. Tidak perlu fitur bahasa khusus atau kasing sudut. Kebocoran memori merupakan indikator bahwa ada sesuatu yang hilang atau bahkan masalah desain.

Nicolas Bousquet
sumber
24
Saya merasa lucu bahwa pada jawaban lain orang mencari kasus tepi dan trik dan tampaknya benar-benar kehilangan intinya. Mereka hanya bisa menunjukkan kode yang menyimpan referensi tidak berguna ke objek yang tidak akan pernah digunakan lagi, dan tidak pernah menghapus referensi tersebut; bisa dikatakan kasus-kasus itu bukan "benar" kebocoran memori karena masih ada referensi ke objek-objek di sekitar, tetapi jika program tidak pernah menggunakan referensi itu lagi dan juga tidak pernah menjatuhkannya, itu benar-benar setara dengan (dan seburuk) a " kebocoran memori ".
ehabkost
@Nicolas Bousquet: "Kebocoran memori memang benar mungkin" Terima kasih banyak. +15 upvotes. Bagus. Saya dimarahi di sini karena menyatakan fakta itu, sebagai tempat pertanyaan tentang bahasa Go: stackoverflow.com/questions/4400311 Pertanyaan ini masih memiliki downvotes negatif :(
SyntaxT3rr0r
GC di Jawa dan .NET dalam beberapa hal didasarkan pada asumsi grafik objek yang menyimpan referensi ke objek lain sama dengan grafik objek yang "peduli" pada objek lain. Pada kenyataannya, ada kemungkinan bahwa edge dapat ada dalam grafik referensi yang tidak mewakili hubungan "caring", dan mungkin saja suatu objek peduli dengan keberadaan objek lain meskipun tidak ada jalur referensi langsung atau tidak langsung (bahkan menggunakan WeakReference) ada dari satu ke yang lain. Jika referensi objek memiliki bit cadangan, mungkin bermanfaat untuk memiliki indikator "peduli tentang target" ...
supercat
... dan minta sistem memberikan notifikasi (melalui cara yang mirip dengan PhantomReference) jika suatu objek ditemukan tidak memiliki orang yang peduli. WeakReferencedatang agak dekat, tetapi harus dikonversi ke referensi yang kuat sebelum dapat digunakan; jika siklus GC terjadi ketika referensi kuat ada, target akan dianggap berguna.
supercat
Ini menurut saya jawaban yang benar. Kami menulis simulasi bertahun-tahun lalu. Entah bagaimana kami secara tidak sengaja menghubungkan kondisi sebelumnya ke kondisi saat ini yang membuat kebocoran memori. Karena tenggat waktu kami tidak pernah menyelesaikan kebocoran memori tetapi menjadikannya «fitur» dengan mendokumentasikannya.
nalply
163

Jawabannya sepenuhnya tergantung pada apa yang pewawancara pikir mereka bertanya.

Apakah mungkin dalam praktiknya membuat Java bocor? Tentu saja, dan ada banyak contoh dalam jawaban lainnya.

Tetapi ada beberapa pertanyaan meta yang mungkin ditanyakan?

  • Apakah implementasi Java yang "sempurna" secara teoritis rentan terhadap kebocoran?
  • Apakah kandidat memahami perbedaan antara teori dan kenyataan?
  • Apakah kandidat mengerti cara kerja pengumpulan sampah?
  • Atau bagaimana pengumpulan sampah seharusnya bekerja dalam kasus yang ideal?
  • Apakah mereka tahu mereka dapat memanggil bahasa lain melalui antarmuka asli?
  • Apakah mereka tahu kebocoran memori dalam bahasa lain itu?
  • Apakah kandidat bahkan tahu apa itu manajemen memori, dan apa yang terjadi di balik layar di Jawa?

Saya membaca pertanyaan meta Anda sebagai "Apa jawaban yang bisa saya gunakan dalam situasi wawancara ini". Dan karenanya, saya akan fokus pada keterampilan wawancara daripada Jawa. Saya percaya Anda lebih cenderung mengulangi situasi tidak mengetahui jawaban atas sebuah pertanyaan dalam sebuah wawancara daripada Anda harus berada di tempat yang perlu tahu bagaimana membuat Jawa bocor. Jadi, semoga ini membantu.

Salah satu keterampilan paling penting yang dapat Anda kembangkan untuk wawancara adalah belajar untuk secara aktif mendengarkan pertanyaan dan bekerja dengan pewawancara untuk mengekstrak maksud mereka. Ini tidak hanya memungkinkan Anda menjawab pertanyaan mereka seperti yang mereka inginkan, tetapi juga menunjukkan bahwa Anda memiliki beberapa keterampilan komunikasi yang vital. Dan ketika sampai pada pilihan antara banyak pengembang yang sama berbakatnya, saya akan mempekerjakan orang yang mendengarkan, berpikir, dan memahami sebelum mereka merespons setiap waktu.

PlayTank
sumber
22
Setiap kali saya mengajukan pertanyaan itu, saya mencari jawaban yang cukup sederhana - terus menumbuhkan antrian, tidak ada db akhirnya ditutup dll, bukan rincian classloader / utas, menyiratkan mereka memahami apa yang bisa dan tidak bisa dilakukan gc untuk Anda. Tergantung pada pekerjaan yang Anda wawancarai untuk kurasa.
DaveC
Silakan lihat pertanyaan saya, terima kasih stackoverflow.com/questions/31108772/...
Daniel Newtown
130

Berikut ini adalah contoh yang cukup berguna, jika Anda tidak mengerti JDBC . Atau setidaknya bagaimana JDBC mengharapkan pengembang untuk menutup Connection, Statementdan ResultSetcontoh sebelum membuang mereka atau kehilangan referensi kepada mereka, bukannya bergantung pada implementasi finalize.

void doWork()
{
   try
   {
       Connection conn = ConnectionFactory.getConnection();
       PreparedStatement stmt = conn.preparedStatement("some query"); // executes a valid query
       ResultSet rs = stmt.executeQuery();
       while(rs.hasNext())
       {
          ... process the result set
       }
   }
   catch(SQLException sqlEx)
   {
       log(sqlEx);
   }
}

Masalah dengan di atas adalah bahwa Connectionobjek tidak ditutup, dan karenanya koneksi fisik akan tetap terbuka, sampai pengumpul sampah datang dan melihat bahwa itu tidak dapat dijangkau. GC akan memanggil finalizemetode, tetapi ada driver JDBC yang tidak mengimplementasikan finalize, setidaknya tidak dengan cara yang sama yang Connection.closediterapkan. Perilaku yang dihasilkan adalah bahwa sementara memori akan direklamasi karena objek yang tidak terjangkau dikumpulkan, sumber daya (termasuk memori) yang terkait dengan Connectionobjek mungkin tidak dapat direklamasi.

Dalam peristiwa semacam itu di mana Connection's finalizemetode tidak bersih semuanya, orang mungkin benar-benar menemukan bahwa koneksi fisik ke server database akan berlangsung beberapa siklus pengumpulan sampah, sampai database server akhirnya angka bahwa sambungan tidak hidup (jika tidak), dan harus ditutup.

Bahkan jika driver JDBC menerapkan finalize, ada kemungkinan pengecualian dilemparkan selama finalisasi. Perilaku yang dihasilkan adalah bahwa setiap memori yang terkait dengan objek yang sekarang "tidak aktif" tidak akan direklamasi, karena finalizedijamin akan dipanggil hanya sekali.

Skenario bertemu pengecualian di atas selama finalisasi objek terkait dengan skenario lain yang mungkin dapat menyebabkan kebocoran memori - kebangkitan objek. Kebangkitan objek sering dilakukan dengan sengaja dengan membuat referensi yang kuat untuk objek dari penyelesaian, dari objek lain. Ketika objek kebangkitan disalahgunakan akan menyebabkan kebocoran memori dalam kombinasi dengan sumber kebocoran memori lainnya.

Ada banyak lagi contoh yang dapat Anda bayangkan - seperti

  • Mengelola Listcontoh di mana Anda hanya menambah daftar dan tidak menghapusnya (meskipun Anda harus menyingkirkan elemen yang tidak lagi Anda perlukan), atau
  • Membuka Sockets atau Files, tapi tidak menutup mereka ketika mereka tidak lagi diperlukan (mirip dengan contoh di atas yang melibatkan Connectionkelas).
  • Tidak membongkar lajang ketika menjatuhkan aplikasi Java EE. Rupanya, Classloader yang memuat kelas singleton akan mempertahankan referensi ke kelas, dan karenanya instance singleton tidak akan pernah dikumpulkan. Ketika instance baru aplikasi dikerahkan, pemuat kelas baru biasanya dibuat, dan pemuat kelas lama akan terus ada karena singleton.
Vineet Reynolds
sumber
98
Anda akan mencapai batas koneksi terbuka maksimum sebelum Anda mencapai batas memori biasanya. Jangan tanya kenapa saya tahu ...
Hardwareguy
Driver Oracle JDBC terkenal karena melakukan ini.
chotchki
@ Hardwareguy Saya sering mengenai batasan koneksi database SQL sampai akhirnya saya masukkan Connection.closeke dalam blok semua panggilan SQL saya. Untuk kesenangan ekstra, saya menelepon beberapa prosedur tersimpan Oracle yang sudah berjalan lama yang membutuhkan kunci di sisi Java untuk mencegah terlalu banyak panggilan ke database.
Michael Shopsin
@ Hardwareguy Itu menarik tetapi tidak benar-benar diperlukan bahwa batas koneksi aktual akan mengenai semua lingkungan. Misalnya, untuk aplikasi yang digunakan pada server aplikasi weblog 11g Saya telah melihat kebocoran koneksi dalam skala besar. Tetapi karena pilihan untuk memanen koneksi yang bocor, koneksi basis data tetap tersedia saat kebocoran memori diperkenalkan. Saya tidak yakin tentang semua lingkungan.
Aseem Bansal
Dalam pengalaman saya, Anda mendapatkan kebocoran memori bahkan jika Anda menutup koneksi. Anda harus menutup ResultSet dan PreparedStatement terlebih dahulu. Punya server yang crash berulang kali setelah berjam-jam atau bahkan berhari-hari bekerja dengan baik, karena OutOfMemoryErrors, sampai saya mulai melakukan itu.
Bjørn Stenfeldt
119

Mungkin salah satu contoh paling sederhana dari kebocoran memori potensial, dan cara menghindarinya, adalah penerapan ArrayList.remove (int):

public E remove(int index) {
    RangeCheck(index);

    modCount++;
    E oldValue = (E) elementData[index];

    int numMoved = size - index - 1;
    if (numMoved > 0)
        System.arraycopy(elementData, index + 1, elementData, index,
                numMoved);
    elementData[--size] = null; // (!) Let gc do its work

    return oldValue;
}

Jika Anda menerapkannya sendiri, apakah Anda berpikir untuk menghapus elemen array yang tidak lagi digunakan ( elementData[--size] = null)? Referensi itu mungkin membuat benda besar tetap hidup ...

meriton
sumber
5
Dan di mana kebocoran ingatannya di sini?
rds
28
@maniek: Saya tidak bermaksud mengatakan bahwa kode ini menunjukkan kebocoran memori. Saya mengutipnya untuk menunjukkan bahwa kadang-kadang kode tidak jelas diperlukan untuk menghindari retensi objek yang tidak disengaja.
meriton
Apa itu RangeCheck (indeks); ?
Koray Tugay
6
Joshua Bloch memberikan contoh ini di Java Efektif yang menunjukkan implementasi Stacks yang sederhana. Jawaban yang sangat bagus.
sewa
Tapi itu tidak akan menjadi kebocoran memori NYATA, bahkan jika lupa. Elemen tersebut masih akan diakses dengan AMAN dengan Refleksi, hanya saja tidak akan terlihat jelas dan dapat diakses langsung melalui antarmuka Daftar, tetapi objek dan referensi masih ada, dan dapat diakses dengan aman.
DGoiko
68

Setiap kali Anda menyimpan referensi di sekitar objek yang tidak lagi Anda perlukan, Anda mengalami kebocoran memori. Lihat Menangani kebocoran memori dalam program Java untuk contoh bagaimana kebocoran memori memanifestasikan dirinya di Jawa dan apa yang dapat Anda lakukan.

Bill the Lizard
sumber
14
Saya tidak percaya ini adalah "kebocoran". Ini adalah bug, dan itu berdasarkan desain program dan bahasa. Kebocoran akan menjadi benda yang berkeliaran tanpa referensi.
user541686
29
@Mehrdad: Itu hanya satu definisi sempit yang tidak sepenuhnya berlaku untuk semua bahasa. Saya berpendapat bahwa setiap kebocoran memori adalah bug yang disebabkan oleh desain program yang buruk.
Bill the Lizard
9
@Mehrdad: ...then the question of "how do you create a memory leak in X?" becomes meaningless, since it's possible in any language. Saya tidak melihat bagaimana Anda menarik kesimpulan itu. Ada beberapa cara untuk membuat kebocoran memori di Jawa dengan definisi apa pun. Ini jelas masih pertanyaan yang valid.
Bill the Lizard
7
@ 31eee384: Jika program Anda menyimpan objek dalam memori yang tidak pernah dapat digunakan, maka secara teknis itu kebocoran memori. Fakta bahwa Anda memiliki masalah yang lebih besar tidak benar-benar mengubah itu.
Bill the Lizard
8
@ 31eee384: Jika Anda tahu pasti itu tidak akan terjadi, maka itu tidak mungkin terjadi. Program, seperti tertulis, tidak akan pernah mengakses data.
Bill the Lizard
51

Anda dapat membuat kebocoran memori dengan kelas sun.misc.Unsafe . Bahkan kelas layanan ini digunakan di kelas standar yang berbeda (misalnya di kelas java.nio ). Anda tidak dapat membuat turunan dari kelas ini secara langsung , tetapi Anda dapat menggunakan refleksi untuk melakukannya .

Kode tidak dikompilasi dalam Eclipse IDE - kompilasi dengan menggunakan perintah javac(saat kompilasi Anda akan mendapatkan peringatan)

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import sun.misc.Unsafe;


public class TestUnsafe {

    public static void main(String[] args) throws Exception{
        Class unsafeClass = Class.forName("sun.misc.Unsafe");
        Field f = unsafeClass.getDeclaredField("theUnsafe");
        f.setAccessible(true);
        Unsafe unsafe = (Unsafe) f.get(null);
        System.out.print("4..3..2..1...");
        try
        {
            for(;;)
                unsafe.allocateMemory(1024*1024);
        } catch(Error e) {
            System.out.println("Boom :)");
            e.printStackTrace();
        }
    }

}
stemm
sumber
1
Memori yang dialokasikan tidak terlihat oleh pengumpul sampah
batang
4
Memori yang dialokasikan juga bukan milik Java.
bestsss
Apakah matahari / oracle ini khusus? Misalnya apakah ini akan bekerja pada IBM?
Berlin Brown
2
Memori tentu saja "milik Java", setidaknya dalam arti bahwa i) itu tidak tersedia untuk orang lain, ii) ketika aplikasi Java keluar itu akan dikembalikan ke sistem. Itu hanya di luar JVM.
Michael Anderson
3
Ini akan membangun eclipse (setidaknya dalam versi terbaru) tetapi Anda harus mengubah pengaturan kompiler: di Window> Preferences> Java> Compiler> Kesalahan / Peringatan> API yang tidak digunakan lagi dan dibatasi set Referensi terlarang (aturan akses) ke "Peringatan ".
Michael Anderson
43

Saya dapat menyalin jawaban saya dari sini: Cara termudah untuk menyebabkan kebocoran memori di Jawa?

"Kebocoran memori, dalam ilmu komputer (atau kebocoran, dalam konteks ini), terjadi ketika sebuah program komputer mengkonsumsi memori tetapi tidak dapat melepaskannya kembali ke sistem operasi." (Wikipedia)

Jawaban yang mudah adalah: Anda tidak bisa. Java melakukan manajemen memori otomatis dan akan membebaskan sumber daya yang tidak diperlukan untuk Anda. Anda tidak dapat menghentikan hal ini terjadi. Ini akan SELALU dapat melepaskan sumber daya. Dalam program dengan manajemen memori manual, ini berbeda. Anda tidak bisa mendapatkan memori di C menggunakan malloc (). Untuk membebaskan memori, Anda memerlukan pointer yang dikembalikan malloc dan memanggil bebas () di atasnya. Tetapi jika Anda tidak memiliki pointer lagi (ditimpa, atau seumur hidup terlampaui), maka Anda sayangnya tidak mampu membebaskan memori ini dan dengan demikian Anda memiliki kebocoran memori.

Semua jawaban lain sejauh ini dalam definisi saya bukan kebocoran memori. Mereka semua bertujuan untuk mengisi memori dengan hal-hal yang tidak berguna dengan sangat cepat. Tetapi kapan saja Anda masih bisa melakukan referensi objek yang Anda buat dan dengan demikian membebaskan memori -> TANPA KEBOCORAN. jawaban acconrad datang cukup dekat karena saya harus mengakui karena solusinya secara efektif hanya "menabrak" pengumpul sampah dengan memaksanya dalam lingkaran tanpa akhir).

Jawaban panjangnya adalah: Anda bisa mendapatkan kebocoran memori dengan menulis perpustakaan untuk Java menggunakan JNI, yang dapat memiliki manajemen memori manual dan dengan demikian memiliki kebocoran memori. Jika Anda memanggil perpustakaan ini, proses java Anda akan membocorkan memori. Atau, Anda dapat memiliki bug di JVM, sehingga JVM kehilangan memori. Mungkin ada bug di JVM, bahkan mungkin ada beberapa yang diketahui karena pengumpulan sampah tidak sepele, tapi masih bug. Dengan desain ini tidak mungkin. Anda mungkin meminta beberapa kode java yang dipengaruhi oleh bug semacam itu. Maaf saya tidak tahu satu dan mungkin juga bukan bug lagi di versi Java berikutnya.

yankee
sumber
12
Itu definisi kebocoran memori yang sangat terbatas (dan tidak terlalu berguna). Satu-satunya definisi yang masuk akal untuk tujuan praktis adalah "kebocoran memori adalah setiap kondisi di mana program terus menyimpan memori yang dialokasikan setelah data yang disimpannya tidak lagi diperlukan."
Mason Wheeler
1
Jawaban acconrad yang disebutkan telah dihapus?
Tomáš Zato - Reinstate Monica
1
@ TomášZato: Tidak. Saya membuka referensi di atas untuk menautkan sekarang, sehingga Anda dapat dengan mudah menemukannya.
yankee
Apa itu kebangkitan objek? Berapa kali seorang destruktor dipanggil? Bagaimana pertanyaan-pertanyaan ini menyangkal jawaban ini?
autis
1
Yah, pasti Anda bisa membuat kebocoran memori di dalam Java, meski dengan GC, dan masih memenuhi definisi di atas. Hanya memiliki struktur data append-only, dilindungi terhadap akses eksternal sehingga tidak ada kode lain setiap menghapusnya - program tidak dapat melepaskan memori karena tidak memiliki kode.
toolforger
39

Ini yang sederhana / seram via http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 .

public class StringLeaker
{
    private final String muchSmallerString;

    public StringLeaker()
    {
        // Imagine the whole Declaration of Independence here
        String veryLongString = "We hold these truths to be self-evident...";

        // The substring here maintains a reference to the internal char[]
        // representation of the original string.
        this.muchSmallerString = veryLongString.substring(0, 1);
    }
}

Karena substring mengacu pada representasi internal dari string asli, yang jauh lebih lama, yang asli tetap berada dalam memori. Jadi, selama Anda memiliki StringLeaker dalam permainan, Anda memiliki seluruh string asli dalam memori, juga, meskipun Anda mungkin berpikir Anda hanya berpegang pada string karakter tunggal.

Cara untuk menghindari penyimpanan referensi yang tidak diinginkan ke string asli adalah dengan melakukan sesuatu seperti ini:

...
this.muchSmallerString = new String(veryLongString.substring(0, 1));
...

Untuk tambahan kejahatan, Anda mungkin juga .intern()mengambil substring:

...
this.muchSmallerString = veryLongString.substring(0, 1).intern();
...

Melakukannya akan menjaga string panjang asli dan substring yang diperoleh dalam memori bahkan setelah instance StringLeaker telah dibuang.

Jon Chambers
sumber
4
Saya tidak akan menyebut itu kebocoran memori, per se . Ketika muchSmallerStringdibebaskan (karena StringLeakerobjek dihancurkan), string yang panjang akan dibebaskan juga. Apa yang saya sebut kebocoran memori adalah memori yang tidak pernah bisa dibebaskan dalam instance JVM ini. Namun, Anda telah menunjukkan diri sendiri bagaimana untuk membebaskan memori: this.muchSmallerString=new String(this.muchSmallerString). Dengan kebocoran memori nyata, tidak ada yang bisa Anda lakukan.
rds
2
@ Rds, itu poin yang adil. Non- interncase mungkin lebih merupakan "kejutan memori" daripada "kebocoran memori". .intern()Namun, substring tentu saja menciptakan situasi di mana referensi ke string yang lebih panjang dipertahankan dan tidak dapat dibebaskan.
Jon Chambers
15
Metode substring () membuat String baru di java7 (itu adalah perilaku baru)
anstarovoyt
Anda bahkan tidak perlu melakukan substring () sendiri: Gunakan pencocokan regex untuk mencocokkan bagian kecil dari input yang sangat besar, dan bawa string "yang diekstraksi" untuk waktu yang lama. Masukan besar tetap hidup sampai 6. Java
Bananeweizen
37

Contoh umum dari hal ini dalam kode GUI adalah ketika membuat widget / komponen dan menambahkan pendengar ke beberapa objek lingkup / aplikasi statis dan kemudian tidak menghapus pendengar ketika widget dihancurkan. Anda tidak hanya mendapatkan kebocoran memori, tetapi juga kinerja yang memukau ketika apa pun yang Anda dengarkan untuk acara kebakaran, semua pendengar lama Anda juga dipanggil.

pauli
sumber
1
Platform android memberikan contoh kebocoran memori yang dibuat dengan melakukan caching Bitmap di bidang statis Tampilan .
rds
36

Ambil aplikasi web yang berjalan dalam wadah servlet (Tomcat, Jetty, Glassfish, apa pun ...). Gunakan kembali aplikasi 10 atau 20 kali berturut-turut (mungkin cukup dengan hanya menyentuh PERANG dalam direktori autodeploy server.

Kecuali ada orang yang benar-benar menguji ini, kemungkinan besar bahwa Anda akan mendapatkan OutOfMemoryError setelah beberapa penugasan kembali, karena aplikasi tidak mau membersihkan setelah itu sendiri. Anda bahkan dapat menemukan bug di server Anda dengan tes ini.

Masalahnya adalah, masa pakai wadah lebih lama dari masa aplikasi Anda. Anda harus memastikan bahwa semua referensi yang mungkin dimiliki wadah atau objek dari kelas aplikasi Anda dapat dikumpulkan dari sampah.

Jika hanya ada satu referensi yang selamat dari pengabaian aplikasi web Anda, classloader yang sesuai dan akibatnya semua kelas aplikasi web Anda tidak dapat dikumpulkan dengan sampah.

Utas dimulai oleh aplikasi Anda, variabel ThreadLocal, pendaftar pendaftar adalah beberapa tersangka yang biasa menyebabkan kebocoran classloader.

Harald Wellmann
sumber
1
Ini bukan karena kebocoran memori, tetapi karena loader kelas tidak membongkar set kelas sebelumnya. Oleh karena itu tidak disarankan untuk menggunakan kembali server aplikasi tanpa me-restart server (bukan mesin fisik, tetapi server aplikasi). Saya telah melihat masalah yang sama dengan WebSphere.
Sven
35

Mungkin dengan menggunakan kode asli eksternal melalui JNI?

Dengan Java murni, hampir tidak mungkin.

Tapi itu tentang tipe "standar" kebocoran memori, ketika Anda tidak dapat mengakses memori lagi, tetapi masih dimiliki oleh aplikasi. Sebagai gantinya, Anda dapat menyimpan referensi ke objek yang tidak digunakan, atau membuka aliran tanpa menutupnya setelahnya.

Rogach
sumber
22
Itu tergantung pada definisi "kebocoran memori". Jika "memori yang disimpan, tetapi tidak lagi diperlukan", maka itu mudah dilakukan di Jawa. Jika itu "memori yang dialokasikan tetapi tidak dapat diakses oleh kode sama sekali", maka itu menjadi sedikit lebih sulit.
Joachim Sauer
@ Joachim Sauer - Maksud saya tipe kedua. Yang pertama cukup mudah untuk dibuat :)
Rogach
6
"Dengan java murni, hampir tidak mungkin." Yah, pengalaman saya adalah hal lain terutama ketika mengimplementasikan cache oleh orang-orang yang tidak menyadari jebakan di sini.
Fabian Barney
4
@Rogach: pada dasarnya ada +400 upvotes pada berbagai jawaban oleh orang-orang dengan +10.000 rep yang menunjukkan bahwa dalam kedua kasus Joachim Sauer berkomentar itu sangat mungkin. Jadi "hampir tidak mungkin" Anda tidak masuk akal.
SyntaxT3rr0r
32

Saya memiliki "kebocoran memori" yang bagus sehubungan dengan PermGen dan XML parsing sekali. Parser XML yang kami gunakan (saya tidak ingat yang mana) melakukan String.intern () pada nama tag, untuk membuat perbandingan lebih cepat. Salah satu pelanggan kami memiliki ide bagus untuk menyimpan nilai data bukan dalam atribut XML atau teks, tetapi sebagai tagnames, jadi kami memiliki dokumen seperti:

<data>
   <1>bla</1>
   <2>foo</>
   ...
</data>

Faktanya, mereka tidak menggunakan angka tetapi ID tekstual yang lebih panjang (sekitar 20 karakter), yang unik dan masuk pada tingkat 10-15 juta per hari. Itu membuat 200 MB sampah sehari, yang tidak pernah dibutuhkan lagi, dan tidak pernah GCed (karena ada di PermGen). Kami memiliki permgen yang disetel ke 512 MB, jadi butuh sekitar dua hari untuk pengecualian kehabisan memori (OOME) untuk tiba ...

Ron
sumber
4
Hanya untuk nitpick kode contoh Anda: Saya pikir angka (atau string yang dimulai dengan angka) tidak diizinkan sebagai nama elemen dalam XML.
Paŭlo Ebermann
Perhatikan bahwa ini tidak lagi berlaku untuk JDK 7+, di mana String interning terjadi di heap. Lihat artikel ini untuk penulisan rinci: java-performance.info/string-intern-in-java-6-7-8
jmiserez
Jadi, saya pikir menggunakan StringBuffer sebagai pengganti String akan menyelesaikan masalah ini? tidak akan
anubhs
24

Apa itu kebocoran memori:

  • Ini disebabkan oleh bug atau desain yang buruk.
  • Itu buang-buang memori.
  • Semakin buruk dari waktu ke waktu.
  • Pengumpul sampah tidak bisa membersihkannya.

Contoh umum:

Cache objek adalah titik awal yang baik untuk mengacaukan segalanya.

private static final Map<String, Info> myCache = new HashMap<>();

public void getInfo(String key)
{
    // uses cache
    Info info = myCache.get(key);
    if (info != null) return info;

    // if it's not in cache, then fetch it from the database
    info = Database.fetch(key);
    if (info == null) return null;

    // and store it in the cache
    myCache.put(key, info);
    return info;
}

Cache Anda tumbuh dan tumbuh. Dan segera seluruh basis data tersedot ke dalam memori. Desain yang lebih baik menggunakan LRUMap (Hanya menyimpan objek yang baru saja digunakan dalam cache).

Tentu, Anda dapat membuat banyak hal lebih rumit:

  • menggunakan konstruksi ThreadLocal .
  • menambahkan pohon referensi yang lebih kompleks .
  • atau kebocoran yang disebabkan oleh perpustakaan pihak ke-3 .

Yang sering terjadi:

Jika objek Info ini memiliki referensi ke objek lain, yang lagi-lagi memiliki referensi ke objek lain. Di satu sisi Anda juga bisa menganggap ini sebagai semacam kebocoran memori, (disebabkan oleh desain yang buruk).

bvdb
sumber
22

Saya pikir itu menarik bahwa tidak ada yang menggunakan contoh kelas internal. Jika Anda memiliki kelas internal; itu secara inheren mempertahankan referensi ke kelas yang mengandung. Tentu saja itu bukan kebocoran memori secara teknis karena Java AKAN akhirnya membersihkannya; tetapi ini dapat menyebabkan kelas-kelas lebih lama dari yang diperkirakan.

public class Example1 {
  public Example2 getNewExample2() {
    return this.new Example2();
  }
  public class Example2 {
    public Example2() {}
  }
}

Sekarang jika Anda memanggil Example1 dan mendapatkan Example1 membuang Example1, Anda secara inheren masih memiliki tautan ke objek Example1.

public class Referencer {
  public static Example2 GetAnExample2() {
    Example1 ex = new Example1();
    return ex.getNewExample2();
  }

  public static void main(String[] args) {
    Example2 ex = Referencer.GetAnExample2();
    // As long as ex is reachable; Example1 will always remain in memory.
  }
}

Saya juga mendengar desas-desus bahwa jika Anda memiliki variabel yang ada lebih lama dari jumlah waktu tertentu; Java mengasumsikan bahwa ia akan selalu ada dan sebenarnya tidak akan pernah mencoba untuk membersihkannya jika tidak dapat dijangkau dalam kode lagi. Tapi itu sama sekali tidak diverifikasi.

Suroot
sumber
2
kelas batin jarang menjadi masalah. Mereka adalah kasus yang mudah dan sangat mudah dideteksi. Rumor itu hanya rumor juga.
bestsss
2
"Rumor" terdengar seperti seseorang yang setengah membaca tentang cara kerja GC generasi. Benda yang berumur panjang, tetapi sekarang tidak dapat dijangkau, memang dapat bertahan dan memakan waktu untuk sementara, karena JVM mempromosikannya dari generasi yang lebih muda sehingga dapat berhenti memeriksa setiap lintasan. Mereka akan menghindari pass "clean up my 5000 string" yang piddly. Tapi mereka tidak abadi. Mereka masih memenuhi syarat untuk koleksi, dan jika VM diikat untuk RAM, itu akhirnya akan menjalankan penyapuan GC penuh dan repossess memori itu.
cHao
22

Baru-baru ini saya mengalami situasi kebocoran memori yang disebabkan oleh log4j.

Log4j memiliki mekanisme yang disebut Nested Diagnostic Context (NDC) yang merupakan instrumen untuk membedakan keluaran log yang disisipkan dari sumber yang berbeda. Granularitas tempat NDC bekerja adalah utas, sehingga ia membedakan keluaran log dari utas berbeda secara terpisah.

Untuk menyimpan tag spesifik thread, kelas NDC log4j menggunakan Hashtable yang dikunci oleh objek Thread itu sendiri (sebagai lawan untuk mengatakan id utas), dan dengan demikian sampai tag NDC tetap dalam memori semua objek yang menggantung dari utas objek juga tinggal di memori. Dalam aplikasi web kami, kami menggunakan NDC untuk menandai logoutput dengan id permintaan untuk membedakan log dari satu permintaan secara terpisah. Wadah yang mengaitkan tag NDC dengan utas, juga menghapusnya sambil mengembalikan respons dari permintaan. Masalah terjadi ketika selama pemrosesan permintaan, sebuah child thread muncul, kira-kira seperti kode berikut:

pubclic class RequestProcessor {
    private static final Logger logger = Logger.getLogger(RequestProcessor.class);
    public void doSomething()  {
        ....
        final List<String> hugeList = new ArrayList<String>(10000);
        new Thread() {
           public void run() {
               logger.info("Child thread spawned")
               for(String s:hugeList) {
                   ....
               }
           }
        }.start();
    }
}    

Jadi konteks NDC dikaitkan dengan inline thread yang muncul. Objek utas yang merupakan kunci untuk konteks NDC ini, adalah utas inline yang menggantung objek besar daftar itu. Karenanya bahkan setelah utas selesai melakukan apa yang dilakukannya, referensi ke daftar besar tetap hidup oleh konteks NDC yang Dapat Diperbaharui, sehingga menyebabkan kebocoran memori.

Puneet
sumber
Itu menyebalkan. Anda harus memeriksa pustaka logging yang mengalokasikan memori NOL ini saat masuk ke file: mentalog.soliveirajr.com
TraderJoeChicago
+1 Apakah Anda tahu secara langsung apakah ada masalah serupa dengan MDC di slf4j / logback (produk penerus oleh penulis yang sama)? Saya akan melakukan penyelaman mendalam pada sumbernya tetapi ingin memeriksa dulu. Bagaimanapun, terima kasih telah memposting ini.
sparc_spread
20

Pewawancara mungkin mencari referensi melingkar seperti kode di bawah ini (yang kebetulan hanya membocorkan memori di JVM yang sangat lama yang menggunakan penghitungan referensi, yang tidak lagi menjadi masalah). Tapi itu pertanyaan yang cukup samar, jadi ini adalah kesempatan utama untuk memamerkan pemahaman Anda tentang manajemen memori JVM.

class A {
    B bRef;
}

class B {
    A aRef;
}

public class Main {
    public static void main(String args[]) {
        A myA = new A();
        B myB = new B();
        myA.bRef = myB;
        myB.aRef = myA;
        myA=null;
        myB=null;
        /* at this point, there is no access to the myA and myB objects, */
        /* even though both objects still have active references. */
    } /* main */
}

Kemudian Anda dapat menjelaskan bahwa dengan penghitungan referensi, kode di atas akan membocorkan memori. Tetapi sebagian besar JVM modern tidak lagi menggunakan penghitungan referensi, sebagian besar menggunakan pengumpul sampah, yang sebenarnya akan mengumpulkan memori ini.

Selanjutnya Anda dapat menjelaskan membuat Obyek yang memiliki sumber daya asli yang mendasarinya, seperti ini:

public class Main {
    public static void main(String args[]) {
        Socket s = new Socket(InetAddress.getByName("google.com"),80);
        s=null;
        /* at this point, because you didn't close the socket properly, */
        /* you have a leak of a native descriptor, which uses memory. */
    }
}

Kemudian Anda dapat menjelaskan ini secara teknis kebocoran memori, tetapi sebenarnya kebocoran tersebut disebabkan oleh kode asli di JVM yang mengalokasikan sumber daya asli yang mendasarinya, yang tidak dibebaskan oleh kode Java Anda.

Pada akhirnya, dengan JVM modern, Anda perlu menulis beberapa kode Java yang mengalokasikan sumber daya asli di luar ruang lingkup normal kesadaran JVM.

deltamind106
sumber
19

Semua orang selalu lupa rute kode asli. Berikut ini rumus sederhana untuk kebocoran:

  1. Nyatakan metode asli.
  2. Dalam metode asli, panggil malloc. Jangan panggil free.
  3. Panggil metode asli.

Ingat, alokasi memori dalam kode asli berasal dari tumpukan JVM.

Paul Morie
sumber
1
Berdasarkan kisah nyata.
Reg
18

Buat Peta statis dan terus tambahkan referensi keras untuk itu. Itu tidak akan pernah menjadi GC.

public class Leaker {
    private static final Map<String, Object> CACHE = new HashMap<String, Object>();

    // Keep adding until failure.
    public static void addToCache(String key, Object value) { Leaker.CACHE.put(key, value); }
}
duffymo
sumber
87
Bagaimana itu bisa bocor? Itu melakukan persis seperti yang Anda minta. Jika itu kebocoran, membuat dan menyimpan objek di mana saja adalah kebocoran.
Falmarri
3
Saya setuju dengan @ Falmarri. Saya tidak melihat kebocoran di sana, Anda hanya membuat objek. Anda pasti bisa 'mengambil kembali' memori yang baru saja Anda alokasikan dengan metode lain yang disebut 'removeFromCache'. Kebocoran adalah saat Anda tidak dapat mendapatkan kembali memori.
Kyle
3
Maksud saya adalah seseorang yang terus membuat objek, mungkin memasukkannya ke dalam cache, bisa berakhir dengan kesalahan OOM jika mereka tidak hati-hati.
duffymo
8
@duffymo: Tapi bukan itu yang ditanyakan. Ini tidak ada hubungannya dengan hanya menggunakan semua memori Anda.
Falmarri
3
Benar-benar tidak valid Anda hanya mengumpulkan banyak objek dalam koleksi Peta. Referensi mereka akan disimpan karena Peta menyimpannya.
gyorgyabraham
16

Anda dapat membuat kebocoran memori bergerak dengan membuat instance baru kelas dalam metode final kelas itu. Poin bonus jika finalizer menciptakan banyak instance. Berikut adalah program sederhana yang membocorkan seluruh tumpukan dalam waktu antara beberapa detik dan beberapa menit tergantung pada ukuran tumpukan Anda:

class Leakee {
    public void check() {
        if (depth > 2) {
            Leaker.done();
        }
    }
    private int depth;
    public Leakee(int d) {
        depth = d;
    }
    protected void finalize() {
        new Leakee(depth + 1).check();
        new Leakee(depth + 1).check();
    }
}

public class Leaker {
    private static boolean makeMore = true;
    public static void done() {
        makeMore = false;
    }
    public static void main(String[] args) throws InterruptedException {
        // make a bunch of them until the garbage collector gets active
        while (makeMore) {
            new Leakee(0).check();
        }
        // sit back and watch the finalizers chew through memory
        while (true) {
            Thread.sleep(1000);
            System.out.println("memory=" +
                    Runtime.getRuntime().freeMemory() + " / " +
                    Runtime.getRuntime().totalMemory());
        }
    }
}
sethobrien
sumber
15

Saya tidak berpikir ada yang mengatakan ini: Anda dapat menghidupkan kembali objek dengan menimpa metode finalize () sehingga finalize () menyimpan referensi di suatu tempat. Pengumpul sampah hanya akan dipanggil sekali pada objek sehingga setelah itu objek tidak akan pernah hancur.

Ben
sumber
10
Ini tidak benar. finalize()tidak akan dipanggil tetapi objek akan dikumpulkan setelah tidak akan ada lagi referensi. Pengumpul sampah juga tidak 'dipanggil'.
bestsss
1
Jawaban ini menyesatkan, finalize()metode ini hanya dapat dipanggil sekali oleh JVM, tetapi ini tidak berarti bahwa itu tidak dapat dikumpulkan kembali sampah yang dikumpulkan jika objek dibangkitkan dan kemudian direferensikan lagi. Jika ada kode sumber daya penutupan dalam finalize()metode maka kode ini tidak akan dijalankan lagi, ini dapat menyebabkan kebocoran memori.
Tom Cammann
15

Saya menemukan jenis kebocoran sumber daya yang lebih halus baru-baru ini. Kami membuka sumber daya melalui getResourceAsStream pemuat kelas dan kebetulan aliran input input tidak ditutup.

Uhm, bisa dibilang, idiot sekali.

Nah, yang membuat ini menarik adalah: dengan cara ini, Anda bisa membocorkan memori tumpukan proses yang mendasarinya, bukan dari tumpukan JVM.

Yang Anda butuhkan hanyalah file jar dengan file di dalamnya yang akan dirujuk dari kode Java. Semakin besar file jar, semakin cepat memori dialokasikan.

Anda dapat dengan mudah membuat tabung seperti itu dengan kelas berikut:

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

public class BigJarCreator {
    public static void main(String[] args) throws IOException {
        ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(new File("big.jar")));
        zos.putNextEntry(new ZipEntry("resource.txt"));
        zos.write("not too much in here".getBytes());
        zos.closeEntry();
        zos.putNextEntry(new ZipEntry("largeFile.out"));
        for (int i=0 ; i<10000000 ; i++) {
            zos.write((int) (Math.round(Math.random()*100)+20));
        }
        zos.closeEntry();
        zos.close();
    }
}

Cukup tempel ke file bernama BigJarCreator.java, kompilasi dan jalankan dari baris perintah:

javac BigJarCreator.java
java -cp . BigJarCreator

Et voilà: Anda menemukan arsip jar di direktori kerja Anda saat ini dengan dua file di dalamnya.

Mari kita buat kelas kedua:

public class MemLeak {
    public static void main(String[] args) throws InterruptedException {
        int ITERATIONS=100000;
        for (int i=0 ; i<ITERATIONS ; i++) {
            MemLeak.class.getClassLoader().getResourceAsStream("resource.txt");
        }
        System.out.println("finished creation of streams, now waiting to be killed");

        Thread.sleep(Long.MAX_VALUE);
    }

}

Kelas ini pada dasarnya tidak melakukan apa-apa, tetapi membuat objek InputStream yang tidak direferensikan. Benda-benda itu akan menjadi sampah yang dikumpulkan segera dan dengan demikian, tidak berkontribusi pada ukuran tumpukan. Penting bagi contoh kita untuk memuat sumber daya yang ada dari file jar, dan ukuran memang penting di sini!

Jika Anda ragu, coba kompilasi dan mulai kelas di atas, tetapi pastikan untuk memilih ukuran tumpukan yang layak (2 MB):

javac MemLeak.java
java -Xmx2m -classpath .:big.jar MemLeak

Anda tidak akan menemukan kesalahan OOM di sini, karena tidak ada referensi yang disimpan, aplikasi akan tetap berjalan tidak peduli seberapa besar Anda memilih ITERASI dalam contoh di atas. Konsumsi memori dari proses Anda (terlihat di atas (RES / RSS) atau explorer proses) tumbuh kecuali aplikasi sampai ke perintah tunggu. Pada pengaturan di atas, itu akan mengalokasikan sekitar 150 MB dalam memori.

Jika Anda ingin aplikasi bermain aman, tutup aliran input tepat di tempat itu dibuat:

MemLeak.class.getClassLoader().getResourceAsStream("resource.txt").close();

dan proses Anda tidak akan melebihi 35 MB, terlepas dari jumlah iterasi.

Cukup sederhana dan mengejutkan.

Jay
sumber
14

Seperti yang disarankan oleh banyak orang, Kebocoran Sumberdaya cukup mudah disebabkan - seperti contoh JDBC. Kebocoran Memori Aktual sedikit lebih sulit - terutama jika Anda tidak mengandalkan pecahan JVM untuk melakukannya untuk Anda ...

Gagasan membuat objek yang memiliki jejak sangat besar dan kemudian tidak dapat mengaksesnya juga bukan kebocoran memori nyata. Jika tidak ada yang dapat mengaksesnya maka itu akan menjadi sampah yang dikumpulkan, dan jika sesuatu dapat mengaksesnya maka itu bukan kebocoran ...

Salah satu cara yang dulu bekerja - dan saya tidak tahu apakah masih ada - adalah memiliki rantai melingkar sedalam tiga. Seperti dalam Objek A memiliki referensi ke Obyek B, Obyek B memiliki referensi ke Obyek C dan Obyek C memiliki referensi ke Obyek A. GC cukup pintar untuk mengetahui bahwa dua rantai dalam - seperti dalam A <--> B - dapat dikumpulkan dengan aman jika A dan B tidak dapat diakses oleh hal lain, tetapi tidak dapat menangani rantai tiga arah ...

Graham
sumber
7
Belum terjadi beberapa waktu sekarang. GC modern tahu cara menangani referensi melingkar.
assylias
13

Cara lain untuk membuat kebocoran memori yang berpotensi besar adalah untuk memegang referensi untuk Map.Entry<K,V>sebuah TreeMap.

Sulit untuk menilai mengapa ini hanya berlaku untuk TreeMaps, tetapi dengan melihat implementasi alasannya mungkin adalah: a TreeMap.Entrymenyimpan referensi ke saudara kandungnya, oleh karena itu jika TreeMapsiap untuk dikumpulkan, tetapi beberapa kelas lain memiliki referensi ke salah satu nya Map.Entry, maka seluruh Peta akan disimpan ke dalam memori.


Skenario kehidupan nyata:

Bayangkan memiliki permintaan db yang mengembalikan TreeMapstruktur data besar . Orang biasanya menggunakan TreeMaps sebagai urutan penyisipan elemen dipertahankan.

public static Map<String, Integer> pseudoQueryDatabase();

Jika permintaan dipanggil berkali-kali dan, untuk setiap permintaan (jadi, untuk setiap Mappengembalian) Anda menyimpan suatu Entrytempat, memori akan terus tumbuh.

Pertimbangkan kelas pembungkus berikut:

class EntryHolder {
    Map.Entry<String, Integer> entry;

    EntryHolder(Map.Entry<String, Integer> entry) {
        this.entry = entry;
    }
}

Aplikasi:

public class LeakTest {

    private final List<EntryHolder> holdersCache = new ArrayList<>();
    private static final int MAP_SIZE = 100_000;

    public void run() {
        // create 500 entries each holding a reference to an Entry of a TreeMap
        IntStream.range(0, 500).forEach(value -> {
            // create map
            final Map<String, Integer> map = pseudoQueryDatabase();

            final int index = new Random().nextInt(MAP_SIZE);

            // get random entry from map
            for (Map.Entry<String, Integer> entry : map.entrySet()) {
                if (entry.getValue().equals(index)) {
                    holdersCache.add(new EntryHolder(entry));
                    break;
                }
            }
            // to observe behavior in visualvm
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

    }

    public static Map<String, Integer> pseudoQueryDatabase() {
        final Map<String, Integer> map = new TreeMap<>();
        IntStream.range(0, MAP_SIZE).forEach(i -> map.put(String.valueOf(i), i));
        return map;
    }

    public static void main(String[] args) throws Exception {
        new LeakTest().run();
    }
}

Setelah setiap pseudoQueryDatabase()panggilan, mapinstance harus siap untuk dikumpulkan, tetapi itu tidak akan terjadi, karena setidaknya satu Entrydisimpan di tempat lain.

Tergantung pada jvmpengaturan Anda , aplikasi mungkin macet di tahap awal karena a OutOfMemoryError.

Anda dapat melihat dari visualvmgrafik ini bagaimana memori terus bertambah.

Memory dump - TreeMap

Hal yang sama tidak terjadi dengan struktur data hash ( HashMap).

Ini adalah grafik saat menggunakan a HashMap.

Memory dump - HashMap

Solusinya? Langsung saja simpan kunci / nilai (seperti yang mungkin sudah Anda lakukan) daripada menyimpannya Map.Entry.


Saya telah menulis patokan yang lebih luas di sini .

Marko Pacak
sumber
11

Thread tidak dikumpulkan sampai mereka berakhir. Mereka berfungsi sebagai akar pengumpulan sampah. Mereka adalah salah satu dari sedikit objek yang tidak akan direklamasi hanya dengan melupakannya atau membersihkan referensi untuk itu.

Pertimbangkan: pola dasar untuk mengakhiri thread pekerja adalah mengatur beberapa variabel kondisi yang dilihat oleh thread. Thread dapat memeriksa variabel secara berkala dan menggunakannya sebagai sinyal untuk mengakhiri. Jika variabel tidak dideklarasikan volatile, maka perubahan ke variabel mungkin tidak terlihat oleh utas, sehingga tidak akan tahu untuk mengakhiri. Atau bayangkan jika beberapa utas ingin memperbarui objek yang dibagikan, tetapi jalan buntu saat mencoba untuk menguncinya.

Jika Anda hanya memiliki sedikit utas, bug ini mungkin akan terlihat jelas karena program Anda akan berhenti bekerja dengan baik. Jika Anda memiliki kumpulan utas yang menciptakan lebih banyak utas sesuai kebutuhan, maka utas usang / macet mungkin tidak diperhatikan, dan akan terakumulasi tanpa batas waktu, menyebabkan kebocoran memori. Utas cenderung menggunakan data lain dalam aplikasi Anda, jadi juga akan mencegah apa pun yang mereka rujuk secara langsung untuk tidak pernah dikumpulkan.

Sebagai contoh mainan:

static void leakMe(final Object object) {
    new Thread() {
        public void run() {
            Object o = object;
            for (;;) {
                try {
                    sleep(Long.MAX_VALUE);
                } catch (InterruptedException e) {}
            }
        }
    }.start();
}

Sebut System.gc()semua yang Anda suka, tetapi objek yang dilewati leakMetidak akan pernah mati.

(* diedit *)

Boann
sumber
1
@Spidey Tidak ada yang "macet". Metode pemanggilan kembali dengan segera, dan objek yang dikirimkan tidak akan pernah direklamasi. Itu tepatnya kebocoran.
Boann
1
Anda akan memiliki utas "berlari" (atau tidur, apa pun) selama masa pakai program Anda. Itu tidak dianggap sebagai kebocoran bagi saya. Serta kolam tidak dihitung sebagai kebocoran, bahkan jika Anda tidak menggunakannya sepenuhnya.
Spidey
1
@Spidey "Anda akan memiliki [hal] untuk seumur hidup program Anda. Itu tidak dianggap sebagai kebocoran bagi saya." Apakah kamu mendengar sendiri?
Boann
3
@Spidey Jika Anda menghitung memori yang diketahui proses tidak bocor, maka semua jawaban di sini salah, karena proses selalu melacak halaman mana di ruang alamat virtualnya yang dipetakan. Ketika proses berakhir, OS membersihkan semua kebocoran dengan meletakkan kembali halaman di tumpukan halaman gratis. Untuk membawa itu ke ekstrem berikutnya, seseorang dapat mengalahkan sampai mati setiap kebocoran yang diperdebatkan dengan menunjukkan bahwa tidak ada bit fisik dalam chip RAM atau dalam ruang swap pada disk yang secara fisik salah tempat atau hancur, sehingga Anda dapat mematikan komputer dan lagi untuk membersihkan kebocoran.
Boann
1
Definisi praktis dari kebocoran adalah ingatannya yang telah hilang jejaknya sehingga kita tidak tahu dan dengan demikian tidak dapat melakukan prosedur yang diperlukan untuk mendapatkan kembali hanya itu; kita harus menghancurkan dan membangun kembali seluruh ruang memori. Thread nakal seperti ini dapat muncul secara alami melalui kebuntuan atau implementasi threadpool yang cerdik. Objek yang dirujuk oleh utas tersebut, bahkan secara tidak langsung, sekarang dicegah agar tidak pernah dikumpulkan, jadi kami memiliki memori yang tidak akan secara alami direklamasi atau digunakan kembali selama masa program. Saya akan menyebutnya masalah; secara khusus ini adalah kebocoran memori.
Boann
10

Saya pikir contoh yang valid bisa menggunakan variabel ThreadLocal di lingkungan di mana utas dikumpulkan.

Misalnya, menggunakan variabel ThreadLocal di Servlets untuk berkomunikasi dengan komponen web lainnya, membuat utas dibuat oleh wadah dan memelihara yang menganggur di kumpulan. Variabel ThreadLocal, jika tidak dibersihkan dengan benar, akan tinggal di sana sampai, mungkin, komponen web yang sama menimpa nilainya.

Tentu saja, setelah diidentifikasi, masalah dapat diselesaikan dengan mudah.

mschonaker
sumber
10

Pewawancara mungkin sedang mencari solusi referensi melingkar:

    public static void main(String[] args) {
        while (true) {
            Element first = new Element();
            first.next = new Element();
            first.next.next = first;
        }
    }

Ini adalah masalah klasik dengan referensi penghitungan pengumpul sampah. Anda kemudian akan dengan sopan menjelaskan bahwa JVM menggunakan algoritma yang jauh lebih canggih yang tidak memiliki batasan ini.

-Kami Tarle

Wesley Tarle
sumber
12
Ini adalah masalah klasik dengan referensi penghitungan pengumpul sampah. Bahkan 15 tahun yang lalu Jawa tidak menggunakan penghitungan referensi. Ref. berhitung juga lebih lambat dari GC.
bestsss
4
Bukan kebocoran memori. Hanya loop tak terbatas.
Esben Skov Pedersen
2
@Esben Di setiap iterasi, sebelumnya firsttidak berguna dan harus dikumpulkan. Dalam referensi menghitung pengumpul sampah, objek tidak akan dibebaskan karena ada referensi aktif di atasnya (dengan sendirinya). Infinite loop ada di sini untuk menghilangkan kebocoran: ketika Anda menjalankan program, memori akan naik tanpa batas.
rds
@ rds @ Wesley Tarle mengira loop itu tidak terbatas. Apakah masih ada kebocoran memori?
nz_21