NoClassDefFoundError vs ClassNotFoundException
java.lang.NoClassDefFoundError
i java.lang.ClassNotFoundException
brzmią bardzo podobnie ze względu na swoje nazwy i są często mylone. Tak naprawdę ich znaczenie jest różne.
- obie klasy pochodzą z JDK 1.0,
- są podklasami klasy
Throwable
, NoClassDefFoundError
jest unchecked – podtypError
,ClassNotFoundException
jest checked – podtypException
,- oba „wyjątki” rzucane są w runtime’ie,
- związane są z „niedostępnością” zależnej klasy i/lub niepoprawnym
classpath
.
NoClassDefFoundError
- jest to błąd krytyczny,
- najczęściej pojawia się podczas wywoływania
new
, - rzucany jest podczas próby załadowania (w runtime’ie) definicji klasy przez JVM lub classloader, a klasa (plik
.class
) nie jest fizycznie dostępna. Oznaczać to może, że: - kolejnym przypadkiem może być sytuacja, gdy inny (parent) classloader załadował tą klasę i nie jest ona widoczna dla danego (child) classloadera (nie może być załadowana drugi raz); najczęściej taka sytuacja zdarza się w środowisku serwerów aplikacji, np. WebSphere.
- – klasa ta była dostępna podczas kompilacji, a nie jest dostępna podczas uruchamiania,
- – klasa jest dostępna, ale jest w niezgodnej wersji (Javy lub biblioteki),
- –
classpath
został niepoprawnie ustawiony,- – pojawił się błąd inicjalizacji, np. w bloku lub polu statycznym.
ClassNotFoundException
- jest to wyjątek checked; program nie jest przerywany, działa
exception handling
, - rzucany jest gdy aplikacja (w runtime’ie) próbuje znaleźć i załadować klasę na podstawie jej nazwy, ale jej definicja nie może być znaleziona i załadowana. Dzieje się tak na przykład podczas dynamicznego ładowania klas, tj. używania metod
forName
(zClass
),loadClass
,findClass
lubfindSystemClass
(zClassLoader
). Istnienia wszystkich zależnych klas podawanych jako literał nie da się sprawdzić w czasie kompilacji, - kolejną sytuacją jest przekazywanie niedostępnej klasy w parametrze metody, która jako argument przyjmuje interfejs, który spełnia wspomniana klasa,
- innym przypadkiem może być błędne użycie
classpath
a zdefiniowanego w manifeście pliku.jar
, zamiast właściwego, - często spotykany podczas używania driverów JDBC.
Jest zdecydowanie trudniejszy w namierzeniu w środowisku z wieloma ClassLoaderami. Np. w projekcie spakowanym do EARa z modułem WAR w środku. Biblioteki umieszczone w katalogu lib EARa są widoczne dla klas z WARa, ale żadne klasy spakowane do JARa znajdujące się w katalogu WEB-INF/lib w WARze nie mogą być widoczne przez inne moduły (np. inne WARy, EJB-JARy itd). Podobna sytuacja opisana jest tutaj.
Wyjątek jest na tyle popularny, że powstała stronka www.classnotfound.com, gdzie można przeszukiwać biblioteki w celu odnalezienia szukanej klasy:)
Poniżej zamieściłem kod, który pokazuje różnice w działaniu poszczególnych wyjątków.
public class User { } public class UserService { public void createUser() { new User(); } public void createUserClass() throws ClassNotFoundException { Class.forName("User"); } } public class UserTester { public static void main(String[] args) throws Exception { UserService service = new UserService(); service.createUser(); service.createUserClass(); } }
Kiedy usuniemy plik User.class, zakomentujemy 19 linijkę, to po uruchomieniu programu otrzymamy ClassNotFoundException
:
java UserTester Exception in thread "main" java.lang.ClassNotFoundException: User at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:247) at java.lang.Class.forName0(Native Method) at java.lang.Class.forName(Class.java:169) at UserService.createUserClass(UserService.java:11) at UserTester.main(UserTester.java:8)
Natomiast gdy zakomentujemy 20 linijkę (a 19 odkomentujemy), to po uruchomieniu programu otrzymamy NoClassDefFoundError
:
java UserTester Exception in thread "main" java.lang.NoClassDefFoundError: User at UserService.createUser(UserService.java:7) at UserTester.main(UserTester.java:9) Caused by: java.lang.ClassNotFoundException: User at java.net.URLClassLoader$1.run(URLClassLoader.java:202) at java.security.AccessController.doPrivileged(Native Method) at java.net.URLClassLoader.findClass(URLClassLoader.java:190) at java.lang.ClassLoader.loadClass(ClassLoader.java:306) at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301) at java.lang.ClassLoader.loadClass(ClassLoader.java:247) ... 2 more
Jeśli ktoś z Was ma inne doświadczenia z tymi wyjątkami, to zapraszam do komentowania.
W artykule podane są błędne numery linii.
Dzięki za czujność. Już poprawione.
Podoba mi się artykuł jednak pojawiły się w nim pewne nieścisłości.
1. Napisałeś „innym przypadkiem może być błędne użycie classpatha zdefiniowanego w manifeście pliku .jar, zamiast właściwego” jako przyczyny rzucania
ClassNotFoundException
z czym się nie zgadzam – w tym przypadku zostanie rzucony wyjątekNoClassDefFoundError
natomiast jego przyczyną będzie oczywiścieClassNotFoundException
. Warto to generalnie uściślić jeżeli próbujemy rozróżnić przyczyny powstawania obu wyjątków.Zrobiłem prosty test, który pokazuje taką sytuację:
Kod został skompilowany z wersją
guava-13.0.1
. Manifest wygląda następująco:Manifest-Version: 1.0
Archiver-Version: Plexus Archiver
Created-By: Apache Maven
Built-By: dembol
Build-Jdk: 1.6.0_37
Main-Class: pl.wp.dembol.examples.User
Class-Path: guava-13.0.1.jar
Po zmianie
Class-Path
na artefakt nieistniejący otrzymuję:2.
NoClassDefFoundError
– „kolejnym przypadkiem może być sytuacja, gdy inny (parent) classloader załadował tą klasę i nie jest ona widoczna dla danego (child) classloadera (nie może być załadowana drugi raz); najczęściej taka sytuacja zdarza się w środowisku serwerów aplikacji, np. WebSphere.”.Czy mógłbyś przedstawić jakiś konkretny przykład do tej przyczyny?
3. Do
ClassNotFoundException
– jako przyczynę warto podać mechanizm serializacji. W przypadku deserializacji obiektu (metodareadObject()
klasyObjectInputStream
) może zostać także rzucony wyjątekClassNotFoundException
gdy klasa zserializowanego obiektu nie istnieje w przestrzeni adresowej.>
ClassNotFoundException
to wyjątek checked; program nie jest przerywany, działa exception handlingPonieważ cały artykuł jest porównaniem, rozumiem że autor chce jednocześnie przekazać, że
NoClassDefFoundError
przerywa program, bo nie działa exception handling, co nie jest prawdą. KażdyThrowable
można złapać – niezależnie czy jest toError
czyException
.Poza tym, w czasach, gdy mamy OSGi i inne cuda, Errory to już nie zawsze „błędy krytyczne”.
NoClassDefFoundError
to właśnie wyjątek od tej reguły – OSGi’owe aplikacje normalnie je obsługują i aplikacja działa dalej, tylko np. nie załadowała jakiegoś pluginu. Innym wyjątkiem od reguły jestIOError
– teoretycznie powinien występować „when a serious I/O error has occurred”, ale implementacjajava.io.Console#readLine
robicatch IOException
,throw IOError
.NoClassDefFoundError
jest po prostu najgorszym możliwym przykładem „błędu krytycznego”, a dobrym przykładem jest prawie każdy innyError
– tyle chcę powiedzieć. 😉Co do
ClassNotFoundException
, to chodzi o to, że język wymaga zastosowania exception handling.O OSGi nie pisałem, bo nie mam zbytniego doświadczenia. Twoje przykłady uzupełniają temat:)