Optimalisasi tumpukan dan memori
Hari ini saya menguji fungsi penyortiran data otomatis suatu proyek, memilah puluhan ribu catatan dan gambar dalam database. Ketika operasi mendekati akhir, java.lang.outofmemoryError, kesalahan dalam ruang tumpukan java terungkap. Di masa lalu, saya jarang mengalami kesalahan memori seperti itu dalam program penulisan, karena Java memiliki mekanisme pengumpul sampah, jadi saya belum terlalu memperhatikannya. Hari ini saya mencari beberapa informasi secara online dan menyelesaikannya berdasarkan ini.
1. Tumpukan dan Tumpukan
Heap-built dengan pengumpul sampah baru bertanggung jawab untuk mendaur ulang
1. Ketika program mulai berjalan, JVM mendapatkan memori dari OS, bagiannya adalah heap memori. Heap memori biasanya diatur ke atas di bagian bawah alamat penyimpanan.
2. Tumpukan adalah area data "runtime", dan objek yang dipakai di kelas mengalokasikan ruang dari tumpukan;
3. Mengalokasikan ruang pada tumpukan dibuat melalui instruksi seperti "baru". Tumpukan adalah ukuran memori yang dialokasikan secara dinamis, dan masa pakai tidak perlu diberitahu kepada kompiler terlebih dahulu;
4. Tidak seperti C ++, Java secara otomatis mengelola tumpukan dan tumpukan, dan pengumpul sampah dapat secara otomatis mendaur ulang heap memori yang tidak lagi digunakan;
5. Kerugiannya adalah karena memori secara dinamis dialokasikan pada saat runtime, kecepatan akses memori lebih lambat.
Stack - Simpan tipe dasar dan jenis referensi, cepat
1. Struktur data pertama dan kemudian biasanya digunakan untuk menyimpan parameter dan variabel lokal dalam metode ini;
2. Di Java, semua variabel tipe dasar (pendek, int, panjang, byte, float, double, boolean, char) dan jenis referensi disimpan dalam tumpukan;
3. Ruang hidup data dalam tumpukan umumnya dalam lingkup saat ini (area yang dikelilingi oleh {...};
4. Kecepatan akses tumpukan lebih cepat dari tumpukan, kedua setelah register yang terletak langsung di CPU;
5. Data dalam tumpukan dapat dibagikan, dan beberapa referensi dapat menunjuk ke alamat yang sama;
6. Kerugiannya adalah bahwa ukuran data dan masa pakai tumpukan harus ditentukan dan tidak memiliki fleksibilitas.
2. Pengaturan memori
1. Periksa status memori mesin virtual
long maxcontrol = runtime.getRuntime (). maxMemory (); // Dapatkan jumlah maksimum memori yang dapat dikontrol mesin virtual long CurrentUse = runtime.getRuntime (). TotalMemory (); // Dapatkan jumlah memori yang saat ini digunakan oleh mesin virtual ()
Secara default, maxControl = 66650112b = 63.5625m mesin virtual java;
Jika Anda tidak melakukan apa pun, CurrentUse diukur pada mesin saya = 5177344b = 4.9375m;
2. Perintah untuk mengatur ukuran memori
-Xms <Size> Set Awal Java Heap Ukuran: Atur ukuran memori heap inisialisasi JVM; Nilai ini dapat diatur sama dengan -xmx untuk menghindari memori redistribusi JVM setiap kali pengumpulan sampah selesai.
-Xmx <siece> Setel ukuran java heap maksimum: atur ukuran memori heap maksimum JVM;
-Xmn <Size>: Tetapkan ukuran generasi muda, seluruh ukuran tumpukan = ukuran generasi muda + ukuran generasi lama + ukuran generasi terakhir.
-XSS <SIECE> Atur ukuran tumpukan benang java: Atur ukuran memori tumpukan jvm;
3. Operasi spesifik (1) Pengaturan memori JVM:
Buka Myeclipse (Eclipse) Window-Preferences-Java JRES-Edit-Default VM Argumen
Masukkan: -xmx128m -xms64m -xmn32m -xss16m
(2) Pengaturan memori IDE:
Ubah konfigurasi di bawah -vmargs di myeclipse.ini (atau eclipse.ini di direktori root eclipse):
(3) Pengaturan memori Tomcat
Buka folder bin di direktori root Tomcat dan edit catalina.bat
Modifikasi ke: atur java_opts = -xms256m -xmx512m
3. Analisis Kesalahan OutofMemoryError di Java Heap
Ketika JVM dimulai, heap memori yang ditetapkan oleh parameter -xms digunakan. Saat program berlanjut dan membuat lebih banyak objek, JVM mulai memperluas memori heap untuk menampung lebih banyak objek. JVM juga menggunakan pengumpul sampah untuk mendaur ulang memori. Ketika memori heap maksimum yang ditetapkan oleh -xmx hampir tercapai, jika tidak ada lagi memori yang dapat dialokasikan ke objek baru, JVM akan melempar java.lang.outofmemoryError dan program akan macet. Sebelum melempar outofmemoryError, JVM akan mencoba membebaskan ruang yang cukup dengan pengumpul sampah, tetapi akan membuang kesalahan ini ketika menemukan bahwa masih ada cukup ruang. Untuk mengatasi masalah ini, Anda harus lebih jelas tentang informasi tentang objek program, seperti objek mana yang telah dibuat, objek mana yang menempati berapa banyak ruang, dll. Anda dapat menggunakan profiler atau penganalisa tumpukan untuk menangani kesalahan outofmemoryError. "Java.lang.outofmemoryError: Java Heap Space" berarti bahwa tumpukan tidak memiliki ruang yang cukup dan tidak dapat terus berkembang. "Java.lang.outofmemoryError: Permgen Space" berarti bahwa generasi permanen penuh, dan program Anda tidak dapat lagi memuat kelas atau mengalokasikan string.
4. Koleksi tumpukan dan sampah
Kita tahu bahwa benda -benda dibuat dalam memori tumpukan, pengumpulan sampah adalah proses yang membersihkan benda mati dari ruang tumpukan dan mengembalikan memori ini ke tumpukan. Untuk menggunakan pengumpul sampah, tumpukan sebagian besar dibagi menjadi tiga area, yaitu generasi baru, generasi lama atau generasi bertenor, dan ruang perm. Generasi Baru adalah ruang yang digunakan untuk menyimpan objek yang baru dibuat dan digunakan saat objek baru dibuat. Jika digunakan untuk waktu yang lama, mereka akan dipindahkan ke generasi lama (atau generasi bertenor) oleh pengumpul sampah. Ruang Perm adalah tempat JVM menyimpan data meta, seperti kelas, metode, kumpulan string, dan detail tingkat kelas.
5. Ringkasan:
1. Java Heap Memory adalah bagian dari memori yang dialokasikan untuk JVM oleh sistem operasi.
2. Saat kami membuat objek, mereka disimpan dalam memori java heap.
3. Untuk memfasilitasi pengumpulan sampah, ruang tumpukan java dibagi menjadi tiga area, yang disebut generasi baru, generasi lama atau generasi bertenor, dan ruang perm.
4. Anda dapat menyesuaikan ukuran ruang tumpukan java dengan menggunakan opsi baris perintah JVM -xms, -xmx, dan -xmn.
5. Anda dapat menggunakan jconsole atau runtime.maxmemory (), runtime.totalMemory (), dan runtime.freememory () untuk melihat ukuran memori tumpukan di java.
6. Anda dapat menggunakan perintah "JMAP" untuk mendapatkan tumpukan tumpukan, dan menggunakan "Jhat" untuk menganalisis tumpukan tumpukan.
7. Java Heap Space berbeda dari ruang tumpukan. Ruang tumpukan digunakan untuk menyimpan tumpukan panggilan dan variabel lokal.
8. Kolektor sampah Java digunakan untuk merebut kembali memori yang ditempati oleh benda mati (objek yang tidak lagi digunakan) dan melepaskannya ke ruang tumpukan Java.
9. Saat bertemu java.lang.outofmemoryError, Anda tidak perlu khawatir. Terkadang Anda hanya perlu meningkatkan ruang tumpukan. Tetapi jika itu sering terjadi, Anda harus melihat apakah ada kebocoran memori dalam program Java.
10. Gunakan Alat Analisis Profiler dan Heap Dump untuk melihat ruang tumpukan Java, dan Anda dapat melihat berapa banyak memori yang dialokasikan untuk setiap objek.
Penjelasan terperinci tentang penyimpanan tumpukan
Java Stack Storage memiliki karakteristik berikut:
1. Ukuran data dan siklus hidup dalam tumpukan harus ditentukan.
Misalnya, penyimpanan tipe dasar: int a = 1; Variabel ini berisi nilai literal, A adalah referensi ke tipe int, menunjuk pada nilai literal 3. Karena ukuran dan masa pakai data literal ini, nilai -nilai literal ini secara fokus didefinisikan dalam blok program, dan setelah blok program keluar, nilai -nilai literal menghilang), ada di tumpukan demi kecepatan mengejar.
2. Data yang ada di tumpukan dapat dibagikan.
(1) Penyimpanan Data Jenis Dasar:
menyukai:
int a = 3; int b = 3;
Kompiler pertama proses int a = 3; Pertama akan membuat referensi ke variabel A di tumpukan, dan kemudian cari tahu apakah ada alamat dengan nilai literal 3. Jika tidak ditemukan, itu akan membuka alamat dengan nilai literal 3, dan kemudian titik A ke alamat 3. Kemudian proses int B = 3; Setelah membuat variabel referensi B, karena sudah ada nilai literal 3 di tumpukan, B secara langsung menunjuk ke alamat 3. Dengan cara ini, A dan B keduanya menunjuk ke 3 pada saat yang sama.
Catatan: Referensi literal ini berbeda dari objek kelas. Dengan asumsi bahwa referensi dua objek kelas menunjuk ke suatu objek pada saat yang sama, jika satu variabel referensi objek mengubah keadaan internal objek, variabel referensi objek lainnya segera mencerminkan perubahan ini. Sebaliknya, memodifikasi nilainya melalui referensi literal tidak akan menyebabkan nilai lain diubah sesuai. Seperti dalam contoh di atas, setelah kita mendefinisikan nilai -nilai a dan b, biarkan a = 4; Kemudian, B tidak akan sama dengan 4, atau sama dengan 3. Di dalam kompiler, ketika a = 4 ditemui, itu akan mencari kembali apakah ada nilai literal 4 di tumpukan. Jika tidak, buka kembali alamat untuk menyimpan nilai 4; Jika sudah ada, langsung tunjuk A ke alamat ini. Oleh karena itu, perubahan nilai A tidak akan mempengaruhi nilai b.
(2) Pengemasan Penyimpanan Data:
Kelas yang membungkus tipe data dasar yang sesuai, seperti integer, ganda, string, dll. Semua data kelas ini ada di tumpukan. Java menggunakan pernyataan baru () untuk menampilkan kompiler dan hanya menciptakan secara dinamis sesuai kebutuhan saat runtime, sehingga lebih fleksibel, tetapi kerugiannya adalah memakan waktu lebih banyak.
Misalnya: ambil string sebagai contoh.
String adalah data pengemasan khusus. Artinya, dapat dibuat dalam bentuk string str = string baru ("ABC"); atau dapat dibuat dalam bentuk string str = "abc";. Yang pertama adalah proses pembuatan kelas standar, yaitu, di Java, semuanya adalah objek, dan objek adalah instance dari kelas, semua dibuat dalam bentuk baru (). Beberapa kelas di Java, seperti kelas DateFormat, dapat mengembalikan kelas yang baru dibuat melalui metode getInstance () kelas, yang tampaknya melanggar prinsip ini. Sebenarnya, bukan itu masalahnya. Kelas ini menggunakan pola singleton untuk mengembalikan instance kelas, tetapi contoh ini dibuat di dalam kelas melalui baru (), yang menyembunyikan detail ini dari luar.
Lalu mengapa instance tidak dibuat melalui baru () di string str = "abc";? Apakah itu dilanggar prinsip di atas? Sebenarnya tidak ada.
Tentang karya internal string str = "ABC". Java secara internal mengubah pernyataan ini menjadi langkah -langkah berikut:
A. Pertama -tama tentukan variabel referensi objek bernama str ke kelas string: string str;
B. Temukan apakah ada alamat dengan nilai "ABC" di tumpukan. Jika tidak, buka alamat dengan nilai literal "ABC", lalu buat objek baru O dari kelas string, dan arahkan nilai string O ke alamat ini, dan perhatikan objek referensi o di sebelah alamat ini di tumpukan. Jika ada alamat dengan nilai "ABC", cari objek O dan kembalikan alamat O.
C. Arahkan STR ke alamat objek O.
Perlu dicatat bahwa biasanya nilai string di kelas string disimpan secara langsung. Tetapi dalam situasi seperti string str = "ABC";, nilai stringnya memegang referensi ke data yang ada di tumpukan (mis.: String str = "ABC"; baik penyimpanan tumpukan dan penyimpanan tumpukan).
Untuk menggambarkan masalah ini dengan lebih baik, kita dapat memverifikasi melalui kode -kode berikut.
String str1 = "ABC"; String str2 = "ABC"; System.out.println (str1 == str2); //BENAR
(Nilai kebenaran dikembalikan hanya jika kedua referensi menunjuk ke objek yang sama. Adalah STR1 dan STR2 yang menunjuk ke objek yang sama)
Hasilnya menunjukkan bahwa JVM membuat dua referensi STR1 dan STR2, tetapi hanya satu objek yang dibuat, dan kedua referensi menunjukkan objek ini.
String str1 = "ABC"; String str2 = "ABC"; str1 = "bcd"; System.out.println (str1 + "," + str2); // BCD, ABC System.out.println (str1 == str2); //PALSU
Ini berarti bahwa perubahan dalam penugasan menghasilkan perubahan dalam referensi objek kelas, STR1 menunjuk ke objek baru lainnya, sementara STR2 masih menunjuk ke objek asli. Dalam contoh di atas, ketika kita mengubah nilai STR1 menjadi "BCD", JVM menemukan bahwa tidak ada alamat untuk menyimpan nilai dalam tumpukan, sehingga membuka alamat ini dan membuat objek baru yang nilai stringnya menunjuk ke alamat ini.
Faktanya, kelas string dirancang untuk menjadi kelas yang tidak dapat diubah. Jika Anda ingin mengubah nilainya, Anda bisa, tetapi JVM diam -diam membuat objek baru berdasarkan nilai baru saat runtime (tidak dapat diubah berdasarkan memori asli), dan kemudian mengembalikan alamat objek ini ke referensi kelas asli. Meskipun proses pembuatan ini sepenuhnya otomatis, ia membutuhkan lebih banyak waktu. Dalam lingkungan yang lebih sensitif terhadap persyaratan waktu, itu akan memiliki efek samping tertentu.
String str1 = "ABC"; String str2 = "ABC"; str1 = "bcd"; String str3 = str1; System.out.println (str3); // bcd string str4 = "bcd"; System.out.println (str1 == str4); //BENAR
Referensi ke objek STR3 secara langsung menunjuk ke objek yang ditunjukkan oleh STR1 (perhatikan bahwa STR3 tidak membuat objek baru). Setelah STR1 mengubah nilainya, buat referensi string STR4 dan arahkan ke objek baru yang dibuat oleh STR1 memodifikasi nilai. Dapat ditemukan bahwa kali ini STR4 tidak membuat objek baru, sehingga menyadari berbagi data di tumpukan lagi.
String str1 = string baru ("ABC"); String str2 = "ABC"; System.out.println (str1 == str2); //PALSUDua referensi dibuat. Dua objek dibuat. Dua referensi menunjuk ke dua objek yang berbeda.
String str1 = "ABC"; String str2 = string baru ("ABC"); System.out.println (str1 == str2); //PALSUDua referensi dibuat. Dua objek dibuat. Dua referensi menunjuk ke dua objek yang berbeda.
Dua kode di atas menunjukkan bahwa selama objek dibuat dengan baru (), itu akan dibuat di tumpukan, dan stringnya disimpan secara terpisah. Bahkan jika mereka sama dengan data di tumpukan, mereka tidak akan dibagikan dengan data di tumpukan.
Meringkaskan:
(1) Ketika kami mendefinisikan kelas menggunakan format seperti string str = "ABC";, kami selalu menerima begitu saja bahwa kami membuat objek STR dari kelas string. Khawatir tentang jebakan! Objek mungkin belum dibuat! Satu -satunya hal yang pasti adalah bahwa referensi ke kelas string dibuat. Adapun apakah referensi ini menunjuk ke objek baru, itu harus dipertimbangkan berdasarkan konteks, kecuali jika Anda menggunakan metode baru () untuk secara eksplisit membuat objek baru. Oleh karena itu, lebih tepatnya, kami membuat variabel referensi STR ke objek kelas string, yang menunjuk ke kelas string dengan nilai "ABC". Menyadari hal ini sangat membantu dalam pemecahan masalah bug yang sulit dalam program.
(2) menggunakan string str = "ABC"; Dapat meningkatkan kecepatan berjalan program sampai batas tertentu, karena JVM akan secara otomatis memutuskan apakah perlu membuat objek baru berdasarkan situasi aktual data dalam tumpukan. Untuk kode string str = string baru ("ABC");, objek baru dibuat dalam tumpukan terlepas dari apakah nilai string mereka sama atau tidak, apakah perlu membuat objek baru, sehingga meningkatkan beban pada program.
(3) Karena sifat abadi dari kelas string (karena nilai kelas pembungkus tidak dapat dimodifikasi), ketika variabel string perlu sering diubah, kelas StringBuffer harus dipertimbangkan untuk meningkatkan efisiensi program.