JBossClassLoadingUseCases(jboss 类型加载使用案例)

Advanced(先进的) JBoss Class Loading




One of the main concerns(关注点) of a developer(开发者) writing hot re-deployable(热部署) JBoss applications is to understand how JBoss class loading works. Within(在。。之内) the internals(内部构建) of the class loading mechanism(机制) lies(位于) the answer to questions like


  • What happens if I pack a newer(更新的) version of an utility(实用的) library(库) with(pack with挤进) my application, while an older version of the same library lingers(存留) somewhere in the server's lib directory?

  • How can I use two different versions of the same utility library, simultaneously(同时地), within the same instance of the application server?

  • What version of an utility class I am currently(当前的) using(使用)?


or even the most fundamental(基本的) of them all


  • Why do I need to mess with all this class loading stuff(材料,原料) anyway(无论如何,不管怎样;总之)?


This article tries to provide the reader with the knowledge required to answer these questions. It will start by trying to answer the last one, and then it will present several(几次的,几个的) often encountered(曾经遇到过的) use cases and explain the behavior of the JBoss class loading mechanism when faced with those situations(场景).


The Need for Class Loaders and Class Loading Management(管理


Class Namespace Isolation(分离)

An application server should ideally(理想地) give its deployed applications the freedom to use whatever utility library and whatever version of the library they see fit(合适的), regardless of (不顾) the presence(场景) of concurrent applications that want to use the same library. This is mandated(委托统治的,委托 )by the J2EE specifications(规范), which calls it class namespace isolation(隔离 (Java EE 5 Specifications, Section EE.8.4). The fact that different applications load their classes in different class name spaces or class loading domains allow them to do just that: run whatever class version they like, oblivious to(不用在意) the fact that their neighbors(邻居) use the same class.


Java doesn't provide the formal(正式的) notion(概念) of class version. So how is it possible to implement a class loading domain? The runtime identity of a class in Java 2 is defined by the fully qualified class name and its defining class loader. This means that the same class, loaded by two different class loaders, is seen by the Virtual Machine as two completely different types.


If you like history, you probably(或许) know that this wasn't always the case. In Java 1.1 the runtime identity of a class was defined only by its fully qualified(合格的) class name. That made Vijay Saraswat declare in 1997 that "[Java is not type-safe|http://matrix.research.att.com/vj/bug.html]" and Sheng Liang and Gilad Bracha fixed it by strengthening(强化) the type system to include a class's defining class loader in addition to the name of the class to fully define the type. This is good and ... not so good. It is good because now its not possible anymore that a rogue(盗贼) class loader would re-define your "java.lang.String" class. The VM will detect(发现) that and throw a ClassCastException. It is also good because now it is possible to have class loading domains within the same VM. Not so good however, is the fact that passing an object instance by(错过) reference(参考) between two class loading domains is not possible. Doing so results in the dreaded(令人畏惧的) ClassCastException. If you would like to know more details about how this happens, please follow this link. Not being able to pass an Object by reference means you have to fall back on serialization and serialization means performance degradation.


Hot Redeployment(热部署)


Returning to application servers, the need for class loaders becomes probably obvious: this is how an application server implements class namespace isolation: each application gets its own class loader at deployment, and hence(从此以后), its own "version" of classes.


To extend this even more, it would be nice if we could re-deploy an application (i.e. instantiate a newer version of a class), at run-time, without necessarily bringing down(降低) the VM and re-starting it to reload the class. That would mean 24x7 uptime(开机时间). Java 4 doesn't intrinsically(本质地) support the concept(观念,概念) of hot re-deployment. Once a dependent(依赖的) class reference was added to the runtime constant pool of a class, it is not possible to drop that reference anymore.


However, it is possible to trick the server (or the VM) into (trick into 诱使) doing this. If application A interacts(交互) with application B, but doesn't have any direct(直接的) references(引用) to the B classes, and B changes, let's say a newer and better version becomes available(可利用的), it is possible to create a new class loader, load the new B classes and have the invocation(调用) bus (the application server) route all invocations from A to the new B classes. This way, A deals with(涉及) a new version of B without even knowing it. If the application server is careful to drop all explicit(显示) references(引用) to the old B classes, they will eventually(最后) be garbage collected(垃圾回收) and the old B will eventually disappear from the system.


Sharing Classes


Isolating(分离的) class loading domains is nice. Our applications will run happily and safe. But very slowly, when it comes to interacting(相互制约) with each other. This is because each interaction involves passing arguments by value, which means serialization, which means overhead.


We're sometimes (actually quite often) faced with the situation where in we would like to allow applications to share classes. We know precisely(恰好), for example, that in our environment, application A and B, otherwise independent, will always use the same version of the utility library and doing so, they could pass references among themselves without any problem. The added benefit in this case is that the invocations will be faster, given the fact serialization is cut out.


One word of caution though. In this situation, if we hot redeploy the utility library, we also must re-deploy the application A and B: the current A and B classes used direct references to the utility classes, so they're tainted forever, they won't ever be able to use the new utility classes.


JBoss makes possible for applications to share classes. JBoss 3.x does that by default. JBoss 4.0 does this for the "standard" configuration, but maintains( 维护) class namespace isolation between applications for its "default" configuration. JBoss 4.0.1 reverts(归还了) to the 3.x convention(规范).


Class Repositories or How JBoss Class Loading Works


JBoss makes sharing classes possible by introducing the concept of class loader repository. The central piece of the class loading mechanism is the org.jboss.mx.loading.UnifiedClassLoader3 (UCL). UnifiedClassLoader3 extends URLClassLoader. Each UCL is associated with 和…联系在一起 a shared共享的 repository of classes and resources, usually an instance of org.jboss.mx.loading.UnifiedLoaderRepository3. Every UCL is associated with 和…联系在一起 a single instance of UnifiedLoaderRepository3, but a repository can have multiple UCLs. A UCL may have multiple URLs associated with it for class and resource loading. By default, there is a single UnifiedLoaderRepository3 shared across all UCL instances. UCLs form a single flat class namespace.


The class loader parent for each UCL is a NoAnnotationClassLoader instance. The NoAnnotationClassLoader extends URLClassLoader. A singleton NoAnnotationClassLoader instance is created during the server's boot process and its job is to define classes available in the $JBOSS_HOME/lib libraries (ex: commons-logging.jarconcurrent.jardom4j.jarjboss-common.jarjboss-jmx.jarjboss-system.jarlog4j-boot.jarxercesImpl.jar, etc.). The NoAnnotationClassLoader's parent is the system class loader (sun.misc.Launcher$AppClassLoader).













When a new UCL is created and associated with the repository, it contributes to (有助于)the repository a map of packages it can potentially可能的 serve供应 classes from. It doesn't add any class to the repository's class cache yet, because nobody has requested any class at this stage. The repository just walks through the class loader's URL to see what packages that UCL is capable of handling. So, the UCL just declares that it can potentially serve classes from the packages that are present in its classpath.


When requested to load a class, a UCL overrides the standard Java2 class loading model by first trying to load a class from its associated repository's cache. If it doesn't find it there, it delegates the task of loading the class to the first UCL associated with the repository that declared it can load that class. The order in which the UCLs have been added to the repository becomes important, because this is what defines "first" in this context. If no "available" UCL is found, the initiating UCL falls back to the standard Java2 parent delegation. This explains why you are still able to use "java.lang.String", for example.

请求的加载一个类时,涵盖标准Java2类加载模型的UCL首先试图从一个类库的缓存加载类。如果没有找到它,意味着存储库相关的第一个UCL的类型加载任务宣布它可以加载该类。已经加载存储库的the UCLs 的顺序变得重要,因为上下文中定义了ucls的加载顺序。如果没有发现上一个加载到类型的上一个ucl,系统将初始化ucl并回到到标准的java父类委派。这就解释了为什么你仍然能够使用java.lang.String

At the end of this process, if no class definition is found in the bootstrap libraries, in the $JBOSS_HOME/lib libraries nor among the libraries associated with the repository's UCL, the UCL throws a ClassNotFoundException. However, if one of the pair UCL is able to load the class, the class will be added to the repository's class cache and from this moment on, it will be returned to any UCL requesting it.

在进程的结束,如果UCL对应的$ JBOSS_HOME / lib库没有找到类定义,、UCL将抛出ClassNotFoundException。然而,如果单UCL能够加载类,这个类将被添加到存储库的类缓存,从这一刻开始,它将返回到请求它任何UCL。


Even if the Java bootstrap packages or $JAVA_HOME/lib packages are not added to the repository's package map, the classes belonging to those packages can be loaded through the process described above and they are added to the repository too. This explains why you'll find "java.lang.String" in the repository.

即使java bootstrap packages or $JAVA_HOME/lib packages 没有添加到repository的package map,属于那些包的类仍然可以通过上述过程加载,他们也被添加到repository。这就解释了为什么你会发现在repository中存在java.lang.String


Class sharing can be turned off. J2EE-style class namespace isolation is available. You get an "isolated" application by scoping the application's deployment. At the JBoss class loading management system level, scoping translates into creating a child repository. A scoped application still can load the classes present in the classpaths of the UCLs or the root repository. Depending on whether repository's "Java2ParentDelegation" flag is turned on or off, a scoped application even has access to the class instances available in the root repository's cache. However, sibling child repositories can never share classes.

共享的类可以被关闭。J2EE-style类名称空间隔离是可见的。你可以通过分离部署应用程序获取孤立的应用在JBoss类加载管理系统层面,范围转化为创造一个child repository。一个隔离的应用程序是否可以加载在UCLs路径中的或者是root repository的classes。取决于respository的“Java2ParentDelegation”标志为开启或关闭,隔离的程序甚至可以访问root repository's cache。然而,子repositorys 不能共享类。














Note: Even if an HierarchicalLoaderRepository3$NoParentClassLoader instance has its parent set to be an instance of NoAnnotationURLClassLoader, as represented above, the NoParentClassLoader implementation of loadClass() always throws a ClassNotFoundException  to force the UCL to only load from its URLs. We will look closer at how NoParentClassLoader works and how a scoped application loads a class available in the system's bootstrap libraries when we present the Cases 3 and 4, below.

正如上面所代表的,即使一个HierarchicalLoaderRepository3$NoParentClassLoader实例包含他的父类NoAnnotationURLClassLoader的一个实例,NoParentClassLoader实现的loadClass()总是抛出ClassNotFoundException迫使UCL只加载的url。,在我们介绍了下面的用例3和用例4的情况下,我们将更详细地了解NoParentClassLoader如何工作以及如何f分离的应用程序加载一个在system's bootstrap libraries 可见的类。


Real World Scenarios 真实世界的场景


We will explore the complex interactions presented above based on concrete use cases.



We start by assuming that we want to deploy our own application (be it a JBoss service, a complex enterprise archive or a simple stateless session bean), and this application relies on an external library. For simplicity, we could assume that the utility library contains only a single class, org.useful.Utility. The Utility class can be packed together with the application classes inside the application archive, or it could be packed in its own archive, utility.jar. We could also assume that we always use the JBoss' default configuration.



Our hypothetical application consists of a single class, org.pkg1.A. We will consider several common situations:



Case 1. The Utility.class is present in the application's archive, but nowhere else on the server.



The short story: The current UCL will become the defining class loader of the class, and the class will be added to the repository's class cache. The details of the process are presented below.


简短的铺垫:当前的UCL将成为起决定性作用的类装入器的类,并且该UCL将被添加到repository'class cache。以下程序了该过程的细节













First time the application needs to use a strong-typed Utility reference, the VM asks the current UCL to load the class. The UCL tries to get the class from the repository's cache (1). If it is found, the class is returned and the process stops right here. If the class is not found, the UCL queries the repository for UCLs capable to load classes from the package the unknown class is part of (3). Being the single UCL able to define the class, the control returns to it and load manager calls loadClassLocally() on it (4). loadClassLocally() first tries to call super.loadClass() (5), which ends by involving the NoAnnotationClassLoader in the loading process. If the class is present in the bootstrap libraries or $JBOSS_HOME/lib (the URLs associated with the NoAnnotationClassLoader instance), it is loaded. Otherwise, the class is loaded from the URLs associated with the current UCL. Finally, the class is added to the repository's class cache (6).


This is the configuration of the UnifiedLoaderRepository after the class loading takes place.

(1)首次应用程序需要使用强类型的Utility引用,VM要求当前UCL加载该类。UCL的试图从repository's cache中加载。(2)如果可以找到,该类将会被返回摒弃进程将停止在这里。如果没有找到类,UCL将通过UCLs查询repository中的对应包,通过包查询未知的class。(3)单一的UCL能够定义类,控制返回它和负载管理器调用loadClassLocally()(4)首先尝试调用super.loadClass()(5)涉及NoAnnotationClassLoader结束的加载过程。如果类存在于bootstrap libraries 或$JBOSS_HOME/lib(与NoAnnotationClassLoader实例相关联的url),它被加载。否则,该类将只从对应的urls中加载到ucl中。最后,类添加到repository's class cache.(6)这就是class加载就为之后的UnifiedLoaderRepository的配置。













Case 2. The Utility.class is present both in the application's archive AND server/default/lib. The deployment is non-scoped.

该utility。class在the application's archive 中并且在 server/default/lib中 。该部署是无分离式部署


The short story: The version of the class available in server/default/lib/utility.jar will be used by the new deployment. The version of the class packed with the deployment will be ignored.

在 server/default/lib/utility.jar 中的class将会在新部署的应用中使用,新部署的应用中的class版本将会被忽略









The key element here is that when getPackageClassLoaders() is invoked on the repository, the method calls returns two potential classloaders that can load org.useful.Utility: UCL0 and UCL1. The UCL0 is chosen, because it was added to the repository before UCL1 and it will be used to load org.useful.Utility.


This is the configuration of the UnifiedLoaderRepository after the class loading takes place.













Case 3. The Utility.class is present both in the application's archive AND server/default/lib. The deployment is scoped and Java2ParentDelegation is turned off (default).

该utility。class在the application's archive 中并且在 server/default/lib中 。该部署是分离式的并且 Java2ParentDelegation是关闭的。


The short story: The utility class is loaded from the application's archive.

utility class 将从application‘s archive中加载。













Because Java2ParentDelegation is turned off by default, the Step (1.1) is never executed, parentRepository.getCachedClass() never gets called, so the UCL doesn't have access to the repository's cached classes.


Within the scope of the call to getPackageClassLoaders() at Step 3, the child repository also calls getPackageClassLoaders() on its parent, and also includes into the returned class loader set a UCL (constructed on the spot and associated to the child repository) that has among its ancestors an instance of NoAnnotationURLClassLoader, which ultimately can reach the system class loader. Why is that? Remember that the UCL's parent, HierarchicalLoaderRepository3$NoParentClassLoader, overrides loadClass() to always throw a ClassNotFoundException, thus forcing the UCL to only load from its URLs. If the UCL relies only on its class loader parent to load bootstrap classes, it will throw ClassNotFoundException and fail when your application wants to load "java.lang.String", for example. The NoAnnotationURLClassLoader-delegating UCL instance included in the return set provides a way load bootstrap library classes.


Always the HierarchialLoaderRepository's class loaders take precedence over the parent's (their "order" is lower). For the case depicted above, UCL1 is the preferred class loader.


This is the configuration of the UnifiedLoaderRepository after the class loading takes place.













Case 4. The Utility.class is present both in the application's archive AND server/default/lib. The deployment is scoped, but java2ParentDelegation is turned on.













When Java2ParentDelegation is turned on, the Step (1.1) is executed, and if a cached class is found in the parent repository, it is returned and the process stops here.


Within the scope of the call to getPackageClassLoaders() at Step (3), the child repository also calls getPackageClassLoaders() on its parent, but does not include into the returned class loader set a UCL with a parent to the system class loader. If there are no class loaders in the repository capable of handling the request ask the class loader itself in the event that its parent(s) can load the class (repository.loadClassFromClassLoader())


The HierarchialLoaderRepository's class loaders take precedence over the parent's (their "order" is lower). For the case depicted above, UCL1 is the preferred class loader.


This is the configuration of the UnifiedLoaderRepository after the class loading takes place.














Question: What happens if the parent delegation is true and a classloader already loaded the class in the parent repository's class cache?

Answer: My scoped application will use the already loaded class from the parent repository's class cache.


- See more at: https://developer.jboss.org/wiki/JBossClassLoadingUseCases#sthash.O2hTGTaz.dpuf
个人分类: javaweb学习
想对作者说点什么? 我来说一句