Saya sadar bahwa setiap objek membutuhkan memori tumpukan dan setiap primitif / referensi pada stack memerlukan memori tumpukan.
Ketika saya mencoba membuat objek di heap dan ada memori yang tidak cukup untuk melakukannya, JVM membuat java.lang.OutOfMemoryError pada heap dan melemparkannya ke saya.
Jadi secara implisit, ini berarti bahwa ada beberapa memori yang disediakan oleh JVM saat startup.
Apa yang terjadi ketika memori yang dicadangkan ini digunakan (pasti akan digunakan, baca diskusi di bawah) dan JVM tidak memiliki cukup memori pada heap untuk membuat instance java.lang.OutOfMemoryError ?
Apakah hanya menggantung? Atau apakah dia akan melemparkan saya null
karena tidak ada memori untuk new
instance OOM?
try {
Object o = new Object();
// and operations which require memory (well.. that's like everything)
} catch (java.lang.OutOfMemoryError e) {
// JVM had insufficient memory to create an instance of java.lang.OutOfMemoryError to throw to us
// what next? hangs here, stuck forever?
// or would the machine decide to throw us a "null" ? (since it doesn't have memory to throw us anything more useful than a null)
e.printStackTrace(); // e.printStackTrace() requires memory too.. =X
}
==
Mengapa JVM tidak dapat menyimpan memori yang cukup?
Tidak peduli berapa banyak memori yang dicadangkan, masih mungkin memori itu digunakan jika JVM tidak memiliki cara untuk "merebut kembali" memori itu:
try {
Object o = new Object();
} catch (java.lang.OutOfMemoryError e) {
// JVM had 100 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
// JVM had 99 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e3) {
// JVM had 98 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e4) {
// JVM had 97 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e5) {
// JVM had 96 units of "spare memory". 1 is used to create this OOM.
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e6) {
// JVM had 95 units of "spare memory". 1 is used to create this OOM.
e.printStackTrace();
//........the JVM can't have infinite reserved memory, he's going to run out in the end
}
}
}
}
}
}
Atau lebih ringkas:
private void OnOOM(java.lang.OutOfMemoryError e) {
try {
e.printStackTrace();
} catch (java.lang.OutOfMemoryError e2) {
OnOOM(e2);
}
}
sumber
OutOfMemoryException
dan kemudian melakukan sesuatu yang melibatkan pembuatan buffer besar ...OutOfMemoryError
dan mempertahankan referensi untuk itu. Terlihat bahwa menangkapOutOfMemoryError
tidak berguna seperti yang mungkin dipikirkan orang, karena Anda hampir tidak dapat memastikan keadaan program Anda saat menangkapnya. Lihat stackoverflow.com/questions/8728866/…Jawaban:
JVM tidak pernah benar-benar kehabisan memori. Itu perhitungan memori tumpukan tumpukan di muka.
The Struktur JVM, Bab 3 , bagian 3.5.2 negara:
Untuk Heap , Bagian 3.5.3.
Jadi, ia melakukan perhitungan terlebih dahulu sebelum melakukan alokasi objek.
Apa yang terjadi adalah bahwa JVM mencoba mengalokasikan memori untuk objek dalam memori yang disebut Permanent Generation region (atau PermSpace). Jika alokasi gagal (bahkan setelah JVM meminta Pengumpul Sampah untuk mencoba & mengalokasikan ruang kosong), itu melempar
OutOfMemoryError
. Bahkan pengecualian membutuhkan ruang memori sehingga kesalahan akan dibuang tanpa batas.Bacaan lebih lanjut. ? Selanjutnya,
OutOfMemoryError
dapat terjadi dalam struktur JVM yang berbeda .sumber
Graham Borland tampaknya benar : setidaknya JVM saya tampaknya menggunakan kembali OutOfMemoryErrors. Untuk menguji ini, saya menulis sebuah program tes sederhana:
Menjalankannya menghasilkan output ini:
BTW, JVM yang saya jalankan (di Ubuntu 10,04) adalah ini:
Sunting: Saya mencoba melihat apa yang akan terjadi jika saya memaksa JVM untuk kehabisan memori menggunakan program berikut:
Ternyata, itu tampaknya berulang selamanya. Namun, anehnya, mencoba untuk menghentikan program dengan Ctrl+ Ctidak berhasil, tetapi hanya memberikan pesan berikut:
Java HotSpot(TM) 64-Bit Server VM warning: Exception java.lang.OutOfMemoryError occurred dispatching signal SIGINT to handler- the VM may need to be forcibly terminated
sumber
n
OOM dan menggunakan kembali salah satunya setelah itu, sehingga OOM selalu dapat dilempar. Sun / JVM Oracle tidak mendukung rekursi ekor sama sekali IIRC?n
bingkai pada tumpukan dan akhirnya menciptakan dan menghancurkan bingkain+1
untuk selamanya, memberikan tampilan berlari tanpa henti.Sebagian besar lingkungan runtime akan melakukan pra-alokasi pada saat startup, atau sebaliknya cadangan, cukup memori untuk menangani situasi kelaparan memori. Saya membayangkan implementasi JVM paling waras akan melakukan ini.
sumber
catch
klausa Anda mencoba menggunakan lebih banyak memori, JVM bisa terus melempar instance OOM yang sama berulang-ulang.Terakhir kali saya bekerja di Java dan menggunakan debugger, inspektur heap menunjukkan bahwa JVM mengalokasikan instance OutOfMemoryError pada startup. Dengan kata lain, itu mengalokasikan objek sebelum program Anda memiliki kesempatan untuk mulai mengkonsumsi, apalagi kehabisan, memori.
sumber
Dari Spesifikasi JVM, Bab 3.5.2:
Setiap Java Virtual Machine harus menjamin bahwa ia akan melempar
OutOfMemoryError
. Itu menyiratkan, bahwa ia harus mampu membuat contohOutOfMemoryError
(atau harus dibuat di muka) bahkan jika tidak ada ruang tumpukan yang tersisa.Meskipun tidak harus menjamin, bahwa ada memori yang tersisa untuk menangkapnya dan mencetak stacktrace yang bagus ...
Tambahan
Anda menambahkan beberapa kode untuk ditampilkan, bahwa JVM mungkin kehabisan ruang tumpukan jika harus membuang lebih dari satu
OutOfMemoryError
. Tetapi implementasi seperti itu akan melanggar persyaratan dari atas.Tidak ada persyaratan bahwa instance yang dilemparkan
OutOfMemoryError
itu unik atau dibuat atas permintaan. JVM dapat menyiapkan tepat satu contohOutOfMemoryError
selama startup dan melemparkan ini setiap kali kehabisan ruang tumpukan - yang pernah, dalam lingkungan normal. Dengan kata lain: contohOutOfMemoryError
yang kita lihat bisa saja singleton.sumber
Pertanyaan menarik :-). Sementara yang lain telah memberikan penjelasan yang baik tentang aspek-aspek teoretis, saya memutuskan untuk mencobanya. Ini ada di Oracle JDK 1.6.0_26, Windows 7 64 bit.
Pengaturan tes
Saya menulis sebuah program sederhana untuk menghabiskan memori (lihat di bawah).
Program ini hanya membuat statis
java.util.List
, dan terus memasukkan string segar ke dalamnya, hingga OOM terlempar. Kemudian menangkapnya dan terus mengisi dalam loop tanpa akhir (JVM yang buruk ...).Hasil tes
Seperti yang dapat dilihat dari output, OOME empat kali pertama dilempar, ia datang dengan jejak tumpukan. Setelah itu, OOME berikutnya hanya mencetak
java.lang.OutOfMemoryError: Java heap space
jikaprintStackTrace()
dipanggil.Jadi rupanya JVM berusaha untuk mencetak jejak stack jika itu bisa, tetapi jika memori benar-benar ketat, itu hanya menghilangkan jejak, seperti jawaban yang disarankan lainnya.
Yang juga menarik adalah kode hash OOME. Perhatikan bahwa beberapa OOME pertama semuanya memiliki hash yang berbeda. Setelah JVM mulai menghilangkan jejak stack, hash selalu sama. Ini menunjukkan bahwa JVM akan menggunakan instance OOME baru (preallocated?) Selama mungkin, tetapi jika push datang untuk mendorong, itu hanya akan menggunakan kembali contoh yang sama daripada tidak memiliki apa pun untuk dilemparkan.
Keluaran
Catatan: Saya memotong beberapa jejak tumpukan untuk membuat output lebih mudah dibaca ("[...]").
Program
sumber
System.out
tetapiprintStackTrace()
digunakanSystem.err
secara default. Anda mungkin mendapatkan hasil yang lebih baik dengan menggunakan aliran mana pun secara konsisten.Saya cukup yakin, JVM akan memastikan bahwa ia memiliki setidaknya memori yang cukup untuk melempar pengecualian sebelum kehabisan memori.
sumber
Pengecualian yang menunjukkan upaya untuk melanggar batas-batas lingkungan memori terkelola ditangani oleh runtime dari lingkungan tersebut, dalam hal ini JVM. JVM adalah prosesnya sendiri, yang menjalankan IL aplikasi Anda. Jika suatu program mencoba untuk membuat panggilan yang memperpanjang tumpukan panggilan melampaui batas, atau mengalokasikan lebih banyak memori daripada yang dapat dipesan JVM, runtime itu sendiri akan menyuntikkan pengecualian, yang akan menyebabkan tumpukan panggilan dibatalkan. Terlepas dari jumlah memori yang dibutuhkan oleh program Anda saat ini, atau seberapa dalam tumpukan panggilannya, JVM akan mengalokasikan cukup memori dalam batas prosesnya sendiri untuk membuat pengecualian dan menyuntikkannya ke dalam kode Anda.
sumber
Anda tampaknya membingungkan memori virtual yang disediakan oleh JVM di mana JVM menjalankan program Java dengan memori asli OS host di mana JVM dijalankan sebagai proses asli. JVM pada mesin Anda berjalan di memori yang dikelola oleh OS, bukan di memori yang disediakan JVM untuk menjalankan program Java.
Bacaan lebih lanjut:
Dan sebagai catatan akhir, mencoba untuk menangkap sebuah java.lang.Error (dan kelas turunannya) untuk mencetak stacktrace mungkin tidak memberikan informasi yang berguna. Anda ingin tumpukan tumpukan sebagai gantinya.
sumber
Untuk lebih memperjelas jawaban @ Boraham Borland, secara fungsional, JVM melakukan hal ini pada saat startup:
Kemudian, JVM mengeksekusi salah satu bytecode Java berikut: 'baru', 'anewarray', atau 'multianewarray'. Instruksi ini menyebabkan JVM untuk melakukan sejumlah langkah dalam kondisi kehabisan memori:
allocate()
.allocate()
mencoba mengalokasikan memori untuk beberapa instance baru dari kelas atau array tertentu.doGC()
, yang mencoba melakukan pengumpulan sampah.allocate()
cobalah untuk mengalokasikan memori untuk instance sekali lagi.throw OOME;
, mengacu pada OOME yang dipakai saat startup. Perhatikan bahwa itu tidak harus mengalokasikan OOME itu, itu hanya merujuk padanya.Jelas, ini bukan langkah literal; mereka akan bervariasi dari JVM ke JVM dalam implementasi, tetapi ini adalah ide tingkat tinggi.
(*) Sejumlah besar pekerjaan terjadi di sini sebelum gagal. JVM akan berusaha untuk menghapus objek SoftReference, mencoba alokasi langsung ke generasi bertenor saat menggunakan kolektor generasi, dan mungkin hal-hal lain, seperti finalisasi.
sumber
Jawaban yang mengatakan bahwa JVM akan melakukan pra-alokasi
OutOfMemoryErrors
memang benar.Selain menguji ini dengan memprovokasi situasi kehabisan memori kita hanya dapat memeriksa tumpukan JVM (saya menggunakan program kecil yang hanya tidur, menjalankannya menggunakan Oracle Hotspot JVM dari Java 8 update 31).
Menggunakan
jmap
kami melihat bahwa tampaknya ada 9 contoh dari OutOfMemoryError (walaupun kami memiliki banyak memori):Kami kemudian dapat menghasilkan heap dump:
dan membukanya menggunakan Eclipse Memory Analyzer , di mana kueri OQL menunjukkan bahwa JVM sebenarnya pra-alokasi
OutOfMemoryErrors
untuk semua pesan yang mungkin:Kode untuk Java 8 Hotspot JVM yang sebenarnya preallocates ini dapat ditemukan di sini , dan terlihat seperti ini (dengan beberapa bagian dihilangkan):
dan kode ini menunjukkan bahwa JVM pertama-tama akan mencoba menggunakan salah satu kesalahan yang dialokasikan sebelumnya dengan ruang untuk jejak stack, dan kemudian kembali ke kesalahan tanpa jejak stack:
sumber
Metaspace
. Akan lebih baik jika Anda bisa menunjukkan sepotong kode yang juga melayani PermGen dan membandingkannya dengan Metaspace juga.