web-dev-qa-db-fra.com

Java - arrêt sur erreur de mémoire insuffisante

J'ai entendu des choses très contradictoires sur la meilleure façon de gérer cela, et je suis confronté au dilemme suivant:

  • un OOME fait tomber un fil, mais pas toute l'application
  • et je dois supprimer toute l'application mais je ne peux pas, car le fil n'a plus de mémoire

J'ai toujours compris que la meilleure pratique consiste à les laisser partir afin que la machine virtuelle puisse mourir, car elle est dans un état incohérent à ce stade, mais cela ne semble pas fonctionner ici.

36
djechlin

OutOfMemoryError est comme n'importe quelle autre erreur. S'il s'échappe de Thread.run(), le fil mourra. Rien de plus. De plus, lorsqu'un thread meurt, il ne s'agit plus d'une racine GC. Par conséquent, toutes les références conservées uniquement par ce thread sont éligibles pour le garbage collection. Cela signifie que la machine virtuelle Java est très susceptible de récupérer d'OOME.

Si vous voulez tuer votre machine virtuelle Java à tout moment car vous pensez qu'elle peut être dans un état incohérent, ajoutez ceci à vos options Java:

-XX:OnOutOfMemoryError="kill -9 %p"

%p est l'espace réservé actuel du PID du processus Java. Le reste est explicite.

Bien sûr, vous pouvez également essayer d’attraper OutOfMemoryError et de le manipuler d’une manière ou d’une autre. Mais c'est délicat.

37
Tomasz Nurkiewicz

Avec la version 8u92, il existe maintenant une option JVM dans le JDK Oracle permettant à la machine virtuelle de quitter, à la suite d'une erreur OutOfMemoryError:

À partir des notes de publication :

ExitOnOutOfMemoryError - Lorsque vous activez cette option, la machine virtuelle Java se ferme lors de la première occurrence d'une erreur de mémoire insuffisante. Il peut être utilisé si vous préférez redémarrer une instance de la machine virtuelle Java plutôt que de gérer les erreurs de mémoire insuffisante.

29
ahu

Dans la version 8u92 de Java, les arguments VM 

  • -XX:+ExitOnOutOfMemoryError 
  • -XX:+CrashOnOutOfMemoryError 

ont été ajoutés, voir les notes de version .

ExitOnOutOfMemoryError
Lorsque vous activez cette option, la machine virtuelle Java se ferme sur le fichier première occurrence d'une erreur de mémoire insuffisante. Il peut être utilisé si vous préférez redémarrer une instance de la machine virtuelle Java plutôt que de gérer à partir de erreurs de mémoire. 

CrashOnOutOfMemoryError
Si cette option est activée, lorsqu’un fichier Une erreur de mémoire insuffisante se produit, la machine virtuelle Java se bloque et produit du texte et fichiers d'erreur binaires.

Requête d'amélioration: JDK-8138745 (la dénomination des paramètres est fausse bien que JDK-8154713 , ExitOnOutOfMemoryError au lieu de ExitOnOutOfMemory)

24
flavio.donze

Vous pouvez forcer votre programme à se terminer de plusieurs manières, une fois l'erreur survenue. Comme d'autres l'ont suggéré, vous pouvez détecter l'erreur et effectuer un System.exit par la suite, si nécessaire. Mais je vous suggère également d'utiliser -XX: + HeapDumpOnOutOfMemoryError, afin que la machine virtuelle crée un fichier de vidage de la mémoire avec le contenu de votre application une fois l'événement produit. Vous utiliserez des profils, je vous recommande d’Eclipse MAT d’examiner l’image. De cette façon, vous trouverez assez rapidement quelle est la cause du problème et réagissez correctement. Si vous n'utilisez pas Eclipse, vous pouvez utiliser Eclipse MAT en tant que produit autonome, voir: http://wiki.Eclipse.org/index.php/MemoryAnalyzer .

5
dan

Si vous souhaitez supprimer votre programme, examinez l’option -XX:OnOutOfMemoryError="<cmd args>;<cmd args>" ( documentée ici ) sur la ligne de commande. Il suffit de le pointer vers un script kill pour votre application.

En général, je n'ai jamais eu la chance de gérer cette erreur sans avoir à redémarrer l'application. Il y a toujours eu une sorte de dossier qui passe, alors je vous suggère personnellement d'arrêter votre demande mais de rechercher la source du problème.

4
Marcus Riemer

En règle générale, vous ne devriez jamais écrire un bloc catch qui capture Java.lang.Error ni aucune de ses sous-classes, y compris OutOfMemoryError. La seule exception à cela serait si vous utilisez une bibliothèque tierce qui lance une sous-classe personnalisée de Error alors qu'elle aurait dû sous-classer RuntimeException. C'est vraiment juste un moyen de contourner une erreur dans leur code cependant.

Depuis le JavaDoc pour Java.lang.Error:

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

Si vous rencontrez des problèmes lorsque votre application continue de s'exécuter même après la mort de l'un des threads à cause d'un serveur OOME, vous avez plusieurs options.

Tout d'abord, vous voudrez peut-être vérifier s'il est possible de marquer les threads restants en tant que threads de démon. S'il y a un moment où il ne reste que des threads démon dans la machine virtuelle Java, tous les crochets d'arrêt sont exécutés et les terminaisons aussi ordonnées que possible. Pour ce faire, vous devez appeler setDaemon(true) sur l'objet thread avant de le démarrer. Si les threads sont réellement créés par un framework ou un autre code, vous devrez peut-être utiliser un moyen différent pour définir cet indicateur.

L'autre option consiste à affecter un gestionnaire d'exceptions non capturé aux threads en question et à appeler soit System.exit() ou, si cela est absolument nécessaire, Runtime.getRuntime().halt(). L'appel de halte est très dangereux car les crochets d'arrêt ne tentent même pas de s'exécuter, mais dans certaines situations, l'arrêt peut fonctionner si System.exit aurait échoué si un OOME avait déjà été lancé.

1
Mike Deck

Je suggère de gérer toutes les exceptions non interceptées à partir de l'application pour vous assurer qu'elle essaie de vous fournir les meilleures données possibles avant de terminer . Ensuite, utilisez un script externe qui redémarre votre processus lorsqu'il se bloque.

public class ExitProcessOnUncaughtException implements UncaughtExceptionHandler  
{
    static public void register()
    {
        Thread.setDefaultUncaughtExceptionHandler(new ExitProcessOnUncaughtException());
    }

    private ExitProcessOnUncaughtException() {}


    @Override
    public void uncaughtException(Thread t, Throwable e) 
    {
        try {
            StringWriter writer = new StringWriter();
            e.printStackTrace(new PrintWriter(writer));
            System.out.println("Uncaught exception caught"+ " in thread: "+t);
            System.out.flush();
            System.out.println();
            System.err.println(writer.getBuffer().toString());
            System.err.flush();
            printFullCoreDump();
        } finally {
            Runtime.getRuntime().halt(1);
        }
    }

    public static void printFullCoreDump()
    {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        System.out.println("\n"+
            sdf.format(System.currentTimeMillis())+"\n"+
            "All Stack Trace:\n"+
            getAllStackTraces()+
            "\nHeap\n"+
            getHeapInfo()+
            "\n");
    }

    public static String getAllStackTraces()
    {
        String ret="";
        Map<Thread, StackTraceElement[]> allStackTraces = Thread.getAllStackTraces();

        for (Entry<Thread, StackTraceElement[]> entry : allStackTraces.entrySet())
            ret+=getThreadInfo(entry.getKey(),entry.getValue())+"\n";
        return ret;
    }

    public static String getHeapInfo()
    {
        String ret="";
        List<MemoryPoolMXBean> memBeans = ManagementFactory.getMemoryPoolMXBeans();               
        for (MemoryPoolMXBean mpool : memBeans) {
            MemoryUsage usage = mpool.getUsage();

            String name = mpool.getName();      
            long used = usage.getUsed();
            long max = usage.getMax();
            int pctUsed = (int) (used * 100 / max);
            ret+=" "+name+" total: "+(max/1000)+"K, "+pctUsed+"% used\n";
        }
        return ret;
    }

    public static String getThreadInfo(Thread thread, StackTraceElement[] stack)
    {
        String ret="";
        ret+="\n\""+thread.getName()+"\"";
        if (thread.isDaemon())
            ret+=" daemon";
        ret+=
                " prio="+thread.getPriority()+
                " tid="+String.format("0x%08x", thread.getId());
        if (stack.length>0)
            ret+=" in "+stack[0].getClassName()+"."+stack[0].getMethodName()+"()";
        ret+="\n   Java.lang.Thread.State: "+thread.getState()+"\n";
        ret+=getStackTrace(stack);
        return ret;
    }

    public static String getStackTrace(StackTraceElement[] stack)
    {
        String ret="";
        for (StackTraceElement element : stack)
            ret+="\tat "+element+"\n";
        return ret;
    }
}
1
Shloim

Depuis les options de la JVM

-XX:+ExitOnOutOfMemoryError

-XX:+CrashOnOutOfMemoryError

-XX:OnOutOfMemoryError=...

ne fonctionne pas si OutOfMemoryError survient à cause de threads épuisés (voir le rapport de bogue JDK correspondant ), il peut être intéressant d'essayer l'outil jkill . Il s'enregistre via JVMTI et quitte le VM si la mémoire ou les threads disponibles sont épuisés.

Dans mes tests, cela fonctionne comme prévu (et comment je m'attendrais à ce que les options de JVM fonctionnent).

0
radlan

Vous pouvez entourer votre code de thread avec un test try pour l'OOME et effectuer un nettoyage manuel si un tel événement se produit. Une astuce consiste à faire en sorte que votre fonction de fil ne soit qu'une tentative d’essayer autour d’une autre fonction. En cas d'erreur de mémoire, il devrait libérer de l'espace sur la pile, ce qui vous permettra d'effectuer des suppressions rapides. Cela devrait fonctionner si vous faites une requête de récupération de place sur certaines ressources immédiatement après avoir intercepté et/ou définissez un indicateur mourant pour indiquer à d'autres threads de quitter. 

Une fois que le fil avec OOME est mort et que vous avez fait une collecte sur ses éléments, vous devriez avoir suffisamment d’espace libre pour que les autres threads s’arrêtent de manière ordonnée. Il s’agit d’un abandon plus gracieux avec une occasion de consigner le problème avant de mourir également.

0
Pyrce