Kasus dan analisis
Latar belakang masalah
Di Tomcat, kode berikut ada di dalam Webapp, yang akan menyebabkan WebappClassLoader bocor dan tidak dapat didaur ulang.
kelas publik mycounter {private int count = 0; public void increment () {count ++; } public int getCount () {return count; }} kelas publik mythreadlocal memperluas threadlocal <mycounter> {} kelas publik LeakingServlet memperluas httpservlet {private static mythreadlocal myThreadlocal = myThreadlocal () baru; DOGET VOID DOGET (HTTPSERVLETREQUEST, HTTPSERVLETRESPONSE Tanggapan) melempar ServletException, ioException {myCounter counter = mythreadlocal.get (); if (counter == null) {counter = new mycounter (); myThreadlocal.set (counter); } response.getWriter (). println ("Utas saat ini melayani servlet ini" + counter.getCount () + "Times"); counter.increment (); }} Dalam kode di atas, selama LeakingServlet dipanggil sekali dan utas yang dieksekusi tidak dihentikan, kebocoran WebappClassLoader akan terjadi. Setiap kali Anda reload aplikasi, Anda akan memiliki instance WebappClassLoader tambahan, yang pada akhirnya akan mengarah ke PermGen OutOfMemoryException .
Menyelesaikan masalahnya
Sekarang mari kita pikirkan: mengapa subkelas ThreadLocal di atas menyebabkan bocor memori?
WebAppClassLoader
Pertama -tama, kita perlu mencari tahu apa itu WebappClassLoader ?
Untuk aplikasi web yang berjalan dalam wadah Java EE, implementasi class loader berbeda dari aplikasi Java umum. Wadah web yang berbeda juga akan diimplementasikan secara berbeda. Di Apache Tomcat, setiap aplikasi web memiliki instance loader kelas yang sesuai. Loader kelas ini juga menggunakan mode proxy, perbedaannya adalah pertama -tama mencoba memuat kelas tertentu, dan kemudian proxy ke loader kelas induk jika tidak dapat ditemukan. Ini adalah kebalikan dari urutan loader kelas umum. Ini adalah praktik yang disarankan dalam spesifikasi Java Servlet, dan tujuannya adalah untuk membuat kelas aplikasi web sendiri memprioritaskan lebih tinggi daripada yang disediakan oleh wadah web. Pengecualian untuk pola proxy ini adalah bahwa kelas -kelas di Perpustakaan Inti Java tidak berada dalam ruang lingkup pencarian. Ini juga untuk memastikan jenis keamanan Perpustakaan Inti Java.
Dengan kata lain, WebappClassLoader adalah loader kelas khusus untuk Tomcat untuk memuat WebApps. Kelas loader dari setiap WebApp berbeda. Ini untuk mengisolasi kelas yang dimuat oleh aplikasi yang berbeda.
Jadi apa hubungannya fitur WebappClassLoader dengan kebocoran memori? Ini belum terlihat, tetapi ini adalah fitur yang sangat penting yang patut mendapat perhatian kami: setiap WebApp memiliki WebappClassLoader sendiri, yang berbeda dari loader kelas inti Java.
Kita tahu bahwa kebocoran WebappClassLoader harus karena sangat dirujuk oleh objek lain, sehingga kita dapat mencoba menggambar diagram hubungan referensi mereka. dll! Apa sebenarnya yang bekerja oleh class loader? Mengapa terpaksa mengutip?
Siklus hidup kelas dan loader kelas
Untuk menyelesaikan masalah di atas, kita harus mempelajari hubungan antara siklus hidup kelas dan loader kelas.
Hal utama yang terkait dengan kasus kami adalah uninstalasi kelas:
Setelah kelas digunakan, jika situasi berikut dipenuhi, kelas akan dihapus:
1. Semua contoh kelas ini telah didaur ulang, yaitu, tidak ada contoh kelas ini di tumpukan Java.
2. ClassLoader memuat kelas ini telah didaur ulang.
3. Objek java.lang.Class yang sesuai dengan kelas ini tidak dirujuk di mana pun, dan tidak ada metode untuk mengakses kelas melalui refleksi di mana saja.
Jika semua kondisi di atas dipenuhi, JVM akan menghapus instalan kelas selama pengumpulan sampah di area metode. Proses uninstallasi kelas sebenarnya adalah untuk menghapus informasi kelas di area metode, dan seluruh siklus hidup kelas Java berakhir.
Kelas yang dimuat oleh class loader yang dilengkapi dengan mesin virtual Java tidak akan pernah dihapus selama siklus hidup mesin virtual. Loader kelas yang dilengkapi dengan mesin virtual Java termasuk loader kelas root, loader kelas ekstensi dan pemuat kelas sistem. Mesin virtual Java itu sendiri selalu merujuk loader kelas ini, dan loader kelas ini selalu merujuk pada objek kelas dari kelas yang dimuat, sehingga objek kelas ini selalu dapat diakses.
Kelas yang dimuat oleh loader kelas yang ditentukan pengguna dapat diturunkan.
Perhatikan kalimat di atas. Jika WebappClassLoader bocor, itu berarti bahwa kelas yang dimuat tidak dapat dihapus, yang menjelaskan mengapa kode di atas menyebabkan PermGen OutOfMemoryException .
Lihatlah gambar di bawah ini di titik kunci
Kita dapat menemukan bahwa objek Loader Class secara dua arah dikaitkan dengan objek kelas yang dimuatnya. Ini berarti bahwa objek kelas dapat menjadi penyebab referensi paksa ke WebappClassLoader , menyebabkannya bocor.
Mengutip diagram hubungan
Setelah memahami hubungan antara class loader dan siklus hidup kelas, kita dapat mulai menggambar diagram hubungan referensi. ( LeakingServlet.class dan myThreadLocal dalam gambar tidak ketat dalam gambar referensi, terutama untuk mengungkapkan bahwa myThreadLocal adalah variabel kelas)
Di bawah ini, kami menganalisis penyebab kebocoran WebappClassLoader berdasarkan diagram di atas.
1. LeakingServlet memegang MyThreadLocal static , menghasilkan siklus hidup myThreadLocal selama siklus hidup kelas LeakingServlet . Ini berarti bahwa myThreadLocal tidak akan didaur ulang, dan referensi yang lemah tidak berguna, sehingga utas saat ini tidak dapat menghapus referensi kuat penghitung melalui langkah -langkah perlindungan ThreadLocalMap .
2. Rantai Referensi Kuat: thread -> threadLocalMap -> counter -> MyCounter.class -> WebappClassLocader , menyebabkan WebappClassLoader bocor.
Meringkaskan
Kebocoran memori sulit dideteksi dan sering disebabkan oleh banyak alasan. Threadlocal telah menjadi pengunjung yang sering terjadi pada kebocoran memori karena siklus hidupnya terikat pada benang, dan sedikit kecerobohan akan menyebabkan bencana. Artikel ini hanyalah analisis kasus tertentu. Jika Anda dapat menggunakan ini untuk menerapkannya pada kasus lain, itu akan sangat bagus. Semoga artikel ini bermanfaat bagi semua orang.