web-dev-qa-db-fra.com

Capture de Java.lang.OutOfMemoryError?

Documentation pour Java.lang.Error dit:

Une erreur est une sous-classe de Throwable qui indique des problèmes graves qu’une application raisonnable ne devrait pas tenter de détecter.

Mais comme Java.lang.Error est une sous-classe de Java.lang.Throwable, Je peux attraper ce type de Throwable.

Je comprends pourquoi ce n’est pas une bonne idée d’attraper ce genre d’exception. Autant que je sache, si nous décidons de l'attraper, le gestionnaire de captures ne devrait pas allouer de mémoire par lui-même. Sinon, OutOfMemoryError sera à nouveau lancé.

Donc, ma question est:

  1. Existe-t-il des scénarios réels lorsque vous attrapez Java.lang.OutOfMemoryError pourrait être une bonne idée?
  2. Si nous décidons d'attraper Java.lang.OutOfMemoryError, comment pouvons-nous nous assurer que le gestionnaire de captures n'alloue aucune mémoire par lui-même (outils ou meilleures pratiques)?
93
Denis Bazhenov

Je suis d'accord et en désaccord avec la plupart des réponses ici.

Il existe un certain nombre de scénarios dans lesquels vous pouvez souhaiter capturer une OutOfMemoryError et, d'après mon expérience (sur les machines virtuelles Java et Solaris), très rarement OutOfMemoryError sonnera le glas d'une machine virtuelle Java.

Il n’ya qu’une seule bonne raison d’attraper un OutOfMemoryError: c’est de fermer en douceur, de libérer proprement les ressources et de consigner au mieux la raison de l’échec (s’il est encore possible de le faire).

En général, OutOfMemoryError est dû à une allocation de mémoire de bloc qui ne peut pas être satisfaite avec les ressources restantes du segment de mémoire.

Lorsque le Error est lancé, le segment de mémoire contient le même nombre d'objets alloués qu'avant l'attribution infructueuse et il est maintenant temps de supprimer les références aux objets d'exécution pour libérer encore plus de mémoire nécessaire au nettoyage. Dans ces cas, il peut même être possible de continuer, mais ce serait certainement une mauvaise idée, car vous ne pouvez jamais être sûr à 100% que la JVM est dans un état réparable.

Démonstration que OutOfMemoryError ne signifie pas que la machine virtuelle Java manque de mémoire dans le bloc catch:

private static final int MEGABYTE = (1024*1024);
public static void runOutOfMemory() {
    MemoryMXBean memoryBean = ManagementFactory.getMemoryMXBean();
    for (int i=1; i <= 100; i++) {
        try {
            byte[] bytes = new byte[MEGABYTE*500];
        } catch (Exception e) {
            e.printStackTrace();
        } catch (OutOfMemoryError e) {
            MemoryUsage heapUsage = memoryBean.getHeapMemoryUsage();
            long maxMemory = heapUsage.getMax() / MEGABYTE;
            long usedMemory = heapUsage.getUsed() / MEGABYTE;
            System.out.println(i+ " : Memory Use :" + usedMemory + "M/" + maxMemory + "M");
        }
    }
}

Sortie de ce code:

1 : Memory Use :0M/247M
..
..
..
98 : Memory Use :0M/247M
99 : Memory Use :0M/247M
100 : Memory Use :0M/247M

Si vous exécutez quelque chose de critique, j’attrape généralement le Error, le connecte à syserr, puis le consulte à l’aide du cadre de journalisation de mon choix, puis procède à la libération des ressources et ferme le système. Quel est le pire qui puisse arriver? La machine virtuelle est en train de mourir (ou est déjà morte) de toute façon et en attrapant le Error, il y a au moins une chance de nettoyage.

La mise en garde est que vous devez cibler la capture de ces types d'erreurs uniquement dans les endroits où le nettoyage est possible. Ne couvrez pas catch(Throwable t) {} partout ou un non-sens comme ça.

75
Chris

Vous pouvez en récupérer:

package com.stackoverflow.q2679330;

public class Test {

    public static void main(String... args) {
        int size = Integer.MAX_VALUE;
        int factor = 10;

        while (true) {
            try {
                System.out.println("Trying to allocate " + size + " bytes");
                byte[] bytes = new byte[size];
                System.out.println("Succeed!");
                break;
            } catch (OutOfMemoryError e) {
                System.out.println("OOME .. Trying again with 10x less");
                size /= factor;
            }
        }
    }

}

Mais cela a-t-il un sens? Que voudriez-vous faire d'autre? Pourquoi voudriez-vous initialement allouer autant de mémoire? Est-ce que moins de mémoire est aussi OK? Pourquoi ne l'utilisez-vous pas déjà de toute façon? Ou, si cela n’est pas possible, pourquoi ne pas simplement donner plus de mémoire à la JVM dès le début?

Retour à vos questions:

1: Y a-t-il de vrais scénarios Word lors de la capture de Java.lang.OutOfMemoryError peut être une bonne idée?

Aucun ne vient à l'esprit.

2: si nous interceptons Java.lang.OutOfMemoryError, comment pouvons-nous nous assurer que le gestionnaire d'attrape n'alloue aucune mémoire par lui-même (aucun outil ni aucune meilleure pratique)?

Cela dépend de ce qui a causé l’OOME. Si elle est déclarée en dehors du bloc try et que cela se soit produit étape par étape, vos chances sont faibles. Vous pouvez voulez réserver un peu d’espace mémoire au préalable:

private static byte[] reserve = new byte[1024 * 1024]; // Reserves 1MB.

puis mettez-le à zéro pendant OOME:

} catch (OutOfMemoryException e) {
     reserve = new byte[0];
     // Ha! 1MB free!
}

Bien sûr, cela n’a aucun sens;) Donnez simplement à JVM suffisamment de mémoire pour répondre aux besoins de votre application. Exécutez un profileur si nécessaire.

29
BalusC

En général, c'est une mauvaise idée d'essayer d'attraper et de récupérer d'un MOO.

  1. Un OOME peut également avoir été jeté sur d'autres threads, y compris des threads que votre application ne connaît même pas. Tous ces fils seront maintenant morts et tout ce qui attendait une notification pourrait être bloqué pour toujours. En bref, votre application pourrait être endommagée de manière définitive.

  2. Même si vous récupérez avec succès, votre machine virtuelle Java risque toujours de souffrir de la famine et votre application fonctionnera alors de manière catastrophique.

La meilleure chose à faire avec un OOME est de laisser la JVM mourir.

(Cela suppose que la machine virtuelle Java fait mourir. Par exemple, les MOO sur un thread de servlet Tomcat ne suppriment pas la machine virtuelle Java, ce qui conduit le Tomcat à entrer dans un état catatonique dans lequel il ne répond à aucune demande, pas même à une demande de redémarrage.)

[~ # ~] éditer [~ # ~]

Je ne dis pas que c’est une mauvaise idée d’attraper OOM. Les problèmes se posent lorsque vous tentez ensuite de vous en sortir, soit délibérément, soit par inadvertance. Chaque fois que vous attrapez un MOO (directement ou en tant que sous-type Error ou Throwable), vous devez soit le renverser, soit demander à ce que l'application/la machine virtuelle JVM se ferme.

De côté: cela suggère que pour une robustesse maximale face aux MOO, une application doit utiliser Thread.setDefaultUncaughtExceptionHandler () pour définir un gestionnaire qui entraînera la fermeture de l'application dans le cas d'un OOME, quel que soit le résultat. enfiler le OOME est jeté. Je serais intéressé par des avis sur ce sujet ...

Le seul autre scénario possible est lorsque vous savez à coup sûr que le MOO n'a causé aucun dommage collatéral; c'est-à-dire que vous savez:

  • ce qui a spécifiquement causé l'OOME,
  • ce que l’application faisait à l’époque, et qu’il est acceptable de simplement écarter ce calcul, et
  • qu'un OOME (à peu près) simultané ne peut s'être produit sur un autre thread.

Il existe des applications où il est possible de connaître ces informations, mais pour la plupart des applications, vous ne pouvez pas savoir avec certitude que la poursuite après une opération OOME est sûre. Même si cela fonctionne "de manière empirique" lorsque vous l'essayez.

(Le problème, c'est qu'une preuve formelle est nécessaire pour montrer que les conséquences des OOME "anticipés" sont sûres et que les OOME "imprévues" ne peuvent pas se produire sous le contrôle d'un OOME à essayer/attraper.)

14
Stephen C

Oui, il existe des scénarios du monde réel. Voici le mien: Je dois traiter des ensembles de données contenant de très nombreux éléments sur un cluster avec une mémoire limitée par nœud. Une instance donnée de machine virtuelle Java parcourt de nombreux éléments l'un après l'autre, mais certains éléments sont trop volumineux pour être traités sur le cluster: je peux saisir le OutOfMemoryError et prendre en note les éléments trop volumineux. Plus tard, je ne pourrai ré-exécuter que les gros articles sur un ordinateur disposant de plus de RAM.

(Etant donné que c'est une allocation d'un tableau sur plusieurs gigaoctets qui échoue, la machine virtuelle Java fonctionne toujours bien après la détection de l'erreur et la mémoire est suffisante pour traiter les autres éléments.)

13
Michael Kuhn

Il existe certainement des scénarios dans lesquels attraper un OOME est logique. IDEA) les intercepte et ouvre une boîte de dialogue vous permettant de modifier les paramètres de la mémoire de démarrage (puis se ferme une fois que vous avez terminé). Un serveur d'applications peut les intercepter et les signaler. La clé de cette opération est faites-le à un niveau élevé dans la répartition afin d'avoir une chance raisonnable de libérer un groupe de ressources au moment où vous attrapez l'exception.

En plus du scénario IDEA ci-dessus, en général, la capture devrait être Throwable, pas uniquement OOM, et devrait être effectuée dans un contexte où au moins le thread sera arrêté sous peu.

Bien sûr, la plupart du temps, la mémoire est affamée et la situation ne peut pas être récupérée, mais il y a des façons de faire sens.

10
Yishai

Je suis tombé sur cette question parce que je me demandais si c’était une bonne idée d’attraper OutOfMemoryError dans mon cas. Je réponds en partie ici pour montrer encore un autre exemple lorsque saisir cette erreur peut avoir un sens pour quelqu'un (c'est-à-dire moi) et en partie pour savoir si c'est une bonne idée dans mon cas en effet (avec moi étant un développeur très subalterne, je soyez trop sûr de toute ligne de code que j'écris).

Quoi qu'il en soit, je travaille sur une application Android qui peut être exécutée sur différents périphériques avec différentes tailles de mémoire. La partie dangereuse consiste à décoder une image dans un fichier et à la supprimer dans une instance ImageView. I Je ne veux pas limiter les appareils les plus puissants en termes de taille de bitmap décodé, ni être sûr que l'application ne sera pas exécutée sur un appareil ancien que je n'ai jamais rencontré avec une mémoire très faible. :

BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); 
bitmapOptions.inSampleSize = 1;
boolean imageSet = false;
while (!imageSet) {
  try {
    image = BitmapFactory.decodeFile(filePath, bitmapOptions);
    imageView.setImageBitmap(image); 
    imageSet = true;
  }
  catch (OutOfMemoryError e) {
    bitmapOptions.inSampleSize *= 2;
  }
}

Ainsi, je parviens à fournir des appareils plus puissants et moins puissants en fonction de leurs besoins et attentes, ou de ceux de leurs utilisateurs.

8
Maria

Oui, la vraie question est "qu'allez-vous faire dans le gestionnaire d'exceptions?" Pour presque tout ce qui est utile, vous allouerez plus de mémoire. Si vous souhaitez effectuer un travail de diagnostic lorsqu'une erreur OutOfMemoryError se produit, vous pouvez utiliser la commande -XX:OnOutOfMemoryError=<cmd> crochet fourni par la machine virtuelle HotSpot. Il exécutera vos commandes lorsqu'une erreur OutOfMemoryError se produira et vous pourrez effectuer une opération utile en dehors du segment de mémoire de Java. Vous voulez vraiment que l'application ne manque pas de mémoire, c'est donc la première étape. Vous pouvez ensuite augmenter la taille de segment de mémoire de MaxPermSize selon vos besoins. Voici quelques autres points utiles HotSpot:

-XX:+PrintCommandLineFlags
-XX:+PrintConcurrentLocks
-XX:+PrintClassHistogram

Voir la liste complète ici

5
Rob Heiser

J'ai une application qui doit récupérer des erreurs OutOfMemoryError. Dans les programmes à un seul thread, cela fonctionne toujours, mais parfois pas dans les programmes à plusieurs threads. L'application est un Java) automatisé qui exécute les séquences de test générées à la profondeur maximale possible pour les classes de test. L'interface utilisateur doit maintenant être stable, mais le moteur de test peut manquer de mémoire tout en augmentant l’arborescence des cas de test, que je traite par le type d’idiome de code suivant dans le moteur de test:

 boolean isOutOfMemory = false; // indicateur utilisé pour signaler 
 try {
 SomeType largeVar; 
 // La boucle principale qui alloue de plus en plus à largeVar 
 // peut mettre fin à OK ou lever OutOfMemoryError 
} 
 Catch (OutOfMemoryError ex) {
 // largeVar est maintenant hors de portée, de même que garbage 
 System.gc (); // nettoie les données volumineuses 
 isOutOfMemory = true; // indicateur disponible pour utilisation 
} 
 // programme teste un indicateur pour signaler la récupération 

Cela fonctionne à chaque fois dans les applications mono-thread. Mais j'ai récemment mis mon moteur de test dans un thread de travail distinct de l'interface utilisateur. Maintenant, le manque de mémoire peut se produire de manière arbitraire dans l’un ou l’autre des threads, et il m’est difficile de comprendre comment le récupérer.

Par exemple, lorsque le cadre OOME se produisait, les images d'un fichier GIF animé dans mon interface utilisateur étaient cyclées par un thread propriétaire créé en arrière-plan par une classe Swing échappant à mon contrôle. J'avais pensé avoir alloué à l'avance toutes les ressources nécessaires, mais l'animateur alloue clairement de la mémoire chaque fois qu'il récupère l'image suivante. Si quelqu'un a une idée sur la façon de gérer les OOMEs créés dans le fil n'importe lequel, j'aimerais entendre.

5
Tony Simons

OOME peut être capturé, mais sera généralement inutile, selon que la machine virtuelle Java est capable de collecter des objets lors de la récupération et de la quantité de mémoire restante à ce moment-là.

Exemple: dans ma machine virtuelle, ce programme s’achève:

import Java.util.LinkedList;
import Java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
        }
        System.out.println("Test finished");
    }  
}

Cependant, ajouter une seule ligne à la capture vous montrera de quoi je parle:

import Java.util.LinkedList;
import Java.util.List;

public class OOMErrorTest {             
    public static void main(String[] args) {
        List<Long> ll = new LinkedList<Long>();

        try {
            long l = 0;
            while(true){
                ll.add(new Long(l++));
            }
        } catch(OutOfMemoryError oome){         
            System.out.println("Error catched!!");
            System.out.println("size:" +ll.size());
        }
        System.out.println("Test finished");
    }
}

Le premier programme fonctionne correctement car lorsque la capture est atteinte, la JVM détecte que la liste ne sera plus utilisée (cette détection peut également être une optimisation effectuée au moment de la compilation). Ainsi, lorsque nous arrivons à l'instruction print, la mémoire de tas a été presque entièrement libérée, nous avons donc une grande marge de manœuvre pour continuer. C'est le meilleur des cas.

Toutefois, si le code est organisé de telle sorte que la liste ll soit utilisée après la capture de l'OOME, la JVM ne peut pas le collecter. Cela se produit dans le deuxième extrait. L'OOME, déclenché par une nouvelle création Long, est intercepté, mais nous créons bientôt un nouvel objet (une chaîne dans le System.out,println ligne), et le tas est presque plein, alors un nouvel OOME est jeté. C'est le pire des cas: nous avons essayé de créer un nouvel objet, nous avons échoué, nous avons attrapé l'OOME, oui, mais maintenant, la première instruction nécessitant une nouvelle mémoire de tas (par exemple: créer un nouvel objet) lève un nouvel OOME. Pensez-y, que pouvons-nous faire d'autre à ce stade avec si peu de mémoire? Probablement juste en train de sortir. D'où l'inutile.

Parmi les raisons pour lesquelles la machine virtuelle Java ne collecte pas de ressources, l’une est vraiment effrayante: une ressource partagée avec d’autres threads l’utilisant également. N'importe qui avec un cerveau peut voir à quel point attraper OOME peut être dangereux s'il est inséré dans une application non expérimentale, quelle qu'elle soit.

J'utilise une machine virtuelle Java x86 32 bits (JRE6). La mémoire par défaut pour chaque Java est de 64 Mo).

4
Mister Smith

J'ai juste un scénario où attraper un OutOfMemoryError semble avoir un sens et semble fonctionner.

Scénario: dans une Android App, je souhaite afficher plusieurs bitmaps avec la résolution la plus élevée possible et je veux pouvoir les zoomer facilement.

En raison du zoom fluide, je veux avoir les bitmaps en mémoire. Cependant, Android présente des limitations en matière de mémoire qui dépendent du périphérique et qui sont difficiles à contrôler.

Dans cette situation, il peut y avoir une erreur OutOfMemoryError lors de la lecture du bitmap. Ici, cela aide si je l’attrape et que je continue ensuite avec une résolution plus basse.

3
Jörg Eisfeld

Pour la question 2, je vois déjà la solution que je suggérerais, de BalusC.

  1. Existe-t-il de véritables scénarios Word lors de la récupération de Java.lang.OutOfMemoryError peut être une bonne idée?

Je pense que je viens de trouver un bon exemple. Lorsque l'application awt distribue des messages, OutOfMemoryError non corrigé s'affiche sur stderr et le traitement du message actuel est arrêté. Mais l'application continue de fonctionner! L'utilisateur peut toujours émettre d'autres commandes ignorant les problèmes graves qui se produisent dans les coulisses. Surtout lorsqu'il ne peut pas ou ne respecte pas l'erreur type. Il est donc souhaitable de capturer une exception et de fournir (ou du moins de suggérer) le redémarrage de l’application.

3
Jarekczek

La seule raison pour laquelle je peux comprendre pourquoi il est possible de détecter les erreurs de MOO réside dans le fait que certaines structures de données volumineuses ne sont plus utilisées, que vous pouvez définir la valeur null et libérer de la mémoire. Mais (1) cela signifie que vous gaspillez de la mémoire, et vous devriez réparer votre code plutôt que de vous contenter de boiter après un OOME, et (2) même si vous le preniez, que feriez-vous? Le MOO peut arriver à tout moment, laissant potentiellement tout à moitié terminé.

3
cHao
  1. Cela dépend de la façon dont vous définissez "bien". Nous faisons cela dans notre application Web buggy et cela fonctionne la plupart du temps (heureusement, maintenant OutOfMemory ne se produit pas à cause d'un correctif indépendant). Cependant, même si vous l'attrapiez, il aurait quand même volé un code important: si vous avez plusieurs threads, l'allocation de mémoire peut échouer dans n'importe lequel d'entre eux. Donc, selon votre application, il y a toujours 10% à 90% de chances qu'elle soit irréversiblement brisée.
  2. Autant que je sache, le dépressage important de la pile sur le chemin invalidera un si grand nombre de références et libérera ainsi une mémoire si importante que vous ne devriez pas vous en soucier.

EDIT: Je vous suggère de l'essayer. Dites, écrivez un programme qui appelle de manière récursive une fonction qui alloue progressivement plus de mémoire. Catch OutOfMemoryError et voyez si vous pouvez continuer à partir de là. Selon mon expérience, vous pourrez le faire, bien que dans mon cas, cela se soit passé sous le serveur WebLogic, il pourrait donc y avoir eu de la magie noire.

0
doublep

Vous pouvez intercepter n'importe quoi dans Throwable. En règle générale, vous ne devriez capturer que les sous-classes d'Exception, à l'exception de RuntimeException (bien qu'un grand nombre de développeurs aient également intercepté RuntimeException ... mais cela n'a jamais été l'intention des concepteurs de langage).

Si vous deviez attraper OutOfMemoryError, que feriez-vous? VM manque de mémoire, vous ne pouvez pratiquement pas quitter. Vous ne pouvez probablement même pas ouvrir une boîte de dialogue pour leur dire que vous êtes à court de mémoire, car cela prendrait de la mémoire :-)

Le VM émet une erreur OutOfMemoryError quand il manque vraiment de mémoire (toutes les erreurs doivent indiquer des situations irrécupérables)) et vous ne devriez vraiment rien y faire.

La chose à faire est de savoir pourquoi vous manquez de mémoire (utilisez un profileur, comme celui de NetBeans) et assurez-vous de ne pas avoir de fuites de mémoire. Si vous n'avez pas de fuite de mémoire, augmentez la mémoire que vous allouez à la machine virtuelle.

0
TofuBeer

JVM a fourni des arguments utiles pour gérer OutOfMemoryError. Dans cet article, nous voudrions souligner ces arguments de la machine virtuelle Java. Ces arguments de la machine virtuelle Java sont les suivants:

  1. -XX: + HeapDumpOnOutOfMemoryError -XX: HeapDumpPath
  2. -XX: OnOutOfMemoryError
  3. -XX: + ExitOnOutOfMemoryError
  4. -XX: + CrashOnOutOfMemoryError

Lorsque vous transmettez cet argument, JVM se ferme dès que OutOfMemoryError est renvoyé. Vous pouvez transmettre cet argument si vous souhaitez mettre fin à l'application. Mais personnellement, je ne préférerais pas configurer cet argument, car nous devrions toujours viser une sortie en douceur. Une sortie brusque peut/va mettre en péril les transactions en cours.

arguments JVM liés à OutOfMemory

0
Jim T