Apa yang terjadi ketika ada memori yang tidak cukup untuk melempar OutOfMemoryError?

207

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 nullkarena tidak ada memori untuk newinstance 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);
    }
}
Pacerier
sumber
2
jawaban Anda akan sangat tergantung pada JVM
MozenRath
23
Satu perpustakaan telepon yang pernah saya gunakan (tahun 90-an) digunakan untuk menangkap OutOfMemoryExceptiondan kemudian melakukan sesuatu yang melibatkan pembuatan buffer besar ...
Tom Hawtin - tackline
@ TomHawtin-tackline Bagaimana jika operasi yang terlibat dalam melakukan itu melempar OOM lain?
Pacerier
38
Seperti halnya ponsel, baterai kehabisan tetapi baterai ini cukup untuk menjaga spam "Anda kehabisan baterai".
Kazuma
1
"Apa yang terjadi ketika memori yang dicadangkan ini habis": itu bisa terjadi hanya jika program menangkap yang pertama OutOfMemoryErrordan mempertahankan referensi untuk itu. Terlihat bahwa menangkap OutOfMemoryErrortidak berguna seperti yang mungkin dipikirkan orang, karena Anda hampir tidak dapat memastikan keadaan program Anda saat menangkapnya. Lihat stackoverflow.com/questions/8728866/…
Raedwald

Jawaban:

145

JVM tidak pernah benar-benar kehabisan memori. Itu perhitungan memori tumpukan tumpukan di muka.

The Struktur JVM, Bab 3 , bagian 3.5.2 negara:

  • Jika tumpukan mesin virtual Java dapat diperluas secara dinamis, dan ekspansi dilakukan tetapi memori yang tidak mencukupi dapat dibuat untuk mempengaruhi ekspansi, atau jika memori yang tidak cukup dapat dibuat tersedia untuk membuat tumpukan mesin virtual Java awal untuk utas baru, Java virtual mesin melempar OutOfMemoryError.

Untuk Heap , Bagian 3.5.3.

  • Jika perhitungan membutuhkan lebih banyak tumpukan daripada yang dapat disediakan oleh sistem manajemen penyimpanan otomatis, mesin virtual Java melempar OutOfMemoryError .

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, OutOfMemoryErrordapat terjadi dalam struktur JVM yang berbeda .

Buhake Sindi
sumber
10
Maksudku ya, tetapi bukankah mesin virtual Java juga perlu memori untuk melempar OutOfMemoryError? Apa yang terjadi ketika tidak ada memori untuk melempar OOM?
Pacerier
5
Tetapi jika JVM tidak mengembalikan referensi ke contoh OOM yang sama, tidakkah Anda setuju bahwa pada akhirnya itu akan kehabisan memori yang dicadangkan? (seperti yang ditunjukkan dalam kode dalam pertanyaan)
Pacerier
1
Izinkan saya untuk memberi referensi pada komentar Graham di sini: stackoverflow.com/questions/9261705/…
Pacerier
2
Mungkin lebih baik jika VM mempertahankan singleton Pengecualian OOM untuk kasus ekstrim yang dinyatakan, dan di pembangkit listrik tenaga nuklir.
John K
8
@ JohnK: Saya berharap pembangkit listrik tenaga nuklir tidak diprogram di Jawa, seperti pesawat ulang-alik dan Boeing 757 tidak diprogram di Jawa.
Dietrich Epp
64

Graham Borland tampaknya benar : setidaknya JVM saya tampaknya menggunakan kembali OutOfMemoryErrors. Untuk menguji ini, saya menulis sebuah program tes sederhana:

class OOMTest {
    private static void test (OutOfMemoryError o) {
        try {
            for (int n = 1; true; n += n) {
                int[] foo = new int[n];
            }
        } catch (OutOfMemoryError e) {
            if (e == o)
                System.out.println("Got the same OutOfMemoryError twice: " + e);
            else test(e);
        }
    }
    public static void main (String[] args) {
        test(null);
    }
}

Menjalankannya menghasilkan output ini:

$ javac OOMTest.java && java -Xmx10m OOMTest 
Got the same OutOfMemoryError twice: java.lang.OutOfMemoryError: Java heap space

BTW, JVM yang saya jalankan (di Ubuntu 10,04) adalah ini:

$ java -version
java version "1.6.0_26"
Java(TM) SE Runtime Environment (build 1.6.0_26-b03)
Java HotSpot(TM) 64-Bit Server VM (build 20.1-b02, mixed mode)

Sunting: Saya mencoba melihat apa yang akan terjadi jika saya memaksa JVM untuk kehabisan memori menggunakan program berikut:

class OOMTest2 {
    private static void test (int n) {
        int[] foo;
        try {
            foo = new int[n];
            test(n * 2);
        }
        catch (OutOfMemoryError e) {
            test((n+1) / 2);
        }
    }
    public static void main (String[] args) {
        test(1);
    }
}

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

Ilmari Karonen
sumber
Tes yang bagus, sama untuk saya dengan versi "1.7.0_01" Java HotSpot (TM) 64-Bit Server VM
Pacerier
Menarik. Itu membuatnya tampak seperti JVM tidak sepenuhnya memahami rekursi ekor ... (Seolah-olah itu melakukan beberapa penggunaan tumpukan ekor rekursif, tetapi tidak cukup untuk membersihkan semua memori ...)
Izkata
Modifikasi kode Anda untuk melihat berapa banyak yang berbeda yang saya dapatkan - Saya selalu menjalankan cabang lain persis 5 kali.
Irfy
@Izkata: Saya lebih suka mengatakan itu adalah keputusan yang sadar untuk melakukan pra-alokasi nOOM dan menggunakan kembali salah satunya setelah itu, sehingga OOM selalu dapat dilempar. Sun / JVM Oracle tidak mendukung rekursi ekor sama sekali IIRC?
Irfy
10
@Izkata Loop ini berjalan tanpa henti karena JVM terus-menerus melempar satu dan sama (5 atau lebih) OOM setelah kehabisan memori. Jadi ia memiliki nbingkai pada tumpukan dan akhirnya menciptakan dan menghancurkan bingkai n+1untuk selamanya, memberikan tampilan berlari tanpa henti.
Irfy
41

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.

Graham Borland
sumber
1
stackoverflow.com/questions/9261215/… : Benar tetapi jika JVM mencadangkan 100 unit memori untuk melakukan itu, dan menghabiskan 1 unit pada OOM pertama, apa yang terjadi jika dalam blok tangkapan OOM saya, saya melakukan e.printStackTrace ()? e.printStackTrace () membutuhkan memori juga. Kemudian JVM akan menghabiskan unit memori lain untuk melemparkan saya OOM lain (98 unit tersisa) dan saya menangkapnya dengan e.printStackTrace () sehingga JVM melempar saya OOM lain (97 unit tersisa) dan juga yang ditangkap dan saya ingin ..
Pacerier
3
Inilah sebabnya mengapa OOME tidak pernah menggunakan jejak stack - jejak stack mengambil memori! OOME hanya mulai berusaha untuk memasukkan jejak tumpukan di java 6 ( blogs.oracle.com/alanb/entry/… ). Saya berasumsi bahwa jika jejak stack tidak mungkin, maka pengecualian dilemparkan tanpa jejak stack.
Sean Reilly
1
@SeanReilly Maksudku pengecualian yang tidak memiliki jejak stack masih merupakan Object, yang masih membutuhkan memori. Memori diperlukan terlepas dari apakah jejak stack disediakan. Apakah benar bahwa di blok tangkap jika tidak ada memori yang tersisa untuk membuat OOM (tidak ada memori untuk membuat bahkan satu tanpa jejak stack), maka saya akan menangkap nol?
Pacerier
17
JVM dapat mengembalikan banyak referensi ke instance statis tunggal pengecualian OOM. Jadi, bahkan jika catchklausa Anda mencoba menggunakan lebih banyak memori, JVM bisa terus melempar instance OOM yang sama berulang-ulang.
Graham Borland
1
@TheEliteGentleman Saya setuju bahwa itu adalah jawaban yang sangat bagus juga, tetapi JVM hidup dengan mesin fisik, Jawaban itu tidak menjelaskan bagaimana JVM secara ajaib dapat memiliki memori yang cukup untuk selalu memberikan contoh OOM. "Itu selalu contoh yang sama" tampaknya memecahkan teka-teki.
Pacerier
23

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.

benzado
sumber
12

Dari Spesifikasi JVM, Bab 3.5.2:

Jika tumpukan mesin virtual Java dapat diperluas secara dinamis, dan ekspansi dilakukan tetapi memori yang tidak mencukupi dapat tersedia untuk memengaruhi ekspansi, atau jika memori yang tidak cukup dapat dibuat tersedia untuk membuat tumpukan mesin virtual Java awal untuk utas baru, Java virtual mesin melemparOutOfMemoryError .

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 OutOfMemoryErroritu unik atau dibuat atas permintaan. JVM dapat menyiapkan tepat satu contoh OutOfMemoryErrorselama startup dan melemparkan ini setiap kali kehabisan ruang tumpukan - yang pernah, dalam lingkungan normal. Dengan kata lain: contoh OutOfMemoryErroryang kita lihat bisa saja singleton.

Andreas Dolk
sumber
Saya kira untuk mengimplementasikan ini harus menahan diri dari merekam stack-trace, jika ruang sempit.
Raedwald
@ Raedwald: Sebenarnya, ini yang dilakukan oleh Oracle VM, lihat jawaban saya.
sleske
11

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 spacejika printStackTrace()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 ("[...]").

iteration 0
iteration 100000
iteration 200000
iteration 300000
iteration 400000
iteration 500000
iteration 600000
iteration 700000
iteration 800000
iteration 900000
iteration 1000000
iteration 1100000
iteration 1200000
iteration 1300000
iteration 1400000
iteration 1500000
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1069480624
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.Arrays.copyOf(Unknown Source)
    at java.util.ArrayList.ensureCapacity(Unknown Source)
    at java.util.ArrayList.add(Unknown Source)
    at testsl.Div.gobbleUpMemory(Div.java:23)
    at testsl.Div.exhaustMemory(Div.java:12)
    at testsl.Div.main(Div.java:7)
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 616699029
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 2136955031
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Unknown Source)
[...]
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1535562945
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
java.lang.OutOfMemoryError: Java heap space
Ouch: java.lang.OutOfMemoryError: Java heap space; hash: 1734048134
Keep on trying...
[...]

Program

public class Div{
    static java.util.List<String> list = new java.util.ArrayList<String>();

    public static void main(String[] args) {
        exhaustMemory();
    }

    private static void exhaustMemory() {
        try {
            gobbleUpMemory();
        } catch (OutOfMemoryError e) {
            System.out.println("Ouch: " + e+"; hash: "+e.hashCode());
            e.printStackTrace();
            System.out.println("Keep on trying...");
            exhaustMemory();
        }
    }

    private static void gobbleUpMemory() {
        for (int i = 0; i < 10000000; i++) {
            list.add(new String("some random long string; use constructor to force new instance"));
            if (i % 10000000== 0) {
                System.out.println("iteration "+i);
            }
        }

    }
}
sleske
sumber
Ketika push datang untuk mendorong, itu tidak dapat mengalokasikan memori untuk OOME, sehingga memerah yang sudah dibuat.
Buhake Sindi
1
Catatan kecil: beberapa output Anda tampaknya tidak berurutan, mungkin karena Anda mencetak System.outtetapi printStackTrace()digunakan System.errsecara default. Anda mungkin mendapatkan hasil yang lebih baik dengan menggunakan aliran mana pun secara konsisten.
Ilmari Karonen
@IlmariKaronen: Ya, saya perhatikan itu. Itu hanya contoh, jadi tidak masalah. Jelas Anda tidak akan menggunakannya dalam kode produksi.
sleske
Benar, hanya butuh beberapa saat untuk mencari tahu apa yang terjadi ketika saya pertama kali melihat hasilnya.
Ilmari Karonen
6

Saya cukup yakin, JVM akan memastikan bahwa ia memiliki setidaknya memori yang cukup untuk melempar pengecualian sebelum kehabisan memori.

Oscar Gomez
sumber
1
Tetapi mereka akan menghadapi situasi ini tidak peduli berapa banyak memori yang dicadangkan: stackoverflow.com/questions/9261705/…
Pacerier
4

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.

KeithS
sumber
"JVM akan mengalokasikan cukup memori dalam batas-batas prosesnya sendiri untuk membuat pengecualian tersebut" tetapi jika kode Anda tetap menggunakan referensi untuk pengecualian itu, sehingga tidak dapat digunakan kembali, bagaimana ia bisa membuat yang lain? Atau Anda menyarankan memiliki objek Singleton OOME khusus?
Raedwald
Saya tidak menyarankan itu. Jika program Anda terperangkap dan bergantung pada setiap pengecualian yang pernah dibuat, termasuk yang dibuat dan disuntikkan oleh JVM atau OS, maka pada akhirnya JVM itu sendiri akan melampaui batas yang ditetapkan oleh OS, dan OS akan mematikannya untuk GPF atau kesalahan serupa. Namun, itu desain yang buruk di tempat pertama; pengecualian harus ditangani dan kemudian keluar dari ruang lingkup, atau dibuang. Dan Anda TIDAK PERNAH mencoba menangkap dan melanjutkan dengan SOE atau OOME; selain "membersihkan" sehingga Anda dapat keluar dengan anggun, tidak ada yang dapat Anda lakukan untuk melanjutkan eksekusi dalam situasi tersebut.
KeithS
"itu desain yang buruk di tempat pertama": desain yang mengerikan. Tetapi secara pedas, apakah JVM akan sesuai dengan spesifikasi jika gagal dengan cara itu?
Raedwald
4

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.

Michael Tiffany
sumber
4

Untuk lebih memperjelas jawaban @ Boraham Borland, secara fungsional, JVM melakukan hal ini pada saat startup:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

Kemudian, JVM mengeksekusi salah satu bytecode Java berikut: 'baru', 'anewarray', atau 'multianewarray'. Instruksi ini menyebabkan JVM untuk melakukan sejumlah langkah dalam kondisi kehabisan memori:

  1. Meminta fungsi asli, katakanlah allocate(). allocate()mencoba mengalokasikan memori untuk beberapa instance baru dari kelas atau array tertentu.
  2. Permintaan alokasi itu gagal, sehingga JVM memanggil fungsi asli lain, misalnya doGC(), yang mencoba melakukan pengumpulan sampah.
  3. Ketika fungsi itu kembali, allocate()cobalah untuk mengalokasikan memori untuk instance sekali lagi.
  4. Jika itu gagal (*), maka JVM, dalam mengalokasikan (), cukup melakukan a 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.

ahawtho
sumber
3

Jawaban yang mengatakan bahwa JVM akan melakukan pra-alokasi OutOfMemoryErrorsmemang 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):

> jmap -histo 12103 | grep OutOfMemoryError
 71: 9 288 java.lang.OutOfMemoryError
170: 1 32 [Ljava.lang.OutOfMemoryError;

Kami kemudian dapat menghasilkan heap dump:

> jmap -dump: format = b, file = heap.hprof 12315

dan membukanya menggunakan Eclipse Memory Analyzer , di mana kueri OQL menunjukkan bahwa JVM sebenarnya pra-alokasi OutOfMemoryErrorsuntuk semua pesan yang mungkin:

masukkan deskripsi gambar di sini

Kode untuk Java 8 Hotspot JVM yang sebenarnya preallocates ini dapat ditemukan di sini , dan terlihat seperti ini (dengan beberapa bagian dihilangkan):

...
// Setup preallocated OutOfMemoryError errors
k = SystemDictionary::resolve_or_fail(vmSymbols::java_lang_OutOfMemoryError(), true, CHECK_false);
k_h = instanceKlassHandle(THREAD, k);
Universe::_out_of_memory_error_java_heap = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_class_metaspace = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_array_size = k_h->allocate_instance(CHECK_false);
Universe::_out_of_memory_error_gc_overhead_limit =
  k_h->allocate_instance(CHECK_false);

...

if (!DumpSharedSpaces) {
  // These are the only Java fields that are currently set during shared space dumping.
  // We prefer to not handle this generally, so we always reinitialize these detail messages.
  Handle msg = java_lang_String::create_from_str("Java heap space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_java_heap, msg());

  msg = java_lang_String::create_from_str("Metaspace", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_metaspace, msg());
  msg = java_lang_String::create_from_str("Compressed class space", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_class_metaspace, msg());

  msg = java_lang_String::create_from_str("Requested array size exceeds VM limit", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_array_size, msg());

  msg = java_lang_String::create_from_str("GC overhead limit exceeded", CHECK_false);
  java_lang_Throwable::set_message(Universe::_out_of_memory_error_gc_overhead_limit, msg());

  msg = java_lang_String::create_from_str("/ by zero", CHECK_false);
  java_lang_Throwable::set_message(Universe::_arithmetic_exception_instance, msg());

  // Setup the array of errors that have preallocated backtrace
  k = Universe::_out_of_memory_error_java_heap->klass();
  assert(k->name() == vmSymbols::java_lang_OutOfMemoryError(), "should be out of memory error");
  k_h = instanceKlassHandle(THREAD, k);

  int len = (StackTraceInThrowable) ? (int)PreallocatedOutOfMemoryErrorCount : 0;
  Universe::_preallocated_out_of_memory_error_array = oopFactory::new_objArray(k_h(), len, CHECK_false);
  for (int i=0; i<len; i++) {
    oop err = k_h->allocate_instance(CHECK_false);
    Handle err_h = Handle(THREAD, err);
    java_lang_Throwable::allocate_backtrace(err_h, CHECK_false);
    Universe::preallocated_out_of_memory_errors()->obj_at_put(i, err_h());
  }
  Universe::_preallocated_out_of_memory_error_avail_count = (jint)len;
}
...

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:

oop Universe::gen_out_of_memory_error(oop default_err) {
  // generate an out of memory error:
  // - if there is a preallocated error with backtrace available then return it wth
  //   a filled in stack trace.
  // - if there are no preallocated errors with backtrace available then return
  //   an error without backtrace.
  int next;
  if (_preallocated_out_of_memory_error_avail_count > 0) {
    next = (int)Atomic::add(-1, &_preallocated_out_of_memory_error_avail_count);
    assert(next < (int)PreallocatedOutOfMemoryErrorCount, "avail count is corrupt");
  } else {
    next = -1;
  }
  if (next < 0) {
    // all preallocated errors have been used.
    // return default
    return default_err;
  } else {
    // get the error object at the slot and set set it to NULL so that the
    // array isn't keeping it alive anymore.
    oop exc = preallocated_out_of_memory_errors()->obj_at(next);
    assert(exc != NULL, "slot has been used already");
    preallocated_out_of_memory_errors()->obj_at_put(next, NULL);

    // use the message from the default error
    oop msg = java_lang_Throwable::message(default_err);
    assert(msg != NULL, "no message");
    java_lang_Throwable::set_message(exc, msg);

    // populate the stack trace and return it.
    java_lang_Throwable::fill_in_stack_trace_of_preallocated_backtrace(exc);
    return exc;
  }
}
Johan Kaving
sumber
Posting yang bagus, saya akan menerima ini sebagai jawaban selama seminggu untuk memberikan lebih banyak visibilitas sebelum kembali ke jawaban sebelumnya.
Pacerier
Di Java 8 dan lebih tinggi, Ruang Generasi Permanen telah dihapus sepenuhnya dan Anda tidak lagi dapat menentukan ukuran memori alokasi ruang tumpukan dari Java 8 dan lebih tinggi karena mereka telah memperkenalkan manajemen memori metadata kelas dinamis yang disebut Metaspace. Akan lebih baik jika Anda bisa menunjukkan sepotong kode yang juga melayani PermGen dan membandingkannya dengan Metaspace juga.
Buhake Sindi
@BuhakeSindi - Saya tidak melihat apa yang harus dilakukan oleh Generasi Permanen dengan ini. Objek baru tidak dialokasikan dalam Generasi Permanen seperti yang Anda nyatakan dalam jawaban Anda. Anda juga tidak pernah menyebutkan fakta bahwa OutOfMemoryErrors sudah dialokasikan sebelumnya (yang merupakan jawaban aktual untuk pertanyaan).
Johan Kaving
1
Ok, apa yang saya katakan adalah bahwa dari Java 8, alokasi objek dihitung secara dinamis sedangkan sebelum dialokasikan pada ruang tumpukan yang telah ditentukan, maka mungkin itu telah dialokasikan sebelumnya. Meskipun OOME sudah dialokasikan sebelumnya, ada "perhitungan" yang dilakukan untuk menentukan apakah OOME perlu dibuang (oleh karena itu mengapa saya merujuk pada spesifikasi JLS).
Buhake Sindi
1
Ukuran heap Java sama seperti yang telah ditentukan di Java 8 seperti sebelumnya. Generasi Permanen adalah bagian dari heap yang berisi meta-data kelas, string yang diinternir dan statika kelas. Itu memiliki ukuran terbatas yang perlu disetel secara terpisah dari ukuran tumpukan total. Di Java 8 meta-data kelas telah dipindahkan ke memori asli dan string yang diinternir dan statika kelas telah dipindahkan ke heap Java reguler (lihat misalnya di sini: infoq.com/articles/Java-PERMGEN-Removed ).
Johan Kaving