Des occurrences de notre code Java ont intercepté un NullPointerException
, mais lorsque j'essaie d'enregistrer StackTrace (qui finit par appeler Throwable.printStackTrace()
), tout ce que je reçois est le suivant:
Java.lang.NullPointerException
Quelqu'un at-il rencontré ce? J'ai essayé de googler pour "trace de pile vide de pointeur null Java" mais je n'ai rien trouvé de tel.
Vous utilisez probablement la machine virtuelle HotSpot (à l'origine de Sun Microsystems, puis rachetée par Oracle, qui fait partie d'OpenJDK), qui effectue de nombreuses optimisations. Pour récupérer les traces de la pile, vous devez transmettre l'option -XX:-OmitStackTraceInFastThrow
à la machine virtuelle Java.
L'optimisation est la suivante: lorsqu'une exception (généralement une exception NullPointerException) se produit pour la première fois, le suivi complet de la pile est imprimé et la JVM se souvient du suivi de la pile (ou peut-être simplement de l'emplacement du code). Lorsque cette exception se produit assez souvent, la trace de pile n'est plus imprimée, à la fois pour améliorer les performances et pour ne pas submerger le journal avec des traces de pile identiques.
Pour voir comment cela est implémenté dans la machine virtuelle HotSpot, prenez-en une copie et recherchez la variable globale OmitStackTraceInFastThrow
. La dernière fois que j'ai consulté le code (en 2019), c'était dans le fichier graphKit.cpp .
Comme vous l'avez mentionné dans un commentaire, vous utilisez log4j. J'ai découvert (par inadvertance) un endroit où j'avais écrit
LOG.error(exc);
au lieu du typique
LOG.error("Some informative message", e);
par la paresse ou peut-être simplement ne pas y penser. La partie malheureuse de ceci est qu'il ne se comporte pas comme prévu. En réalité, l’API de journalisation prend Object comme premier argument, et non une chaîne, puis elle appelle toString () sur l’argument. Donc, au lieu d’obtenir la jolie trace de pile Nice, elle affiche simplement le toString - ce qui dans le cas de NPE est plutôt inutile.
C'est peut-être ce que vous vivez?
Nous avons vu ce même comportement dans le passé. Il s'est avéré que, pour une raison insensée, si une exception NullPointerException se produisait plusieurs fois au même endroit dans le code, après un certain temps, l'utilisation de Log.error(String, Throwable)
cesserait d'inclure des traces de pile complètes.
Essayez de chercher plus loin dans votre journal. Vous pouvez trouver le coupable.
EDIT: ce bogue semble pertinent, mais il a été corrigé il y a si longtemps que ce n'est probablement pas la cause.
Voici une explication: Hotspot a provoqué la perte des traces de pile dans les exceptions en production - et le correctif
Je l'ai testé sur Mac OS X
Serveur 64 bits Java HotSpot (TM) VM (version 20.1-b02-383, mode mixte)
Object string = "abcd";
int i = 0;
while (i < 12289) {
i++;
try {
Integer a = (Integer) string;
} catch (Exception e) {
e.printStackTrace();
}
}
Pour ce fragment de code spécifique, 12288 itérations (+ fréquence?) Semblent constituer la limite où la machine virtuelle Java a décidé d'utiliser une exception préallouée ...
exception.toString
ne vous donne pas le StackTrace, il ne retourne que
une courte description de ce jetable. Le résultat est la concaténation de:
_* the name of the class of this object * ": " (a colon and a space) * the result of invoking this object's getLocalizedMessage() method
_
Utilisez exception.printStackTrace
à la place pour générer le StackTrace.
Autre suggestion - si vous utilisez Eclipse, vous pouvez définir un point d'arrêt sur NullPointerException lui-même (dans la perspective Débogage, accédez à l'onglet "Points de contrôle" et cliquez sur la petite icône qui contient le symbole!).
Vérifiez les options "capturé" et "non capturé". Désormais, lorsque vous déclenchez le NPE, vous définissez immédiatement un point d'arrêt, puis vous pouvez voir comment il est géré et pourquoi vous n'obtenez pas de trace de pile.
Lorsque vous utilisez AspectJ dans votre projet, il peut arriver qu'un aspect masque sa partie de la trace de pile. Par exemple, aujourd'hui j'avais:
Java.lang.NullPointerException:
at com.company.product.MyTest.test(MyTest.Java:37)
Cette trace de pile a été imprimée lors de l'exécution du test via le surefire de Maven.
Par ailleurs, lors de l'exécution du test dans IntelliJ, une trace de pile différente était imprimée:
Java.lang.NullPointerException
at com.company.product.library.ArgumentChecker.nonNull(ArgumentChecker.Java:67)
at ...
at com.company.product.aspects.CheckArgumentsAspect.wrap(CheckArgumentsAspect.Java:82)
at ...
at com.company.product.MyTest.test(MyTest.Java:37)
(Votre question ne permet toujours pas de savoir si votre code appelle printStackTrace()
ou si cela est effectué par un gestionnaire de journalisation.)
Voici quelques explications possibles sur ce qui pourrait se passer:
Le consignateur/gestionnaire utilisé a été configuré pour générer uniquement la chaîne de message de l'exception, pas une trace de pile complète.
Votre application (ou une bibliothèque tierce) enregistre l'exception à l'aide de LOG.error(ex);
plutôt que de la forme à 2 arguments de (par exemple) la méthode log4j Logger.
Le message vient d'un endroit différent de celui que vous pensez être; par exemple. il s'agit en fait d'une méthode de bibliothèque tierce ou de quelques éléments aléatoires laissés par des tentatives de débogage antérieures.
L'exception en cours de journalisation a surchargé certaines méthodes pour masquer le stacktrace. Si tel est le cas, l'exception ne sera pas une exception NullPointerException authentique, mais un sous-type personnalisé de NPE ou même une exception non connectée.
Je pense que la dernière explication possible est assez improbable, mais les gens envisagent au moins de faire ce genre de chose pour "empêcher" le reverse engineering. Bien sûr, cela ne fait que rendre la vie difficile aux développeurs honnêtes.
toString()
ne renvoie que le nom de l'exception et le message facultatif. Je suggère d'appeler
exception.printStackTrace()
pour vider le message, ou si vous avez besoin des détails sanglants:
StackTraceElement[] trace = exception.getStackTrace()
Cela produira l'exception, utilisé uniquement pour déboguer, vous devriez mieux gérer vos exceptions.
import Java.io.PrintWriter;
import Java.io.StringWriter;
public static String getStackTrace(Throwable t)
{
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw, true);
t.printStackTrace(pw);
pw.flush();
sw.flush();
return sw.toString();
}