![]() |
![]() |
|
![]() |
![]() |
Home Contact Links Deutsch |
|
Java: Classloading in standard Java and OSGi environmentsThe starting pointWhile runtime you might have got an error message saying that object of type "X" cannot be cast to type "X", because they don't fit. Of course you checked, if the packages of both types differ, but they are the same. So what problem the JVM (Java Virtual Machine) could have? The problem is, that different classloaders have loaded the type "X". At runtime a class or interface type is identified by a set of the following attributes:
There are multiple ways to load a class. The new-operator does this before it creates any instance. Another way is to use Class.forName(). Anyway both calls invoke the loadClass-method of the current classloader (that's the one that was used to create the current this-class from which the current call occurs). To find out the classloader you can call this.getClass().getClassLoader() which normally returns the system-classloader (if the classloader wasn't changed e.g. by an underlying OSGi framework). Classloader hierarchyJava has a built in classloader hierarchy. Although this hierarchy in many cases (e.g. in OSGi, EJB...) is no longer used, all further technologies still use the underlying concept. The following picture shows loading of class "Worker" which was compiled in "Worker.class" and perhaps was packed into a jar-file. First of all the system-classloader will check its cache, if it already has loaded the class. If not, then the next step always is to call the parent classloader. This happens also at the parent classloaders until the bootstrap-classloader is reached. The bootstrap-classloader (also referred to as "primordial classloader") is the top of the classloader-hierarchy and is part of the JVM-implementation. As a consequence it isn't a Java-class, but its a logical part. The bootstrap-classloader now scans all the class- and jar-files within the directory "/jre/lib" in <JAVA_HOME>. The previous hierarchical searching algorithm ensures that common classes (like java.util.Hashtable) are always loaded from the same classloader. This is the reason why you won't have any class-cast problems with such basic classes. In our example the bootstrap-classloader isn't able to load class "Worker". So the call returns to the extension-classloader, which now will scan all the class- and jar-files within the directory "/jre/etx/lib" in <JAVA_HOME>. The extension-classloader also checks any directory that is specified by "java.ext.dirs" system property, but that shouldn't be of interest in this context. Again class "Worker" isn't found. So the call returns to the system-classloader. The system-classloader now checks the classpath which lists the class- and jar-files to search in. There are a couple of contributors to it. First of all there's (of course) the CLASSPATH environment variable. Then there are command-line options "-classpath" and "-cp". And if a jar-file on the class path has a manifest with the "Class-Path"-attribute, then the jar-files specified by this attribute will also be searched. In one of these locations the system-classloader should find our class "Worker". Class loading in OSGiOne question hasn't been asked until yet ... Why Java offers such a complicated class loading mechanism? The answer is that this feature is urgently needed for many applications, e.g. frameworks like OSGi. One of the biggest achievements of OSGi is to provide a framework that allows to isolate components by isolating their classes. So it's even possible to run multiple versions of the same component at the same time. Think of the class "Worker" above. We could implement such a class in the first version of our component. But after a while we could need to create a new version of "Worker" - perhaps for a new component. In this situation it's really necessary to carefully differentiate both versions at runtime and probably it wouldn't be a good thing to be able to cast a "Worker"-instance of version 1 to a "Worker" of version 2 or vice versa. The upper picture shows it in detail. In the context of OSGi the components were called "bundles". Each bundle has its own classloader. If for example bundle 1 would load class "Worker", then it will have a look into its manifest-file. This is stored at the path "/META-INF/MANIFEST.MF" within its own jar-file. If bundle 2 also loads "Worker", then it will look into its own jar, too. So the classes of the bundles are complety isolated. Even if bundle 1 would try to give an instance of "Worker" to bundle 2, this won't help, because for bundle 2 this "Worker" has nothing to do with his own "Worker". And even if the classloader of bundle 1 would read the same physical jar-file as bundle 2, this wouldn't change anything, because the classes are still loaded from different classloaders. So there comes up another question ... if the classes of the bundles are perfectly isolated, how can bundles collaborate through any common objects? The answer also lies in the manifest. There you can alleviate these restrictions e.g. by providing appropriate "Import-Package:" and "Export-Package:" statements. For more information, please have a look at page OSGi/RCP. By the way ... it's possible to change the parent classloader for bundles. There's a Java property called "osgi.parentClassloader" which supports the following values:
Copyright (c) 2016: Juergen Luers |