OutOfMemoryError atmak için yeterli bellek olmadığında ne olur?


207

Her nesnenin yığın bellek gerektirdiğinin ve yığın üzerindeki her ilkel / referansın yığın bellek gerektirdiğinin farkındayım.

Öbek üzerinde bir nesne oluşturmaya çalıştığımda ve bunu yapmak için yeterli bellek olmadığında, JVM öbek üzerinde bir java.lang.OutOfMemoryError oluşturur ve bana atar.

Örtük olarak, bu, başlangıçta JVM tarafından ayrılmış bir bellek olduğu anlamına gelir.

Bu ayrılmış bellek kullanıldığında (kesinlikle tükenir, aşağıdaki tartışmayı okur) ve JVM'nin yığın üzerinde java.lang.OutOfMemoryError örneği oluşturmak için yeterli belleği yoksa ne olur ?

Sadece asıyor mu? Yoksa bir OOM örneğinin nullhafızası olmadığından beni atar newmıydı?

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
}

==

JVM neden yeterli hafıza ayıramadı?

Ne kadar bellek ayrılmış olursa olsun, JVM'nin bu belleği "geri almanın" bir yolu yoksa, o belleğin kullanılması hala mümkündür:

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
                    }
                }
            }
        }
    }
}

Veya daha kısaca:

private void OnOOM(java.lang.OutOfMemoryError e) {
    try {
        e.printStackTrace();
    } catch (java.lang.OutOfMemoryError e2) {
        OnOOM(e2);
    }
}

2
cevabınız büyük ölçüde
JVM'ye

23
Bir zamanlar kullandığım bir telefon kütüphanesi (90'larda) OutOfMemoryExceptionve daha sonra büyük bir tampon oluşturmayı içeren bir şey
yapıyordum

@ TomHawtin-tackline Ya bunu yapmakla ilgili operasyonlar başka bir OOM fırlatırsa?
Pacerier

38
Bir cep telefonu gibi, pil de bitmek üzeredir, ancak "Pili bitiyordur" mesajlarını spam tutmak için yeterli pile sahiptir.
Kazuma

1
"Bu ayrılmış bellek tükendiğinde ne olur": bu ancak program ilkini yakaladığında OutOfMemoryErrorve ona bir referansı koruduğunda gerçekleşebilir. Bir programın yakalanmasının OutOfMemoryErrordüşündüğünüz kadar yararlı olmadığını söyler , çünkü programınızın yakalanmasıyla ilgili durumu hakkında neredeyse hiçbir şeyi garanti edemezsiniz . Bkz. Stackoverflow.com/questions/8728866/…
Raedwald

Yanıtlar:


145

JVM'nin belleği gerçekten hiç bitmez. Öbek yığınının önceden bellek hesaplamasını yapar.

JVM Yapısı, Bölüm 3 , bölüm 3.5.2 devletler:

  • Java sanal makine yığınları dinamik olarak genişletilebilirse ve genişletme denenirse, ancak genişletmeyi etkilemek için yetersiz bellek kullanılabilir hale gelirse veya yeni bir iş parçacığı için ilk Java sanal makine yığınını oluşturmak için yeterli bellek bulunamazsa, Java sanal makine atar OutOfMemoryError.

İçin Heap Bölüm 3.5.3.

  • Bir hesaplama, otomatik depolama yönetim sistemi tarafından kullanılabilecek olandan daha fazla yığın gerektiriyorsa, Java sanal makinesi bir OutOfMemoryError.

Böylece, nesnenin tahsisini yapmadan önce bir hesaplama yapar.


Olan şey, JVM'nin Kalıcı Nesil bölgesi (veya PermSpace) adlı bellekteki bir nesne için bellek ayırmaya çalışmasıdır. Tahsis başarısız olursa (JVM, Çöp Toplayıcıyı boş alan denemek ve ayırmak için çağırdıktan sonra bile), bir atar OutOfMemoryError. İstisnalar bile bir bellek alanı gerektirir, bu nedenle hata süresiz olarak atılır.

Daha fazla okuma. ? Ayrıca, OutOfMemoryErrorfarklı JVM yapısında meydana gelebilir .


10
Yani evet, ama Java sanal makinesinin OutOfMemoryError atmak için belleğe ihtiyacı olmaz mı? Bir OOM fırlatacak bellek yoksa ne olur?
Pacerier

5
Ancak, JVM referansı aynı OOM örneğine döndürmezse, sonunda ayrılmış belleğinin biteceğini kabul etmiyor musunuz? (sorudaki kodda gösterildiği gibi)
Pacerier

1
Burada Graham'ın yorumuna bir atıf yapmama izin verin: stackoverflow.com/questions/9261705/…
Pacerier

2
VM belirtilen aşırı durum için ve bir nükleer enerji santralinde tek bir OOM İstisnası tutarsa ​​iyi olabilir.
John K

8
@JohnK: Nükleer santrallerin tıpkı uzay mekiği ve Boeing 757'lerin Java'da programlanmadığı gibi Java'da programlanmadığını umuyorum.
Dietrich Epp

64

Graham Borland haklı gibi görünüyor : En az benim JVM görünüşte OutOfMemoryErrors-kullanımları yeniden. Bunu test etmek için basit bir test programı yazdım:

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);
    }
}

Çalıştırıldığında bu çıktı üretilir:

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

BTW, (Ubuntu 10.04'te) çalıştırdığım JVM şudur:

$ 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)

Düzenleme: Aşağıdaki programı kullanarak JVM tamamen bellek yetersiz çalıştırmak zorunda ne olacağını görmek için çalıştı :

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);
    }
}

Görünüşe göre, sonsuza kadar döngü gibi görünüyor. Ancak, merakla, programı Ctrl+ ile sonlandırmaya Cçalışmak işe yaramaz, ancak sadece aşağıdaki mesajı verir:

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


Güzel test, benim için aynı "1.7.0_01" Java HotSpot (TM) 64-Bit Sunucu VM
Pacerier

İlginç. Yani (... bazı kuyruk özyinelemeli yığın yeniden kullanımını yapıyor, ancak tüm hafızayı temizlemek için oldukça yeterli değilse gibi) JVM tamamen kuyruk özyinelemeye anlamıyor gibi ... görünmesini sağlar
Izkata

Kaç tane farklı kod aldığımı görmek için kodunuzu değiştirdim - her zaman tam olarak 5 kez diğer şubeyi çalıştırıyorum.
Irfy

@Izkata: OOM'ları önceden tahsis netmenin ve daha sonra bunlardan birini tekrar kullanmanın bilinçli bir karar olduğunu söyleyebilirim , böylece bir OOM her zaman atılabilir. Sun / Oracle'ın JVM'si tüm IIRC'de kuyruk tekrarını desteklemiyor mu?
Irfy

10
@Izkata JVM, bellek bittikten sonra sürekli olarak bir ve aynı (5. veya daha fazla) OOM atıyorsa, döngü görünüşte sonsuz çalışıyor. Böylece nyığın üzerinde çerçeveler var ve n+1sonsuzluk için çerçeve oluşturup yok ediyor ve sonsuz koşma görünümü veriyor.
Irfy

41

Çoğu çalışma zamanı ortamı, başlangıçta önceden ayrılacak veya bellek açlık durumlarıyla başa çıkmak için yeterli belleği ayıracaktır. Çoğu aklı başında JVM uygulamasının bunu yapacağını düşünüyorum.


1
stackoverflow.com/questions/9261215/… : Doğru ama JVM bunu yapmak için 100 birim bellek ayırdıysa ve ilk OOM için 1 birim harcadıysa, OOM catch bloğumda e.printStackTrace () yaparsam ne olur? e.printStackTrace () de bellek gerektirir. Sonra JVM bana başka bir OOM (98 birim kaldı) atmak için başka bir bellek harcayacaktı ve bunu bir e.printStackTrace () ile yakaladım, böylece JVM bana başka bir OOM (97 birim kaldı) atıyor ve yakalandı ve istedim ..
Pacerier

3
İşte tam olarak bu nedenle OOME hiçbir zaman yığın izlemeyi içermez - yığın izleri hafızayı alır! OOME yalnızca java 6'ya bir yığın izlemesi eklemeye başladı ( blogs.oracle.com/alanb/entry/… ). Yığın izleme mümkün değilse, istisna bir yığın izleme olmadan atılır varsayıyorum.
Sean Reilly

1
@SeanReilly Yığın izlemesi olmayan bir istisna, hala bellek gerektiren bir Nesne'dir. Yığın izinin sağlanıp sağlanmadığına bakılmaksızın bellek gerekir. Yakalama bloğunda bir OOM (yığın izlemesi olmadan bile bir tane oluşturmak için bellek yok) kalmamışsa, o zaman boş bir değer yakalayacağım doğru mu?
Pacerier

17
JVM, OOM özel durumunun tek bir statik örneğine birden çok başvuru döndürebilir. Dolayısıyla catch, hükmünüz daha fazla bellek kullanmaya çalışsa bile , JVM aynı OOM örneğini tekrar tekrar atmaya devam edebilir.
Graham Borland

1
@TheEliteGentleman Ben de çok iyi cevaplar olduğunu kabul ediyorum, ama JVM fiziksel bir makinede yaşıyor, Bu cevaplar JVM sihirli bir şekilde her zaman bir OOM örneği sağlamak için nasıl yeterli bellek olabilir açıklamıyordu. "Her zaman aynı örnek" bilmeceyi çözüyor gibi görünüyor.
Pacerier

23

Java'da en son çalıştığımda ve bir hata ayıklayıcı kullandığımda, yığın denetçisi JVM'nin başlangıçta bir OutOfMemoryError örneği tahsis ettiğini gösterdi. Başka bir deyişle, nesneyi, programınız bellek tükenmeksizin tüketmeye başlama şansına sahip olmadan ayırır.


12

JVM Spesifikasyonu, Bölüm 3.5.2:

Java sanal makine yığınları dinamik olarak genişletilebilirse ve genişletme denenirse, ancak genişletmeyi etkilemek için yetersiz bellek kullanılabilir hale gelirse veya yeni bir iş parçacığı için ilk Java sanal makine yığınını oluşturmak için yeterli bellek bulunamazsa , Java sanal makine atarOutOfMemoryError .

Her Java Sanal Makinesi bir atar garanti etmek zorundadır OutOfMemoryError. Bu, OutOfMemoryErroryığın alanı kalmasa bile bir örnek oluşturabilmesi (veya önceden yaratılmış olması) anlamına gelir.

Her ne kadar garanti etmek zorunda olmasa da, onu yakalamak ve güzel bir yığın izi yazdırmak için yeterli bellek var ...

İlave

JVM'nin birden fazla atmak zorunda kaldığında yığın alanının bitebileceğini göstermek için bazı kodlar eklediniz OutOfMemoryError. Ancak böyle bir uygulama, yukarıdaki gerekliliği ihlal edecektir.

Atılan örneklerin OutOfMemoryErrorbenzersiz olması veya talep üzerine yaratılmasına gerek yoktur . Bir JVM, OutOfMemoryErrorbaşlatma sırasında tam olarak bir örneğini hazırlayabilir ve bunu, bir zamanlar normal ortamda olan yığın alanından her çıktığında atabilir. Başka bir deyişle: OutOfMemoryErrorgördüğümüz örnek tekil olabilir.


Sanırım bunu uygulamak için, eğer alan dar olsaydı, yığın izlemekten kaçınmak zorunda kalacaktı.
Raedwald

@Raedwald: Aslında, Oracle VM'nin yaptığı da bu, cevabımı görün.
sleske

11

İlginç soru :-). Diğerleri teorik yönleri iyi açıklarken, denemeye karar verdim. Bu Oracle JDK 1.6.0_26, Windows 7 64 bit üzerinde.

Test kurulumu

Belleği boşaltmak için basit bir program yazdım (aşağıya bakın).

Program sadece bir statik oluşturur java.util.Listve OOM atılana kadar içine taze dizeler doldurmaya devam eder. Daha sonra onu yakalar ve sonsuz bir döngüde (zayıf JVM ...) doldurmaya devam eder.

Test sonucu

Çıktıdan görebileceğiniz gibi, ilk dört kez OOME atılır, bir yığın iziyle birlikte gelir. Bundan sonra, daha sonraki OOMEs sadece baskı java.lang.OutOfMemoryError: Java heap spaceeğer printStackTrace()çağrılır.

Görünüşe göre JVM, bir yığın izlemesini yazdırmak için çaba gösterir, ancak bellek gerçekten sıkıysa, diğer cevapların önerdiği gibi izini atlar.

Ayrıca ilginç OOME karma kodu. İlk birkaç OOME'un hepsinin farklı karmaları olduğunu unutmayın. JVM yığın izlerini atlamaya başladığında, karma her zaman aynıdır. Bu, JVM'nin mümkün olduğunca taze (önceden yerleştirilmiş?) OOME örnekleri kullanacağını, ancak itme kıpırsa, atmak için hiçbir şeye sahip olmak yerine aynı örneği tekrar kullanacağını gösterir.

Çıktı

Not: Çıktının daha kolay okunabilmesi için bazı yığın izlerini kısalttım ("[...]").

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);
            }
        }

    }
}

Push itmeye geldiğinde, OOME için bellek ayıramaz, bu yüzden zaten oluşturulmuş olanları temizler.
Buhake Sindi

1
Küçük not: Çıktınızın bir kısmı sıra dışı görünüyor, muhtemelen yazdırıyor System.outancak varsayılan olarak printStackTrace()kullanıyor System.err. Muhtemelen her iki akışı da tutarlı bir şekilde kullanarak daha iyi sonuçlar elde edersiniz.
Ilmari Karonen

@IlmariKaronen: Evet, bunu fark ettim. Bu sadece bir örnek, bu yüzden önemli değildi. Açıkçası üretim kodunda kullanmazsınız.
sleske

Doğru, sadece çıktıya ilk baktığımda **** neler olduğunu anlamak için biraz zaman aldı.
Ilmari Karonen

6

Eminim, JVM, bellek bitmeden önce bir istisna atmak için en az yeterli belleğe sahip olduğundan kesinlikle emin olacaktır.


1
Ancak ne kadar bellek ayrılmış olursa olsun bu duruma
çarparlar

4

Yönetilen bellek ortamının sınırlarını ihlal etme girişimini gösteren istisnalar, söz konusu ortamın, bu durumda JVM'nin çalışma zamanı tarafından ele alınır. JVM, uygulamanızın IL'sini çalıştıran kendi işlemidir. Bir program, çağrı yığınını sınırların ötesine uzatan bir çağrı yapmaya çalışırsa veya JVM'nin ayırabileceğinden daha fazla bellek ayırırsa, çalışma zamanının kendisi çağrı yığınının çözülmesine neden olacak bir istisna enjekte eder. Programınızın şu anda ihtiyaç duyduğu bellek miktarına veya çağrı yığınının ne kadar derin olduğuna bakılmaksızın, JVM, söz konusu istisna oluşturmak ve kodunuza enjekte etmek için kendi işlem sınırları içinde yeterli bellek tahsis etmiş olacaktır.


"JVM, söz konusu istisnayı oluşturmak için kendi işlem sınırları içinde yeterli bellek tahsis etmiş olacaktır" ancak kodunuz bu istisna için bir referans tutarsa, tekrar kullanılamazsa, nasıl başka bir tane oluşturabilir? Yoksa özel bir Singleton OOME nesnesi olduğunu mu düşünüyorsunuz?
Raedwald

Bunu önermiyorum. Programınız, JVM veya OS tarafından oluşturulan ve enjekte edilenler de dahil olmak üzere, şimdiye kadar oluşturulmuş her istisnayı yakalar ve devam ederse, JVM'nin kendisi OS tarafından belirlenen bir sınırı aşar ve işletim sistemi bir GPF veya benzer hata. Ancak, ilk etapta bu kötü tasarım; istisnalar ele alınmalı ve daha sonra kapsam dışına çıkarılmalı veya atılmalıdır. Ve ASLA bir SOE veya OOME'u yakalamaya ve devam ettirmeye çalışmamalısınız; "temizlemek" dışında, incelikle çıkabilmek için, bu durumlarda yürütmeye devam etmek için yapabileceğiniz hiçbir şey yoktur.
KeithS

"ilk etapta bu kötü tasarım": korkunç tasarım. Ancak bilgiçliksel olarak, JVM bu şekilde başarısız olursa spesifikasyona uygun mu?
Raedwald

4

JVM'nin Java programlarını çalıştırdığı JVM tarafından ayrılan sanal belleği, JVM'nin yerel bir işlem olarak çalıştırıldığı ana işletim sisteminin yerel belleği ile karıştırıyor gibi görünüyorsunuz. Makinenizdeki JVM, JVM'nin Java programlarını çalıştırmak için ayırdığı bellekte değil, işletim sistemi tarafından yönetilen bellekte çalışıyor.

Daha fazla okuma:

Ve son bir not olarak, bir yığın izi yazdırmak için bir java.lang.Error (ve onun alt sınıflarını) yakalamaya çalışmak size yararlı bilgiler vermeyebilir. Bunun yerine bir yığın dökümü istiyorsunuz.


4

@Graham Borland'ın cevabını işlevsel olarak daha da netleştirmek için JVM bunu başlangıçta yapar:

private static final OutOfMemoryError OOME = new OutOfMemoryError();

Daha sonra, JVM şu Java bayt kodlarından birini yürütür: 'yeni', 'anewarray' veya 'multianewarray'. Bu talimat, JVM'nin yetersiz bellek durumunda birkaç adım gerçekleştirmesine neden olur:

  1. Yerel bir işlev çağırın, deyin allocate() . allocate()belirli bir sınıf veya dizinin yeni bir örneği için bellek ayırmaya çalışır.
  2. Bu ayırma isteği başarısız olur, bu nedenle JVM başka bir yerel işlevi çağırır. doGC() , çöp toplama işlemini yapmaya çalışan .
  3. Bu işlev döndüğünde, allocate() örnek için bir kez daha bellek ayırmaya çalışır.
  4. Bu başarısız olursa (*), tahsis () içindeki JVM, throw OOME; başlangıçta somutlaştırıldığı OOME'ye atıfta bulunarak . OOME tahsis etmek zorunda olmadığını unutmayın, sadece bunu ifade eder.

Açıkçası, bunlar gerçek adımlar değildir; uygulamada JVM'den JVM'ye değişecektir, ancak bu üst düzey fikirdir.

(*) Başarısız olmadan önce burada önemli miktarda çalışma gerçekleşir. JVM, SoftReference nesnelerini temizlemeye, yeni nesil bir toplayıcı kullanırken doğrudan kalıcı üretime tahsis etmeye ve muhtemelen sonlandırma gibi başka şeylere çalışmaya çalışacaktır.


3

JVM'nin ön tahsis edeceğini söyleyen cevaplar OutOfMemoryErrorsgerçekten doğrudur.
Bellek yetersiz bir durumu provoke ederek bunu test etmenin yanı sıra, herhangi bir JVM'nin yığınını da kontrol edebiliriz (Java 8 güncellemesinden 31 Oracle Hotspot JVM'yi kullanarak sadece uyku yapan küçük bir program kullandım).

kullanma jmap 9 OutOfMemoryError örneğinin göründüğünü görüyoruz (bol miktarda belleğimiz olmasına rağmen):

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

Daha sonra bir yığın dökümü oluşturabiliriz:

> jmap -dump: biçim = b, dosya = heap.hprof 12315

ve bir OQL sorgusunun JVM'nin aslında tüm olası mesajlar için önceden ayrıldığını gösterdiği Eclipse Memory Analyzer'ı kullanarak açın OutOfMemoryErrors:

resim açıklamasını buraya girin

Bunları gerçekten önceden konumlandıran Java 8 Hotspot JVM'nin kodu burada bulunabilir ve şu şekilde görünür (bazı bölümler atlanmıştır):

...
// 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;
}
...

ve bu kod , JVM'nin önce bir yığın izlemesi için önceden ayrılmış hatalardan birini boşlukla kullanmaya çalışacağını ve daha sonra yığın izlemesi olmadan geriye düşeceğini gösterir:

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;
  }
}

İyi gönderi, bir önceki cevaba geri dönmeden önce daha fazla görünürlük sağlamak için bunu bir hafta boyunca cevap olarak kabul edeceğim.
Pacerier

Java 8 ve sonraki sürümlerde, Kalıcı Nesil Alanı tamamen kaldırılmıştır ve artık dinamik sınıf meta veri bellek yönetimi adı verilen Java 8 ve üstü için yığın alanı ayırma bellek boyutu belirtemezsiniz Metaspace. PermGen'e hitap eden bir kod parçası gösterebilir ve Metaspace ile de karşılaştırabilirsiniz.
Buhake Sindi

@BuhakeSindi - Daimi Nesil'in bununla ne ilgisi olduğunu anlamıyorum. Yanıtınızda belirttiğiniz gibi Kalıcı Nesil'de yeni nesneler ayrılmaz. Ayrıca, OutOfMemoryErrors öğesinin önceden ayrıldığından (sorunun asıl cevabıdır) asla bahsetmezsiniz.
Johan Kaving

1
Tamam, ben söylüyorum Java 8, nesne ayırma dinamik olarak hesaplanır iken, önceden tanımlanmış bir yığın alanı tahsis önce, bu nedenle belki önceden önceden tahsis edilir. OOME önceden tahsis edilmiş olsa da, OOME'nin atılması gerekip gerekmediğini belirlemek için yapılan "hesaplama" vardır (bu yüzden neden JLS spesifikasyonuna atıfta bulunuyorum).
Buhake Sindi

1
Java yığın boyutu, Java 8'de önceden olduğu gibi önceden tanımlanmıştır. Kalıcı Nesil, sınıf meta verileri, stajyer dizeleri ve sınıf statiklerini içeren yığının bir parçasıydı. Toplam yığın boyutundan ayrı olarak ayarlanması gereken sınırlı bir boyutu vardı. Java 8'de sınıf meta verileri yerel belleğe, stajyer dizeleri ve sınıf statik değerleri normal Java yığınına taşınmıştır (bkz. Örneğin: infoq.com/articles/Java-PERMGEN-Removed ).
Johan Kaving
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.