Artikel ini memperkenalkan prinsip -prinsip manajemen memori Java dan penyebab kebocoran memori secara detail.
Mekanisme Manajemen Memori Java
Dalam C ++, jika sepotong memori diperlukan untuk secara dinamis mengalokasikan sepotong memori, programmer perlu bertanggung jawab atas seluruh siklus hidup dari ingatan ini. Dari aplikasi untuk alokasi, untuk digunakan, hingga rilis akhir. Proses ini sangat fleksibel, tetapi sangat rumit. Bahasa Java telah membuat optimalisasi sendiri untuk manajemen memori, yang merupakan mekanisme pengumpulan sampah. Hampir semua objek memori di Java dialokasikan pada memori heap (kecuali untuk tipe data dasar), dan GC (Gage Collection) kemudian bertanggung jawab untuk mendaur ulang memori secara otomatis yang tidak lagi digunakan.
Di atas adalah situasi dasar dari mekanisme manajemen memori Java. Tetapi jika kita hanya memahami ini, kita masih akan mengalami kebocoran memori dalam pengembangan proyek yang sebenarnya. Beberapa orang mungkin ragu bahwa karena mekanisme pengumpulan sampah Java dapat mendaur ulang memori secara otomatis, mengapa masih akan ada kebocoran memori? Dalam pertanyaan ini, kita perlu tahu kapan GC mendaur ulang objek memori dan objek memori seperti apa yang akan dianggap "tidak lagi digunakan" oleh GC.
Akses ke objek memori di Java menggunakan metode referensi. Dalam kode Java, kami mempertahankan variabel referensi dari objek memori. Dalam program Java, variabel referensi ini sendiri dapat disimpan dalam memori heap dan dalam memori tumpukan kode (sama seperti tipe data dasar). Thread GC mulai melacak dari variabel referensi dalam tumpukan kode untuk menentukan memori mana yang digunakan. Jika utas GC tidak dapat melacak sepotong memori tumpukan dengan cara ini, maka GC percaya bahwa bagian memori ini tidak akan lagi digunakan (karena kode tidak dapat lagi mengakses memori ini).
Melalui metode manajemen memori grafik yang diarahkan ini, ketika objek memori kehilangan semua referensi, GC dapat mendaur ulangnya. Sebaliknya, jika objek masih memiliki referensi, itu tidak akan didaur ulang oleh GC, bahkan jika mesin virtual Java melempar OutofmemoryError.
Kebocoran memori Java
Secara umum, ada dua situasi untuk kebocoran memori. Dalam satu kasus, dalam bahasa C/C ++, semua memori yang dialokasikan dalam tumpukan akan dihapus (seperti penugasan kembali pointer) ketika tidak dirilis; memori dan metode aksesnya (referensi). Kasus pertama adalah bahwa itu telah dipecahkan dengan baik di Java karena pengenalan mekanisme pengumpulan sampah. Oleh karena itu, bocor memori di Java terutama merujuk pada kasus kedua.
Mungkin hanya berbicara tentang konsepnya terlalu abstrak, Anda dapat melihat contoh seperti itu:
Salinan kode adalah sebagai berikut:
Vektor V = vektor baru (10);
untuk (int i = 1; i <100; i ++) {
Objek o = objek baru ();
v.add (o);
o = null;
}
Dalam contoh ini, ada referensi ke objek vektor V dan referensi ke objek objek o dalam tumpukan kode. Di loop untuk, kami terus menghasilkan objek baru, kemudian menambahkannya ke objek vektor, dan kemudian mengosongkan referensi O. Pertanyaannya adalah, jika GC terjadi setelah referensi O kosong, akankah objek objek yang kita buat didaur ulang oleh GC? Jawabannya adalah tidak. Karena ketika GC melacak referensi dalam tumpukan kode, ia akan menemukan referensi V, dan terus melacak ke bawah, ia akan menemukan bahwa ada referensi ke objek objek dalam ruang memori yang ditunjukkan oleh referensi V. Dengan kata lain, meskipun referensi O telah kosong, masih ada referensi lain dalam objek objek dan dapat diakses, sehingga GC tidak dapat melepaskannya. Jika objek tidak berpengaruh pada program setelah loop ini, maka kami berpikir bahwa kebocoran memori terjadi dalam program Java ini.
Meskipun kebocoran memori Java kurang merusak kebocoran memori di C/C ++, program ini masih dapat berjalan secara normal dalam kebanyakan kasus kecuali untuk beberapa kasus di mana program macet. Namun, ketika perangkat seluler memiliki batasan ketat pada memori dan CPU, overflow memori Java akan menyebabkan inefisiensi program dan hunian dalam sejumlah besar memori yang tidak diinginkan. Ini akan menyebabkan kinerja seluruh mesin memburuk, dan dalam kasus yang parah itu juga akan menyebabkan outofmemoryError dilemparkan, menyebabkan program jatuh.
Hindari kebocoran memori secara umum
Secara umum, tanpa melibatkan struktur data yang kompleks, kebocoran memori Java terwujud sebagai siklus hidup objek memori melebihi lamanya waktu yang dibutuhkan program. Kami terkadang menyebutnya "bebas objek".
Misalnya:
Salinan kode adalah sebagai berikut:
PUBLIK PUBLIK PENELITIAN {
konten byte pribadi [];
file pribadi mfile;
Public Filesearch (File File) {
mfile = file;
}
hasstring boolean publik (string str) {
int size = getFileSize (mfile);
konten = byte baru [ukuran];
LoadFile (mfile, konten);
String s = string baru (konten);
return s.contains (str);
}
}
Dalam kode ini, ada fungsi Hasstring di kelas penelitian file untuk menentukan apakah dokumen tersebut berisi string yang ditentukan. Prosesnya adalah memuat mfile ke dalam memori terlebih dahulu dan kemudian membuat penilaian. Namun, masalahnya di sini adalah bahwa konten dinyatakan sebagai variabel instance, bukan variabel lokal. Jadi, setelah fungsi ini kembali, data seluruh file masih ada dalam memori. Jelas bahwa kita tidak lagi membutuhkan data ini di masa depan, yang mengarah pada pemborosan memori yang tidak masuk akal.
Untuk menghindari kebocoran memori dalam hal ini, kami diharuskan mengelola memori kami yang dialokasikan dengan pemikiran manajemen memori C/C ++. Pertama, ini adalah untuk mengklarifikasi ruang lingkup efektif objek memori sebelum mendeklarasikan referensi objek. Objek memori yang valid dalam suatu fungsi harus dinyatakan sebagai variabel lokal, dan mereka yang memiliki siklus hidup yang sama dengan instance kelas harus dinyatakan sebagai variabel instance ... dan seterusnya. Kedua, ingatlah untuk mengosongkan objek memori secara manual saat tidak lagi diperlukan.
Masalah kebocoran memori dalam struktur data yang kompleks
Dalam proyek aktual, kami sering menggunakan beberapa struktur data yang lebih kompleks untuk menyimpan informasi data yang diperlukan selama operasi program. Kadang -kadang, karena kompleksitas struktur data, atau kami memiliki beberapa kebutuhan khusus (misalnya, sebanyak mungkin informasi cache untuk meningkatkan kecepatan berjalan program, dll.), Sulit bagi kami untuk berurusan dengan data tersebut dalam struktur data. Pada saat ini, kita dapat menggunakan mekanisme khusus di Java untuk mencegah kebocoran memori.
Kami telah memperkenalkan sebelumnya bahwa mekanisme GC Java didasarkan pada mekanisme referensi yang melacak memori. Sebelum itu, referensi yang kami gunakan hanya mendefinisikan bentuk "objek o;". Faktanya, ini hanyalah situasi default dalam mekanisme referensi Java, dan ada beberapa metode referensi lain di samping itu. Dengan menggunakan mekanisme referensi khusus ini dan menggabungkan dengan mekanisme GC, kita dapat mencapai beberapa efek yang kita butuhkan.
Beberapa metode referensi di java
Ada beberapa cara mengutip di Java, yaitu: kutipan yang kuat, kutipan lembut, kutipan yang lemah dan kutipan virtual. Selanjutnya, pertama -tama kita memahami pentingnya metode kutipan ini secara rinci.
Kutipan yang kuat
Kutipan yang digunakan dalam konten yang kami perkenalkan sebelumnya adalah kutipan yang kuat, yang merupakan kutipan yang paling umum digunakan. Jika suatu objek memiliki referensi yang kuat, itu mirip dengan kebutuhan harian yang penting, dan pengumpul sampah tidak akan pernah mendaur ulangnya. Ketika ruang memori tidak mencukupi, mesin virtual Java lebih suka melemparkan kesalahan outofmemoryError untuk menyebabkan program mengakhiri secara tidak normal daripada mendaur ulang objek dengan referensi yang kuat untuk menyelesaikan masalah memori.
Softreference
Penggunaan khas kelas Softreference adalah untuk cache yang sensitif memori. Prinsip Softreference adalah untuk memastikan bahwa semua referensi lunak dihapus sebelum JVM melaporkan memori yang tidak mencukupi saat menyimpan referensi ke suatu objek. Poin kunci adalah bahwa pengumpul sampah dapat (atau mungkin tidak) melepaskan objek yang dapat diakses lunak saat runtime. Apakah suatu objek dibebaskan tergantung pada algoritma pengumpul sampah dan jumlah memori yang tersedia saat pengumpul sampah berjalan.
Referensi lemah
Penggunaan khas kelas lemah adalah untuk menormalkan pemetaan (pemetaan kanonikal). Selain itu, referensi yang lemah juga berguna untuk objek dengan masa hidup yang relatif panjang dan overhead rekreasi rendah. Titik kuncinya adalah bahwa jika objek yang dapat diakses dengan lemah ditemukan selama pengumpul sampah, objek yang dirujuk oleh Lemah Referensi akan dilepaskan. Namun, perhatikan bahwa pengumpul sampah mungkin harus berjalan beberapa kali sebelum dapat menemukan dan melepaskan objek yang dapat diakses dengan lemah.
Fantomreference
Kelas phantomreference hanya dapat digunakan untuk melacak koleksi objek yang direferensikan yang akan datang. Demikian juga, ini juga dapat digunakan untuk melakukan operasi kliring pra-mortem. Phantomreference harus digunakan dengan kelas ReferenceQueue. ReferenceQueue diperlukan karena dapat bertindak sebagai mekanisme pemberitahuan. Ketika pengumpul sampah menentukan bahwa suatu objek adalah objek akses virtual, objek phantomreference ditempatkan pada ReferenceQueue. Menempatkan objek phantomreference pada ReferenceQueue adalah pemberitahuan yang menunjukkan bahwa objek yang dirujuk oleh objek phantomreference telah berakhir dan tersedia untuk pengumpulan. Ini memungkinkan Anda untuk mengambil tindakan tepat sebelum memori yang ditempati oleh objek didaur ulang. Referensi dan ReferenceQueue digunakan bersama dengan ReferenceQueue.
GC, Referensi dan ReferenceQueue
A. GC tidak dapat menghapus memori objek dengan referensi yang kuat.
B. GC menemukan memori objek dengan hanya referensi lunak, lalu:
① Bidang referensi dari objek Softreference diatur ke null, sehingga objek tidak lagi mengacu pada objek heap.
② Objek heap yang dirujuk oleh Softreference dinyatakan sebagai finalzable.
③ Ketika metode finalisasi () dari objek heap dijalankan dan memori yang ditempati oleh objek dilepaskan, objek Softreference ditambahkan ke referenceQueue (jika ada yang terakhir).
C. GC menemukan memori objek dengan hanya referensi yang lemah, lalu:
① Bidang referensi dari objek Lemah Referensi diatur ke NULL, sehingga objek tidak lagi mengacu pada objek Heap.
② Objek tumpukan yang dirujuk oleh Lemah Referensi dinyatakan sebagai final.
③ Ketika metode finalize () dari objek heap dijalankan dan memori yang ditempati oleh objek dilepaskan, objek Lemah Referensi ditambahkan ke ReferenceQueue (jika yang terakhir ada).
D. GC menemukan memori objek yang hanya memiliki referensi virtual, lalu:
① Objek heap yang dirujuk oleh phantomreference dinyatakan sebagai final yang dapat difinalisasi.
② Phantomreference ditambahkan ke ReferenceQueue sebelum objek heap dilepaskan.
Poin -poin berikut patut dicatat:
1. GC tidak akan menemukan objek memori yang direferensikan lunak secara umum.
2. Penemuan dan pelepasan referensi lemah GC tidak segera.
3. Ketika referensi lunak dan referensi yang lemah ditambahkan ke ReferenceQueue, referensi untuk memori nyata telah diatur ke kosong, dan memori yang relevan telah dirilis. Saat menambahkan referensi virtual ke ReferenceQueue, memori belum dirilis dan masih dapat diakses.
Melalui pengantar di atas, saya percaya bahwa Anda memiliki pemahaman tertentu tentang mekanisme kutipan Java dan persamaan dan perbedaan beberapa metode kutipan. Suatu konsep mungkin terlalu abstrak.
Salinan kode adalah sebagai berikut:
String str = string baru ("halo"); // ①
ReferenceQueue <String> rq = ReferenceQueue baru <string> ();
Lemah Referensi <String> WF = Referensi Lemah Baru <String> (str, rq);
str = null; // ④Cancel Referensi kuat dari objek "Hello"
String str1 = wf.get (); // ⑤Jika objek "halo" tidak didaur ulang, STR1 mengacu pada objek "Hello"
// Jika objek "halo" tidak didaur ulang, rq.poll () mengembalikan null
Referensi <?
Dalam kode di atas, perhatikan kedua tempat ⑤⑥. Jika objek "halo" tidak didaur ulang wf.get () akan mengembalikan objek string "halo", rq.poll () akan mengembalikan null; dan objek "halo" telah didaur ulang, maka wf.get () akan kembali null, rq.poll () Mengembalikan objek referensi, tetapi tidak ada referensi untuk objek STR dalam objek referensi ini (phantomreference berbeda dari lemahnya refreferensi dan softreference).
Aplikasi bersama mekanisme kutipan dan struktur data yang kompleks
Dengan memahami mekanisme GC, mekanisme referensi, dan menggabungkan dengan ReferenceQueue, kami dapat menerapkan beberapa tipe data kompleks yang mencegah overflow memori.
Misalnya, Softreference memiliki karakteristik membangun sistem cache, sehingga kami dapat mengimplementasikan sistem cache sederhana dalam kombinasi dengan tabel hash. Ini tidak hanya memastikan bahwa banyak informasi dapat di -cache, tetapi juga memastikan bahwa mesin virtual Java tidak akan melempar outofmemoryError karena kebocoran memori. Mekanisme caching ini sangat cocok untuk situasi di mana objek memori memiliki siklus hidup yang panjang dan waktu untuk menghasilkan objek memori relatif panjang, seperti gambar penutup daftar cache, dll. Untuk beberapa kasus di mana siklus hidupnya panjang tetapi overhead menghasilkan objek memori tidak besar, menggunakan refreferensi lemah dapat mencapai manajemen memori yang lebih baik.
Salinan kode sumber SofthashMap dilampirkan.
Salinan kode adalah sebagai berikut:
paket com.
//: softhashmap.java
impor java.util.
impor java.lang.ref.
impor android.util.log;
SofthashMap kelas publik memperluas abstractmap {
/** Hashmap internal yang akan menahan softreference.
hash peta akhir pribadi = hashmap baru ();
/** Jumlah referensi "keras" untuk ditahan secara internal.
final private int hard_size;
/** Daftar Referensi Sulit FIFO, urutan akses terakhir.
Private Final LinkedList hardcache = new LinkedList ();
/** Antrian referensi untuk objek Softreference yang dibersihkan.
Private ReferenceQueue Queue = ReferenceQueue baru ();
// nomor referensi yang kuat
public softhashMap () {this (100);
public softhashMap (int hardsize) {hard_size = hardsize;
objek publik get (tombol objek) {
Hasil Objek = NULL;
// kami mendapatkan softreference yang diwakili oleh kunci itu
Softreference soft_ref = (softreference) hash.get (key);
if (soft_ref! = null) {
// dari softreference kita mendapatkan nilainya, yang bisa
// nol jika tidak ada di peta, atau dihapus
// Metode ProcessQueue () yang didefinisikan di bawah ini
hasil = soft_ref.get ();
if (result == null) {
// Jika nilainya telah dikumpulkan, lepaskan
// entri dari hashmap.
hash.remove (kunci);
} kalau tidak {
// Kami sekarang menambahkan objek ini ke awal yang sulit
// Antrian referensi.
// Sekali, karena pencarian antrian FIFO lambat, jadi
// Kami tidak ingin mencari -cari setiap kali untuk menghapus
// duplikat.
// Simpan objek Gunakan terbaru dalam memori
hardcache.addfirst (hasil);
if (hardcache.size ()> hard_size) {
// Hapus entri terakhir jika daftar lebih panjang dari hard_size
hardcache.removelast ();
}
}
}
hasil pengembalian;
}
/** Kami mendefinisikan subkelas kami sendiri dari Softreference yang mengandung
Tidak hanya nilai tetapi juga kunci untuk membuatnya lebih mudah ditemukan
Entri dalam hashmap setelah dikumpulkan sampah.
Private Static Class SoftValue memperluas softreference {
Kunci Objek Akhir Pribadi; // Selalu Membuat Final Anggota Data
/** Tahukah Anda bahwa kelas luar dapat mengakses data pribadi
Anggota dan metode kelas dalam? Saya tidak tahu itu!
Saya pikir itu hanya kelas dalam yang bisa mengakses
Informasi Pribadi Kelas Luar.
mengakses anggota pribadi kelas dalam di dalamnya
kelas. */
Private SoftValue (Objek K, Kunci Objek, ReferenceQueue Q) {
super (k, q);
ini .key = key;
}
}
/** di sini kita pergi melalui ReferenceQueue dan menghilangkan sampah
terkumpul objek value dari hashmap dengan melihat mereka
Menggunakan SoftValue.Key Data Anggota.
public void processqueue () {
Softvalue sv;
while ((sv = (softvalue) queue.poll ())! = null) {
if (sv.get () == null) {
Log.e ("Processqueue", "null");
} kalau tidak {
Log.e ("Processqueue", "bukan null");
}
hash.remove (sv.key); // kita dapat mengakses data pribadi!
Log.e ("softhashmap", "rilis" + sv.key);
}
}
/** di sini kami memasukkan kunci, nilai pasangan ke dalam hashmap menggunakan
objek softvalue.
Objek Publik Put (Kunci Objek, Nilai Objek) {
Processqueue (); // membuang nilai sampah terlebih dahulu
Log.e ("softhashmap", "dimasukkan ke dalam" + kunci);
return hash.put (kunci, softValue baru (nilai, kunci, antrian));
}
objek publik hapus (tombol objek) {
Processqueue (); // membuang nilai sampah terlebih dahulu
return hash.remove (kunci);
}
public void clear () {
hardcache.clear ();
Processqueue ();
hash.clear ();
}
ukuran int int () {
Processqueue (); // membuang nilai sampah terlebih dahulu
return hash.size ();
}
Entryset Set Publik () {
// Tidak, tidak, Anda mungkin tidak melakukan itu !!!
Lempar baru tidak didukung operasi ();
}
}