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?
java
memory
memory-leaks
Mat B.
sumber
sumber
Jawaban:
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:
ClassLoader
.new byte[1000000]
), menyimpan referensi yang kuat untuk itu di bidang statis, dan kemudian menyimpan referensi untuk dirinya sendiri di aThreadLocal
. Mengalokasikan memori tambahan adalah opsional (membocorkan instance kelas sudah cukup), tetapi itu akan membuat kebocoran bekerja lebih cepat.ClassLoader
mana itu diambil.Karena cara
ThreadLocal
ini diterapkan di JDK Oracle, ini menciptakan kebocoran memori:Thread
memiliki bidang pribadithreadLocals
, yang sebenarnya menyimpan nilai-nilai lokal thread.ThreadLocal
objek, jadi setelahThreadLocal
objek tersebut dikumpulkan dari sampah, entri tersebut dihapus dari peta.ThreadLocal
objek yang merupakan kuncinya , objek tersebut tidak akan dikumpulkan atau dibuang dari peta selama utasnya hidup.Dalam contoh ini, rantai referensi kuat terlihat seperti ini:
Thread
objek →threadLocals
peta → contoh kelas contoh → kelas contoh →ThreadLocal
bidang statis →ThreadLocal
objek.(The
ClassLoader
tidak 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 kelasClassLoader
dialokasikan 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
ThreadLocal
s 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 .
sumber
Referensi objek memegang bidang statis [esp bidang terakhir]
Memanggil
String.intern()
String yang panjang(Tidak tertutup) aliran terbuka (file, jaringan dll ...)
Koneksi tidak tertutup
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.
Opsi JVM salah atau tidak tepat , seperti
noclassgc
opsi pada IBM JDK yang mencegah pengumpulan sampah kelas yang tidak digunakanLihat pengaturan IBM jdk .
sumber
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.intern
konten hashtable -nya . Dengan demikian, ini adalah sampah yang dikumpulkan dengan benar dan bukan kebocoran. (but IANAJP) mindprod.com/jgloss/interned.html#GCHal sederhana yang harus dilakukan adalah menggunakan HashSet dengan yang salah (atau tidak ada)
hashCode()
atauequals()
, 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
sumber
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 menyalinchar[]
, 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.addShutdownHook
dan 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
Thread
masuk ke kategori yang sama seperti di atas.Membuat utas mewarisi
ContextClassLoader
danAccessControlContext
, ditambah denganThreadGroup
dan apa sajaInheritedThreadLocal
, 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 menampilkanThreadFactory
antarmuka 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).ThreadLocal
cache; 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/SoftReference
yang mungkin menyimpan referensi keras kembali ke objek yang dijaga.Menggunakan
java.net.URL
dengan protokol HTTP (S) dan memuat sumber daya dari (!). Yang ini spesial,KeepAliveCache
membuat 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
InflaterInputStream
passingnew java.util.zip.Inflater()
dalam konstruktor (PNGImageDecoder
misalnya) dan tidak memanggilend()
inflater. Nah, jika Anda meneruskan konstruktor dengan adilnew
, tidak ada kesempatan ... Dan ya, memanggilclose()
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,Deflater
tidak banyak digunakan dan setahu saya JDK tidak mengandung penyalahgunaan. Selalu panggilend()
jika Anda secara manual membuat aDeflater
atauInflater
. 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!
sumber
Creating but not starting a Thread...
Astaga, saya digigit oleh yang satu ini beberapa abad yang lalu! (Jawa 1.3)unstarted
jumlah tetapi itu mencegah kelompok utas dari menghancurkan (kejahatan yang lebih rendah tetapi masih bocor)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.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.
sumber
WeakReference
) ada dari satu ke yang lain. Jika referensi objek memiliki bit cadangan, mungkin bermanfaat untuk memiliki indikator "peduli tentang target" ...PhantomReference
) jika suatu objek ditemukan tidak memiliki orang yang peduli.WeakReference
datang agak dekat, tetapi harus dikonversi ke referensi yang kuat sebelum dapat digunakan; jika siklus GC terjadi ketika referensi kuat ada, target akan dianggap berguna.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?
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.
sumber
Berikut ini adalah contoh yang cukup berguna, jika Anda tidak mengerti JDBC . Atau setidaknya bagaimana JDBC mengharapkan pengembang untuk menutup
Connection
,Statement
danResultSet
contoh sebelum membuang mereka atau kehilangan referensi kepada mereka, bukannya bergantung pada implementasifinalize
.Masalah dengan di atas adalah bahwa
Connection
objek tidak ditutup, dan karenanya koneksi fisik akan tetap terbuka, sampai pengumpul sampah datang dan melihat bahwa itu tidak dapat dijangkau. GC akan memanggilfinalize
metode, tetapi ada driver JDBC yang tidak mengimplementasikanfinalize
, setidaknya tidak dengan cara yang sama yangConnection.close
diterapkan. Perilaku yang dihasilkan adalah bahwa sementara memori akan direklamasi karena objek yang tidak terjangkau dikumpulkan, sumber daya (termasuk memori) yang terkait denganConnection
objek mungkin tidak dapat direklamasi.Dalam peristiwa semacam itu di mana
Connection
'sfinalize
metode 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, karenafinalize
dijamin 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
List
contoh di mana Anda hanya menambah daftar dan tidak menghapusnya (meskipun Anda harus menyingkirkan elemen yang tidak lagi Anda perlukan), atauSocket
s atauFile
s, tapi tidak menutup mereka ketika mereka tidak lagi diperlukan (mirip dengan contoh di atas yang melibatkanConnection
kelas).sumber
Connection.close
ke 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.Mungkin salah satu contoh paling sederhana dari kebocoran memori potensial, dan cara menghindarinya, adalah penerapan ArrayList.remove (int):
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 ...sumber
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.
sumber
...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.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)sumber
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.
sumber
Ini yang sederhana / seram via http://wiki.eclipse.org/Performance_Bloopers#String.substring.28.29 .
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:
Untuk tambahan kejahatan, Anda mungkin juga
.intern()
mengambil substring:Melakukannya akan menjaga string panjang asli dan substring yang diperoleh dalam memori bahkan setelah instance StringLeaker telah dibuang.
sumber
muchSmallerString
dibebaskan (karenaStringLeaker
objek 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.intern
case 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.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.
sumber
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.
sumber
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.
sumber
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:
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 ...
sumber
Apa itu kebocoran memori:
Contoh umum:
Cache objek adalah titik awal yang baik untuk mengacaukan segalanya.
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:
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).
sumber
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.
Sekarang jika Anda memanggil Example1 dan mendapatkan Example1 membuang Example1, Anda secara inheren masih memiliki tautan ke objek Example1.
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.
sumber
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:
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.
sumber
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.
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:
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.
sumber
Semua orang selalu lupa rute kode asli. Berikut ini rumus sederhana untuk kebocoran:
malloc
. Jangan panggilfree
.Ingat, alokasi memori dalam kode asli berasal dari tumpukan JVM.
sumber
Buat Peta statis dan terus tambahkan referensi keras untuk itu. Itu tidak akan pernah menjadi GC.
sumber
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:
sumber
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.
sumber
finalize()
tidak akan dipanggil tetapi objek akan dikumpulkan setelah tidak akan ada lagi referensi. Pengumpul sampah juga tidak 'dipanggil'.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 dalamfinalize()
metode maka kode ini tidak akan dijalankan lagi, ini dapat menyebabkan kebocoran memori.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:
Cukup tempel ke file bernama BigJarCreator.java, kompilasi dan jalankan dari baris perintah:
Et voilà: Anda menemukan arsip jar di direktori kerja Anda saat ini dengan dua file di dalamnya.
Mari kita buat kelas kedua:
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):
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:
dan proses Anda tidak akan melebihi 35 MB, terlepas dari jumlah iterasi.
Cukup sederhana dan mengejutkan.
sumber
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 ...
sumber
Cara lain untuk membuat kebocoran memori yang berpotensi besar adalah untuk memegang referensi untuk
Map.Entry<K,V>
sebuahTreeMap
.Sulit untuk menilai mengapa ini hanya berlaku untuk
TreeMap
s, tetapi dengan melihat implementasi alasannya mungkin adalah: aTreeMap.Entry
menyimpan referensi ke saudara kandungnya, oleh karena itu jikaTreeMap
siap untuk dikumpulkan, tetapi beberapa kelas lain memiliki referensi ke salah satu nyaMap.Entry
, maka seluruh Peta akan disimpan ke dalam memori.Skenario kehidupan nyata:
Bayangkan memiliki permintaan db yang mengembalikan
TreeMap
struktur data besar . Orang biasanya menggunakanTreeMap
s sebagai urutan penyisipan elemen dipertahankan.Jika permintaan dipanggil berkali-kali dan, untuk setiap permintaan (jadi, untuk setiap
Map
pengembalian) Anda menyimpan suatuEntry
tempat, memori akan terus tumbuh.Pertimbangkan kelas pembungkus berikut:
Aplikasi:
Setelah setiap
pseudoQueryDatabase()
panggilan,map
instance harus siap untuk dikumpulkan, tetapi itu tidak akan terjadi, karena setidaknya satuEntry
disimpan di tempat lain.Tergantung pada
jvm
pengaturan Anda , aplikasi mungkin macet di tahap awal karena aOutOfMemoryError
.Anda dapat melihat dari
visualvm
grafik ini bagaimana memori terus bertambah.Hal yang sama tidak terjadi dengan struktur data hash (
HashMap
).Ini adalah grafik saat menggunakan a
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 .
sumber
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:
Sebut
System.gc()
semua yang Anda suka, tetapi objek yang dilewatileakMe
tidak akan pernah mati.(* diedit *)
sumber
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.
sumber
Pewawancara mungkin sedang mencari solusi referensi melingkar:
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
sumber
first
tidak 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.