Fitur penting dari Java adalah secara otomatis mengelola daur ulang memori melalui pengumpul sampah (GC), tanpa mengharuskan pemrogram untuk membebaskan memori sendiri. Secara teoritis, semua memori yang ditempati oleh objek yang tidak akan lagi digunakan dalam Java dapat didaur ulang oleh GC, tetapi Java juga memiliki kebocoran memori, tetapi kinerjanya berbeda dari C ++.
Manajemen Memori di Java
Untuk memahami kebocoran memori di Java, Anda harus terlebih dahulu tahu bagaimana memori di Java dikelola.
Dalam program Java, kami biasanya menggunakan yang baru untuk mengalokasikan memori ke objek, dan ruang memori ini ada di tumpukan (heap).
Inilah contohnya:
kelas publik sederhana {public static void main (string args []) {objek objek1 = objek baru (); // obj1 objek objek2 = objek baru (); // obj2 objek2 = objek1; //...At Kali ini, OBJ2 dapat dibersihkan}}Java menggunakan grafik terarah untuk manajemen memori:
Dalam grafik yang diarahkan, kami menyebut OBJ1 dapat diakses, OBJ2 tidak dapat dijangkau, dan jelas tidak terjangkau dapat dibersihkan.
Pelepasan memori, yaitu, membersihkan objek yang tidak dapat dijangkau, ditentukan dan dieksekusi oleh GC, sehingga GC akan memantau status setiap objek, termasuk aplikasi, kutipan, kutipan, dan penugasan. Prinsip mendasar untuk melepaskan suatu objek adalah bahwa objek tidak akan lagi digunakan:
Objek diberi nilai nol, dan tidak pernah dipanggil lagi.
Yang lainnya adalah menetapkan nilai baru ke objek, yang merealisasikan ruang memori.
Biasanya, diyakini bahwa biaya mengalokasikan objek pada tumpukan relatif tinggi, tetapi GC mengoptimalkan operasi ini: dalam C ++, mengalokasikan sepotong memori pada tumpukan akan mencari sepotong memori yang tepat untuk dialokasikan. Jika objek dihancurkan, ingatan ini dapat digunakan kembali; Di Java, Anda ingin strip panjang. Setiap kali objek baru dialokasikan, "heap pointer" Java bergerak mundur ke area yang belum dialokasikan. Oleh karena itu, efisiensi Java mengalokasikan memori sebanding dengan C ++.
Tetapi ada masalah dengan cara kerja ini: jika memori sering diterapkan, sumber daya akan habis. Pada saat ini, GC mengintervensi, ia merebut kembali ruang dan membuat objek di tumpukan lebih kompak. Dengan cara ini, akan selalu ada ruang memori yang cukup untuk dialokasikan.
Metode Penghitungan Referensi Ketika GC Cleansing: Ketika referensi terhubung ke objek baru, jumlah referensi adalah +1; Ketika referensi meninggalkan ruang lingkup atau diatur ke nol, jumlah referensi adalah -1. Ketika GC menemukan bahwa jumlah ini adalah 0, itu mendaur ulang memori yang dikonsumsi. Overhead ini terjadi sepanjang kehidupan program referensi dan tidak dapat menangani referensi melingkar. Jadi metode ini hanya digunakan untuk menggambarkan cara kerja GC, dan tidak akan diterapkan oleh mesin virtual Java mana pun.
Sebagian besar GC mengadopsi metode pembersihan adaptif (ditambah teknik tambahan lainnya untuk peningkatan kecepatan), terutama didasarkan pada menemukan objek "hidup" dan kemudian menggunakan pengumpul sampah "adaptif, generasi, stop-copy, mark-clean". Saya tidak akan memperkenalkan terlalu banyak, ini bukan fokus artikel ini.
Kebocoran memori di Java
Kebocoran memori di Java, secara luas dan dalam istilah awam, adalah bahwa memori objek yang tidak akan lagi digunakan tidak dapat didaur ulang, yang merupakan kebocoran memori.
Kebocoran memori di Java berbeda dari yang ada di C ++.
Dalam C ++, semua objek yang telah dialokasikan memori harus secara manual dilepaskan oleh programmer setelah mereka tidak lagi digunakan. Oleh karena itu, setiap kelas akan berisi destruktor, yaitu untuk menyelesaikan pekerjaan pembersihan. Jika kita lupa melepaskan objek tertentu, itu akan menyebabkan kebocoran memori.
Tetapi di Java, kita tidak perlu (dan tidak bisa) membebaskan memori sendiri, dan objek yang tidak berguna secara otomatis dibersihkan oleh GC, yang sangat menyederhanakan pekerjaan pemrograman kita. Namun, kadang -kadang beberapa objek yang tidak akan lagi digunakan tidak dapat dilepaskan dalam GC, yang akan menyebabkan kebocoran memori.
Kita tahu bahwa benda -benda memiliki siklus hidup, ada yang panjang dan ada yang pendek. Jika objek siklus hidup panjang memiliki referensi dengan siklus hidup pendek, kemungkinan kebocoran memori. Mari berikan contoh sederhana:
kelas publik sederhana {objek objek; public void method1 () {objek = objek baru (); //...Kode lain}}Bahkan, kami berharap itu hanya akan digunakan dalam metode Method1 (), dan tidak akan digunakan di tempat lain. Namun, ketika metode method1 () dieksekusi, memori yang dialokasikan oleh objek tidak akan segera dianggap sebagai objek yang dapat dilepaskan, dan hanya akan dilepaskan setelah objek yang dibuat oleh kelas sederhana dirilis. Sebenarnya, ini adalah kebocoran memori. Solusinya adalah menggunakan objek sebagai variabel lokal dalam metode Method1 (). Tentu saja, jika Anda harus menulis ini, Anda dapat mengubahnya menjadi ini:
kelas publik sederhana {objek objek; public void method1 () {objek = objek baru (); //... Objek Kode Lain = NULL; }}Dengan cara ini, memori yang dialokasikan oleh "newObject ()" dapat didaur ulang oleh GC.
Pada titik ini, kebocoran memori Java harus lebih jelas. Mari kita jelaskan lebih lanjut di bawah ini:
Ketika memori yang dialokasikan dalam tumpukan tidak dirilis, semua cara untuk mengakses memori ini dihapus (seperti penugasan kembali pointer). Ini untuk bahasa seperti C ++. GC di Java akan membantu kami menangani situasi ini, jadi kami tidak perlu peduli.
Ketika objek memori jelas tidak diperlukan, ia masih mempertahankan memori ini dan metode aksesnya (referensi) yang merupakan kebocoran memori yang mungkin terjadi dalam semua bahasa. Jika Anda tidak berhati -hati saat memprogram, ini mudah terjadi, dan jika tidak terlalu serius, itu mungkin hanya kebocoran memori yang singkat.
Beberapa contoh dan solusi yang rentan terhadap kebocoran memori
Situasi seperti contoh di atas mudah terjadi, dan itu juga merupakan situasi yang paling mungkin kita abaikan dan menyebabkan kebocoran memori. Solusinya adalah meminimalkan ruang lingkup objek (misalnya, di Androidstudio, kode di atas akan mengeluarkan peringatan, dan saran yang diberikan adalah untuk menulis ulang variabel anggota kelas menjadi variabel lokal dalam metode ini) dan secara manual mengatur nilai nol.
Adapun ruang lingkup, kita perlu lebih memperhatikan saat menulis kode; Mengatur secara manual nilai nol, kita dapat melihat metode internal untuk menghapus node yang ditentukan dalam kode sumber LinkedList Java Container (lihat: Interpretasi Kode Sumber LinkedList Java (JDK1.8) ):
// Hapus simpul yang ditentukan dan kembalikan nilai elemen yang dihapus e unlink (node <E> x) {// Dapatkan nilai saat ini dan elemen E node depan dan belakang = x.item; Node akhir <E> NEXT = X.NEXT; Node akhir <E> prev = x.prev; if (prev == null) {first = next; // Jika simpul sebelumnya kosong (seperti node saat ini adalah simpul pertama), simpul berikutnya menjadi simpul pertama baru} else {prev.next = next; // Jika node sebelumnya tidak kosong, maka itu menunjuk ke simpul berikutnya x.prev = null; } if (next == null) {last = prev; // Jika simpul berikutnya kosong (seperti simpul saat ini adalah simpul ekor), yang sebelumnya dari simpul saat ini menjadi simpul ekor baru} else {next.prev = prev; // Jika simpul berikutnya tidak kosong, node berikutnya ke depan ke simpul sebelumnya x.next = null; } x.item = null; ukuran--; modcount ++; elemen kembali; }Selain memodifikasi hubungan antara node, yang juga perlu kita lakukan adalah menetapkan nilai nol. Tidak masalah kapan GC akan mulai membersihkan, kita harus menandai objek yang tidak berguna sebagai objek yang dapat dibersihkan dalam waktu.
Kita tahu bahwa Java Container ArrayList diimplementasikan dalam array (lihat: Interpretasi Kode Sumber ArrayList Java (JDK1.8) ). Jika kita ingin menulis metode pop () (pop) untuk itu, mungkin terlihat seperti ini:
publik e pop () {if (size == 0) return null; lain mengembalikan (e) elementData [-ukuran]; }Metode penulisan sangat ringkas, tetapi akan menyebabkan overflow memori di sini: ElementData [size-1] masih memiliki referensi ke objek tipe-e dan tidak dapat didaur ulang oleh GC untuk saat ini. Kita dapat memodifikasinya sebagai berikut:
publik e pop () {if (size == 0) return null; else {e e = (e) elementData [-size]; elementData [size] = null; mengembalikan e; }}Saat menulis kode, kita tidak dapat secara membabi buta mengejar kesederhanaan. Hal pertama adalah memastikan keakuratannya.
Kebocoran memori selama penggunaan wadah
Dalam banyak artikel, Anda dapat melihat contoh kebocoran memori sebagai berikut:
Vektor v = vektor baru (); untuk (int i = 1; i <100; i ++) {objek o = objek baru (); v.add (o); o = null; }Banyak orang mungkin tidak memahaminya di awal, sehingga kita dapat memahami kode di atas sebagai berikut:
void method () {vektor vektor = vektor baru (); untuk (int i = 1; i <100; i ++) {objek objek = objek baru (); vector.add (objek); objek = null; } //...Operasi pada vektor // ... operasi lain yang tidak terkait dengan vektor}Di sini, kebocoran memori merujuk pada fakta bahwa setelah operasi vektor selesai, jika operasi GC terjadi, serangkaian objek ini tidak dapat didaur ulang. Kebocoran memori di sini mungkin berumur pendek, karena setelah seluruh metode metode () dieksekusi, objek-objek tersebut masih dapat didaur ulang. Sangat mudah untuk dipecahkan di sini, cukup tetapkan nilainya secara manual:
void method () {vektor vektor = vektor baru (); untuk (int i = 1; i <100; i ++) {objek objek = objek baru (); vector.add (objek); objek = null; } //...Operasi pada v vektor = null; //... Operasi lain yang tidak terkait dengan V}Vektor di atas sudah ketinggalan zaman, tetapi itu hanya pengantar kebocoran memori menggunakan contoh lama. Saat kami menggunakan wadah, mudah untuk menyebabkan kebocoran memori, seperti contoh di atas. Namun, dalam contoh di atas, efek kebocoran memori yang disebabkan oleh variabel lokal dalam metode selama waktu wadah mungkin tidak terlalu besar (tetapi kita juga harus menghindarinya). Namun, jika wadah ini adalah variabel anggota dari suatu kelas, atau bahkan variabel anggota statis, Anda harus lebih memperhatikan kebocoran memori.
Berikut ini adalah kesalahan yang mungkin terjadi saat menggunakan wadah:
Public Class CollectionMory {public static void main (String s []) {set <myobject> objek = new LinkedHashSet <MyObject> (); objeks.add (myObject baru ()); objeks.add (myObject baru ()); objeks.add (myObject baru ()); objeks.add (myObject baru ()); System.out.println (objects.size ()); while (true) {Objects.add (new myobject ()); }}} class myobject {// Setel panjang array default ke 99999 dan terjadi lebih cepat daftar outofmemoryError <string> Daftar = Daftar Array baru <> (99999);}Menjalankan kode di atas akan melaporkan kesalahan dengan sangat cepat:
3 Exception di Thread "Main" Java.lang.outofmemoryError: Java Heap Space di java.util.arraylist. <inin> (arraylist.java:152) di com.anxpp.memory.myobject. <inin> (collectionMemory.java:21) di com.anxpp.memory.collectionmemory.main (collectionMemory.java:16)
Jika Anda cukup tahu tentang wadah Java, kesalahan di atas tidak akan terjadi. Berikut ini juga postingan yang saya perkenalkan wadah Java: ...
Set wadah hanya menyimpan elemen unik dan dibandingkan melalui metode objek Equals (). Namun, semua kelas di Java secara langsung atau tidak langsung diwariskan ke kelas objek. Metode Equals () dari kelas objek membandingkan alamat objek. Dalam contoh di atas, elemen akan ditambahkan sampai memori overflow.
Oleh karena itu, contoh di atas secara ketat, meluap memori yang disebabkan oleh penggunaan wadah yang salah.
Sejauh menyangkut set, metode Remove () juga menggunakan metode Equals () untuk menghapus elemen yang cocok. Jika suatu objek memberikan metode Equals () yang benar, ingatlah untuk tidak menggunakan Hapus (Objecto) setelah memodifikasi objek ini, ini juga dapat menyebabkan kebocoran memori.
Berbagai objek yang menyediakan metode dekat ()
Misalnya, saat menggunakan koneksi database (DataSourse.getConnection ()), koneksi jaringan (soket) dan koneksi IO, dan ketika menggunakan kerangka kerja lainnya, mereka tidak akan secara otomatis didaur ulang dengan GC kecuali mereka secara eksplisit memanggil metode dekat () mereka (atau metode serupa) untuk menutup koneksi mereka. Faktanya, alasannya adalah bahwa objek siklus panjang memiliki referensi ke objek siklus kehidupan pendek.
Banyak orang mungkin menggunakan Hibernate. Saat kami mengoperasikan database, kami mendapatkan sesi melalui sessionfactory:
Sesi sesi = sessionfactory.opensession ();
Setelah selesai, kita harus memanggil metode tutup () untuk ditutup:
session.close ();
SessionFactory adalah objek panjang, dan sesi adalah objek yang relatif pendek, tetapi kerangka kerja dirancang dengan cara ini: tidak tahu berapa lama kita akan menggunakan sesi, sehingga hanya dapat memberikan cara bagi kita untuk memutuskan kapan kita tidak akan lagi menggunakannya.
Karena pengecualian dapat dilemparkan sebelum metode tutup () dipanggil, sehingga metode tidak dapat dipanggil. Kami biasanya menggunakan bahasa coba, dan akhirnya mengeksekusi Close () dan pekerjaan pembersihan lainnya dalam pernyataan:
coba {session = sessionfactory.opensession (); //...Operasi lain} akhirnya {session.close (); }Kebocoran memori yang disebabkan oleh mode singleton
Dalam banyak kasus, kita dapat menganggap siklus hidupnya mirip dengan siklus hidup seluruh program, jadi itu adalah objek dengan siklus hidup yang panjang. Jika objek ini memegang referensi ke objek lain, kebocoran memori juga rentan terjadi.
Referensi ke kelas internal dan modul eksternal
Faktanya, prinsipnya masih sama, tetapi cara yang tampak berbeda.
Metode yang terkait dengan pembersihan
Bagian ini terutama berbicara tentang metode GC () dan finalisasi ().
GC ()
Untuk pemrogram, GC pada dasarnya transparan dan tidak terlihat. Fungsi yang menjalankan GC adalah System.gc (), dan setelah menyebutnya, ia memulai pengumpul sampah dan mulai membersihkan.
Namun, menurut definisi spesifikasi bahasa Java, fungsi ini tidak menjamin bahwa pengumpul sampah JVM akan dieksekusi. Karena, pelaksana JVM yang berbeda dapat menggunakan algoritma yang berbeda untuk mengelola GC. Secara umum, utas GC memiliki prioritas yang lebih rendah.
Ada banyak strategi bagi JVM untuk menelepon GC. Beberapa dari mereka hanya mulai bekerja ketika penggunaan memori mencapai tingkat tertentu. Beberapa mengeksekusi mereka secara teratur. Beberapa mengeksekusi GC dengan lancar, dan beberapa mengeksekusi GC dengan cara interupsi. Tetapi secara umum, kita tidak perlu peduli tentang ini. Kecuali dalam beberapa situasi tertentu, pelaksanaan GC mempengaruhi kinerja aplikasi. Misalnya, untuk sistem berbasis web real-time seperti game online, pengguna tidak ingin GC tiba-tiba mengganggu eksekusi aplikasi dan melakukan pengumpulan sampah, maka kita perlu menyesuaikan parameter GC sehingga GC dapat membebaskan memori dengan cara yang mulus, seperti menguraikan koleksi sampah menjadi serangkaian langkah kecil untuk dijalankan. Hotspotjvm yang disediakan oleh Sun mendukung fitur ini.
menyelesaikan()
finize () adalah metode di kelas objek.
Mereka yang tahu C ++ tahu bahwa ada destruktor, tetapi perhatikan bahwa finalisasi () sama sekali tidak sama dengan destruktor di C ++.
Ini dijelaskan dalam Ide Pemrograman Java: Setelah GC siap untuk membebaskan ruang penyimpanan yang ditempati oleh objek, metode finalisasi () akan dipanggil terlebih dahulu, dan memori yang ditempati oleh objek akan didaur ulang hanya ketika tindakan daur ulang GC berikutnya terjadi, sehingga kita dapat menempatkan beberapa pekerjaan pembersihan di finalisasi ().
Tujuan penting dari metode ini adalah: Ketika memanggil kode non-java (seperti C dan C ++) di Java, operasi yang diterapkan memori yang sesuai (seperti fungsi C malloc () C) dapat digunakan dalam kode non-java ini, dan dalam metode non-java ini, memori ini tidak merilis memori ini secara efektif, Anda dapat menggunakan metode finalze () local () () (), free free.) Dan memori lain.
Oleh karena itu finalisasi () tidak cocok untuk pembersihan biasa.
Namun, terkadang, metode ini memiliki beberapa kegunaan:
Jika ada serangkaian objek, salah satu objek memiliki keadaan palsu. Jika kita telah memproses objek ini, negara akan menjadi kenyataan. Untuk menghindari objek yang hilang tanpa diproses, Anda dapat menggunakan metode finalisasi ():
kelas myObject {boolean state = false; public void deal () {//...Some Operations Processing State = true; } @Override Protected void finalize () {if (! State) {System.out.println ("error:" + "objek tidak diproses!"); }} // ...}Tetapi dari banyak aspek, metode ini disarankan untuk tidak digunakan dan dianggap berlebihan.
Secara umum, kebocoran memori disebabkan oleh penyandian yang buruk. Kami tidak dapat menyalahkan JVM karena tidak membersihkan lebih wajar.
Meringkaskan
Di atas adalah semua penjelasan terperinci dari kode bocor memori dalam bahasa Java. Saya harap ini akan membantu semua orang. Teman yang tertarik dapat terus merujuk ke topik terkait lainnya di situs ini. Jika ada kekurangan, silakan tinggalkan pesan untuk menunjukkannya. Terima kasih teman atas dukungan Anda untuk situs ini!