web-dev-qa-db-fra.com

Pourquoi NoClassDefFoundError causé par un échec d'initialisation de champ statique?

Voici une question Java intéressante.

le programme Java simple suivant contient un champ statique initialisé par une méthode de manière statique. En fait, je force la méthode qui calcule la valeur intiailize à déclencher une exception NullPointException. Lorsque j'accède à un tel champ statique, un message NoClassDefFoundError est déclenché. il semble que le VM traiter la classe n'est pas complète.

Mais lorsque j'accède à la classe, elle est toujours disponible.

Est-ce que quelqu'un sait pourquoi?

class TestClass {
    public static TestClass instance = init();

    public static TestClass init() {
       String a = null;
       a.charAt(0); //force a null point exception;
       return new TestClass();
    }
}

class MainClass {
    static public void main(String[] args) {
       accessStatic(); // a ExceptionInInitializerError raised cause by NullPointer
       accessStatic(); //now a NoClassDefFoundError occurs;

       // But the class of TestClass is still available; why?
       System.out.println("TestClass.class=" + TestClass.class);
    }

    static void accessStatic() {
        TestClass a;

        try {
            a = TestClass.instance; 
        } catch(Throwable e) {
            e.printStackTrace();
        }
    }   
}
32
ext2

La réponse à de telles questions est généralement enterrée quelque part dans les spécifications ... (§12.4.2)

Que se passe-t-il lorsque les classes sont initialisées:

Les étapes 1 à 4 ne sont pas liées à cette question. L'étape 5, voici ce qui déclenche l'exception:

5. Si l'objet Classe est dans un état erroné, alors l'initialisation n'est pas possible. Libérez le verrou sur l'objet de classe et lance une erreur NoClassDefFoundError.

6 à 8 poursuivent l’initialisation, 8 exécute les initialiseurs, et il s’agit généralement de l’étape 9:

9. Si l'exécution des initialiseurs se termine normalement, verrouillez cet objet Class, étiquetez-le complètement initialisé, avertissez tous les threads en attente, relâchez le verrou et terminez cette procédure normalement.

Mais nous avons eu une erreur dans l'initialiseur alors:

10. Sinon, les initialiseurs doivent s'être terminés abruptement en générant une exception E. Si la classe de E n'est pas Error ou l'une de ses sous-classes, alors crée une nouvelle instance de la classe ExceptionInInitializerError, avec E comme l'argument et utilisez cet objet à la place de E à l'étape suivante. Toutefois, si une nouvelle instance de ExceptionInInitializerError ne peut pas être créée car une OutOfMemoryError se produit, utilisez plutôt un objet OutOfMemoryError à la place de E à l'étape suivante.

Oui, nous voyons une ExceptionInInitializerError b/c de l'exception de pointeur null.

11. Verrouillez l'objet Classe, étiquetez-le comme étant erroné, informez tous les threads en attente, relâchez le verrou et terminez cette procédure abruptement avec la raison E ou son remplacement, comme déterminé à l'étape précédente. (En raison d'une faille dans certaines implémentations antérieures, une exception lors de l'initialisation de la classe a été ignorée, plutôt que de provoquer une ExceptionInInitializerError comme décrit ici.) 

Et puis la classe est marquée comme étant erronée, c'est pourquoi nous obtenons l'exception de l'étape 5 une deuxième fois.


La partie surprenante est la troisième impression montrant que TestClass.class dans MainClass contient en réalité une référence à un objet physique Class.

Probablement parce que TestClass existe toujours, c'est simplement erroné. Il a déjà été chargé et vérifié.

32
trutheality

Oui, c’est généralement pourquoi NoClassDefFoundError est élevé. C'est diaboliquement nommé, c'est tout. Il aurait dû être nommé "exception d'initialisation de la classe init" ou quelque chose du genre.

En raison du nom trompeur, les programmeurs Java qui ont eu cette erreur ont perdu des centaines d'années de travail en essayant de comprendre pourquoi la classe ne pouvait pas être trouvée.

Chaque fois que vous voyez cette exception, vous devriez consulter le journal à la hausse et essayer de trouver la cause première lorsque la classe n'a pas pu initier.

11
irreputable

Lorsque j'accède à un tel champ statique, une erreur NoClassDefFoundError est déclenchée. il semble que le VM traiter la classe n'est pas complète.

C'est correct ...

Mais quand j'accède au cours, il est toujours disponible

Oui.

Le chargeur de classe n'a pas essayé de supprimer la classe cassée pour les raisons suivantes:

  • ce serait difficile à faire, 
  • il serait extrêmement difficile à faire en toute sécurité
  • cela laisserait la machine virtuelle Java dans un état où une application pourrait facilement perdre beaucoup de temps à charger et à recharger de manière répétée le code erroné, et
  • les spécifications disent (ou du moins impliquent) que cela ne devrait pas; voir d'autres réponses pour plus de détails.

Pour entrer dans un état où cette incohérence est visible, votre application doit attraper ClassDefNotFoundError (ou une superclasse) et tenter de la récupérer. C’est un fait bien documenté que les exceptions Error ne sont généralement pas récupérables; c’est-à-dire que si vous essayez de récupérer, la JVM peut se retrouver dans un état incohérent. C'est ce qui s'est passé ici ... en ce qui concerne les classes qui étaient en cours de chargement/initialisation.

3
Stephen C
0
Noypi Gilas