Class Loadery


Po co powstały

Żeby nie wiązać się ze sposobem dostarczania do JVMa kodu klasy. Można go ściągać, odszyfrowywać albo generować w locie. Umożliwiają instrumentalizację kodu (debugger). Klasy załadowane przez sąsiednie albo potomne Class Loadery są wzajemnie niewidoczne (bezpieczeństwo). Pozwala na załadowanie kilkukrotnie różnego kodu pod tą samą nazwą klasy ale nie wiem czemu ktoś miałby to robić.

Co robią

Dostarczają JVMowi bajty klasy w ściśle zdefiniowanej postaci. Same nie tworzą obiektu Class (te javowe). Robi to natywny kod który sprawdza poprawność i kompatybilność binarną.

Linkowanie

Referencje do obiektów są określane w czasie wykonania a referencje do klas w czasie linkowania. Linkowanie może być robione wcześnie (tuż po kompilacji) albo późno (w czasie wykonania). Java linkuje tak późno jak to możliwe. Typ jest określany przez kwalifikowaną nazwę klasy oraz CL który ją załadował.

Linkowanie jest robione przy wykorzystaniu Class.forName
nie jest robione przy loadClass z Class Loadera – jest przeciążona metoda z flagą loadClass(String name, boolean resolve) ale jest protected.

Klasa do załadowania swoich zależności używa CL który ją załadował.

Inicjalizacja

Klasa która jest zlinkowana ma w Constant Pool mapowania kwalifikowanych nazw klas na ich definicję (Class). Definicja klasy niekoniecznie musi być zainicjalizowana. Tzn. jej statyczne bloki inicjalizujące mogą nie być wykonane tak samo jak przypisania do statycznych pól. Jeśli w wyniku wykonania kodu JVM musi linkować kod to także go inicjalizuje.

Użycie stałej NazwaKlasy.class wykorzystuje metodę loadClass Class Loadera która nie inicjalizuje. Analogiczny kod w kotlinie inicjalizuje companion object. Przeciążone Class.forName(String name, boolean initialize, ClassLoader loader) przy przekazaniu false nie linkuje

Kompatybilność binarna

Przy kompilowaniu klas/modułów oddzielnie klasa która wykorzystuje inną klasę mogła być kompilowana z inną definicją swojej zależności niż ta aktualnie dostępna na Class Pathu. Nie wszystkie zmiany spowodują błąd. Niektóre kompatybilne zmiany [JLS]:

  • Reimplementing existing methods, constructors, and initializers to improve performance.
  • Changing methods or constructors to return values on inputs for which they previously either threw exceptions that normally should not occur or failed by going into an infinite loop or causing a deadlock.
  • Adding new fields, methods, or constructors to an existing class or interface.
  • Deleting private fields, methods, or constructors of a class.
  • When an entire package is updated, deleting package access fields, methods, or constructors of classes and interfaces in the package.
  • Reordering the fields, methods, or constructors in an existing type declaration.
  • Moving a method upward in the class hierarchy.
  • Reordering the list of direct superinterfaces of a class or interface.
  • Inserting new class or interface types in the type hierarchy.

Podobna sytuacja występuje dla serializacji.

Implementacja Class Loaderów

Obiekt Class powstaje w wyniku wywołania jednej z wersji natywnej metody defineClass w ClassLoader. Class ma referencje do Class Loadera który ją załadował. Dlatego ten sam kod klasy załadowany przez dwa różne ClassLoadery skutkuje dwoma typami (między którymi nie można rzutować).

Natywne metody ładujące są prywatne i są wywoływane przez protected defineClass które robi walidacje certyfikatów o ile są i nazwy ładowanej klasy (np czy pakiet nie zaczyna się od java żeby nie można było załadować swojej wersji Stringa). Bez Security Managera prywatne metody można zawołać refleksją. Przewidziany przez twórców sposób to rozszerzenie klasy ClassLoader i wywołanie metod z walidacją.

Hierarchia Class Loaderów

Klasa Class Loader zawiera metodę szablonową loadClass(String name, boolean resolve) która zakłada locki w przypadku ładowania wielowątkowego (tylko jeden wątek ładuje klasę z wykorzystaniem jednego CL) oraz zapewnia domyślną hierarchiczność CL. Metoda do zaimplementowania to findClass(String name) która musi zwrócić klasę więc w implementacji zawołać którąś z natywnych defineClass. Domyślna hierarchia to :

  • Bootstrap
  • Extension
  • Application/System

Loader najpierw deleguje do rodzica zanim spróbuje załadować sam. Zapewnia to że klasy core’owe będą ładowane tylko raz i to przez Bootstrapowy CL oraz że wszystkie klasy odnosząc się do Stringa dostaną tą samą klasę Stringa.

Bootstrap

Bootstrapowy CL jest napisany w C i nie można mieć referencji do niego z poziomu Javy. Ładuje klasy z JRE. Powtarza część walidacji robionych w javie i dodaje swoje (dt. spójności typów [Bracha])

Extension

Extension CL powstał na potrzeby Apletów (jak być może cały mechanizm CL) i daje alternatywny/globalny „Class Path” czyli miejsce z którego są ładowane klasy dla wszystkich JVMów na danej maszynie o ile nie zmieniały parametrów uruchomieniowych (java.ext.dirs które powinno zawierać foldery a nie pliki).

Extension CL ładuje pliki z pominięciem walidacji Security Managera (? AccessController.doPrivileged() i nie sprawdza uprawnienia createClassLoader).

System

Systemowy CL ładuje kod z Class Patha.

Problemy

Nie zawsze CL działają parent first, servlety są child first:

„The Web application class loader must load classes from the WEB-INF/ classes directory first, and then from library JARsB in the WEB-INF/lib directory.” str 109 spec 3.0

To może powodować problem jeśli klasa możliwa do załadowania przez rodzica i potomnego. Kod rodzica będzie używał innej klasy niż kod potomny i nie będą mogły przekazywać instancji tej klasy w wywołaniach między sobą.

Przy ładowaniu parent first problem pojawia się jeśli kod core’owy (rodzica) korzysta z klas na Class Path np. Xerces (xml), JNDI (service locator).

Przy każdym sposobie ładowania pojawia się problem współpracy między kodem załadowanym przez różne CL. Musi się to odbywać przez interfejs który jest wspólny dla obu (ładowany prawdopodobnie przez trzeciego CL do którego oba mają referencje).

ContextClassLoadery

Żeby zignorować hierarchie wprowadzono ContextClassLoadery. To pole w Thread do którego można wsadzić Class Loadera (żeby nie trzeba było do Thread Locala). A później w tym samym wątku ale w klasie załadowanej przez innego CL zignorować jej aktualnego CL i użyć tego z wątku.

Źródła:
„The Java® Virtual Machine Specification” Tim Lindholm, Frank Yellin, Gilad Bracha, Alex Buckley

„Inside the Java Virtual Machine” Bill Venners

Dynamic Class Loading in the Java VirtualMachine Sheng Liang, Gilad Bracha

Dokumentacja mechanizmu rozszerzeń

Specyfikacja Servletów 4.0

Ogólnie o ładowaniu klas

O Context Class Loaderach

Rożnica między Class.forName() i ClassLoader.loadClass()

JLS Kompatybilność binarna