从Java 虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(Bootstrap ClassLoader),它C++语言实现,是虚拟机自身的一部分:另一种就是所有其他的类加载器,这些类加载器都出Java 语言实现,独立于虚拟机外部,并且全都继承自抽象类java.lang.ClassLoader。
从发开人员的角度来看,类加载器有下面3种。
(一)、启动类加载器 (BootstrapClassLoader):存放在<JAVA_HOM/lib>目录中的,或者被-Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中(一般是java.lang下的类,和java.io下的类)。启动类加载器无法被Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,直接使用null 代替。
(二)、扩展类加载器(ExtensionClassloader):由sun.misc.Launcher$ExtClassLoader 实现,负责加载<JAVA_HOME>/lib/ext 目录中的,或者被java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加器。
(三)、应用程序类加载器(ApplicationClassLoader): 这个类加载器由sun.misc.Launcher$AppClassLoader 来实现. 由于这个类加载器是ClassLoader 中的getSystemC lassLoader()方法的返回值,所以一般也称它为系统类加载器.负责加载用户类路径经( ClassPath )上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,就是程序中默认的类加载器。
双亲委派模型
类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。除了顶层的启动类加载器外,其余的类加载器都有父类加载器.
工作过程是: 如果一个类加载器收到了类加载的请求,先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父加器无法加载这个类时,子载加载器才自己去加载。
类加载器的三个特性
类加载器有三个特性,分别为委派,可见性和单一性,其他文章上对这三个特性的介绍如下:
* 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。
* 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
* 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
其中,委派机制是基础,在其他资料中也把这种机制叫做类加载器的双亲委派模型,其实说的是同一个意思。可加性和单一性是依赖于委派机制的。
破坏双亲委派模型
第一次“被破坏”发生在双亲委派模型出现之前。亲委派模型在JDK 1.2 之后才被引入,类加载器和抽象类Java. lang. ClassLoader 则在JDK 1 .0 时代就已经存在,对已经存在的用户自定义类加载器的实现代码, Java 设计者们引入双亲委派模型时做出了一些妥协。
第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的类加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被用户代码词用的API。但是基础类有时又要调用用户代码,如JNDI。此时就引入了线程上下文类加器。
第三次“被破坏”是由于用户对程序动态性的追求而导致的。代码热替换(HotSwap)、模块热部署(Hot Deployment)等
The Dreaded Thread Context Class Loader
Neil Bartlett
The Dreaded Thread Context Class Loader
Tomorrow at OSGi Community Event 2012 I will be talking about “How to make your library OSGi friendly without depending on OSGi”. During this talk I will make reference to Java’s Thread Context Class Loader (TCCL), but since it is only a 25-minute talk I will not have time to go into great detail on this subject. Therefore I thought it would be helpful to write this post with some of the background information.
Much of the technical information and research in this post is derived from an old, unpublished OSGi RFP authored by Peter Kriens. I have used it with his permission.
History
The thread context class loader (TCCL) has a very interesting history in Java.
Java defines a hierarchy of class loaders, and in a typical Java runtime the “application” loader at the bottom is aware of the “classpath” (as supplied via the -classpath/-cp switch), and the boot loader is aware of the JRE classes in rt.jar etc. There is also an “extension” class loader in the middle, which loads from the JRE ext directory. Class loaders delegate upwards to their parent, which means the application loader can see any class offered by the boot loader but the boot loader cannot see the application classes.
Sun first ran into problems with this scheme when implementing Java Serialization, as used in Remote Method Invocation (RMI). Deserializing data from a stream as Java objects required knowledge of the application classes, but the deserialization code was loaded by the boot loader; therefore it would not normally have visibility of the classes. The problem was fixed by adding private native methods to the JRE that would allow inspection of the call stack in order to find the first non-null class loader.
However, many other extension libraries soon ran into the same problem, and they could not (and should not!) all solve it with private native methods in the Sun JVM. At about the same time, J2EE became very important. J2EE is an application model where the Java code runs in a severely restricted environment. Applications run in a “silo”. Each application runs in a separate class loader and threads may not cross application/silo boundaries. Applications can use the extensions provided by the VM and the libraries provided by the container, but they cannot use any code from each other. This model also suffers from the problem that the libraries provided by the container cannot access the application code.
Therefore in Java 1.2 the TCCL was introduced: a class loader that is associated with a thread, as a thread-local variable. This means that any libary can at any time access the “current” context loader by asking the calling thread. This context loader is expected to have visibility to the specific application class loader that is responsible for the call, and therefore visibility of the application classes.
Sun also started to use the TCCL heavily itself, though without proper specification or guidance. The 1.5 JDK has 79 references to Thread.getContextClassLoader(). Since Java 1.4, the Java VM was modified to use the context class loader in JNDI, JAXP, CORBA, JMX, Xalan, Xerces, AWT, Beans, SQL, Logging, Prefs, RMI, Security, Swing, and most of the XML subsystems. Additionally most middleware libraries like Hibernate, Saxon, Jakarta Commons Logging and many others started to use the TCCL as either the first or last resort. Java has provided virtually no specification or guidelines on the use of TCCL. This has caused almost every library to follow a different strategy.
The key questions to answer regarding the correct use of TCCL are:
when should it be set, and by whom?
of what set of classes should it have visibility?
From a J2EE point of view these questions are relatively easy to answer because the programming model is so constrained. The container owns all the entry points; i.e., it controls the HTTP thread running the Servlets and the RMI sockets for EJBs, creation of additional threads is forbidden, etc) and it can therefore ensure that the TCCL is always set on entry into the application code. Since applications are entirely self-contained, the set of visible classes is simply all classes owned by the application.
In a modular runtime such as OSGi these questions are far harder to answer. Bundles are free to create their own threads and entry points, and there is no general way to identify the set of bundles that contribute classes for an “application”; indeed, the very concept of an “application” is much harder to pin down. We could constrain the programming model and require that an application be deployed as a single bundle with no dependencies, but this would negate most of the benefits of using OSGi. Therefore OSGi does not attempt to define what the TCCL should be at any particular time, and typically it is left null.
Alternatives to TCCL
In our own OSGi-compliant code, or in any library with explicit OSGi support, we need not worry about the TCCL, because OSGi Services provide a far cleaner approach than any ClassLoader-based approach to loading extensions. However, legacy third-party libraries are still an issue. Many such libraries attempt to load application classes by name at runtime: for example, Hibernate reads class names from .hbm.xml files and then creates instances of those classes for each database record.
When you move such a library to any kind of modular environment – including OSGi, JBoss Modules or even Jigsaw – you find that it breaks, because the name of a class is not sufficient to uniquely identify it. The identity of a class consists of its fully qualified name AND the class loader that defined it, which in OSGi usually corresponds to the bundle that contains it. So in addition to the name, we need to know the class loader from which to load the class. Due to the wide variety of class loading environments created by various application servers, many libraries attempt to solve this problem with a set of heuristics. Consulting the TCCL is usually one of the heuristics, along with checking the library’s own class loader, the JRE extension class loader, etc.
If a library only consults the TCCL then we are somewhat stuck: we will have to explicitly set the TCCL from our own code before calling into the library. Fortunately this is rarely the case, for example most such libraries will also call Class.forName(), which means the library will consult its own class loader to load the class. This is still far from ideal, but we can work around it by deploying a single fragment rather than scattering calls to setContextClassLoader() throughout our code. Far better is when a library eschews class names entirely and allows Class objects to be passed; or at least offers an API method to set the class loader from which the class names should be loaded.
Unfortunately it’s very hard to predict in advance – at least without closely inspecting the code – what kind of heuristics the library employs, again because of the complete lack of specification and guidance in this area.
Summary
I hope you will attend my talk tomorrow, which is mainly addressed to authors of Java libraries that want their code to work well in OSGi, but not only in OSGi. As you can doubtless infer from this blog post, avoiding the TCCL and other weird ClassLoader-based hacks is a big part of being OSGi-friendly, but I will also talk about service dynamics and configuration issues. See you there!
Posted on 23 October 2012
Find a way out of the ClassLoader maze
中文翻译 引用
[说明]几个关键字将不翻译
ClassLoader
System
Context
Thread
走出ClassLoader的迷宫
System、Current和Context ClassLoader?分别在何种情形下使用?
1、问题:在何种情形下使用thread.getcontextclassloader()?
尽管没经常遇到这个问题,但是想获得准确的答案并不那么容易,特别是在开发应用框架的时候,你需要动态的加载一些类和资源,不可避免的你会被此困扰。一般来说,动态载入资源有三种ClassLoader可以选择,System ClassLoader(也叫App ClassLoader)、当前类的ClassLoader和CurrentThread的Context ClassLoader。那么, 如何选择使用?
首先可以简单排除的是System ClassLoader,这个ClassLoader负责从参数-classpath、-cp、和操作系统CLASSPATH中载入资源。并且,任何ClassLoader的getSystemXXX()方法都是有以上几个路径指定的。我们应该很少需要编写直接使用ClassLoader的程序,否则你的代码将只能在命令行运行,发布你的代码成为ejb、web应用或者java web start应用,我肯定他们会崩溃!
接下来,我们只剩下两个选择了:当前ClassLoader和Thread Context ClassLoader
Current ClassLoader:当前类所属的ClassLoader,在虚拟机中类之间引用,默认就是使用这个ClassLoader。另外,当你使用Class.forName(), Class.getResource()这几个不带ClassLoader参数的方法是,默认同样适用当前类的ClassLoader。你可以通过方法XX.class.GetClassLoader()获取。
Thread Context ClassLoader,没一个Thread有一个相关联系的Context ClassLoader(由native方法建立的除外),可以通过Thread.setContextClassLoader()方法设置。如果你没有主动设置,Thread默认集成Parent Thread的 Context ClassLoader(注意,是parent Thread 不是父类)。如果 你整个应用中都没有对此作任何处理,那么 所有的Thread都会以System ClassLoader作为Context ClassLoader。知道这一点很重要,因为从web服务器,java企业服务器使用一些复杂而且精巧的ClassLoader结构去实现诸如JNDI、线程池和热部署等功能以来,这种简单的情况越发的少见了。
这篇文章中为什么把Thread Context ClassLoader放在首要的位置,别人并没有大张旗鼓的介绍它?很多开发者都对此不甚了解,因为sun没有提供很好的说明文档。
事实上,Context ClassLoader提供一个突破委托代理机制的后门。虚拟机通过父子层次关系组织管理ClassLoader,没有个ClassLoader都有一个Parent ClassLoader(BootStartp不在此范围之内),当要求一个ClassLoader装载一个类是,他首先请求Parent ClassLoader去装载,只有parent ClassLoader装载失败,才会尝试自己装载。
但是,某些时候这种顺序机制会造成困扰,特别是jvm需要动态载入有开发者提供的资源时。就以JNDI为例,JNDI的类是由bootstarp ClassLoader从rt.jar中间载入的,但是JNDI具体的核心驱动是由正式的实现提供的,并且通常会处于-cp参数之下(注:也就是默认的System ClassLoader管理),这就要求bootstartp ClassLoader去载入只有SystemClassLoader可见的类,正常的逻辑就没办法处理。怎么办呢?parent可以通过获得当前调用Thread的方法获得调用线程的Context ClassLoder 来载入类。
顺带补充一句,JAXP从1.4之后也换成了类似JNDI的ClassLoader实现,嘿嘿,刚刚我说什么来着,SUN文档缺乏 ^_^
介绍完这些之后,我们走到的十字路口,任一选择都不是万能的。一些人认为Context ClassLoader将会是新的标准。但是 一旦你的多线程需要通讯某些共享数据,你会发现,你将有一张极其丑陋的ClassLoader分布图,除非所有的线程使用一样的Context ClassLoader。并且委派使用当前ClassLoder对一些方法来说是默认继承来的,比如说Class.forName()。尽管你明确的在任何你能控制的地方使用Context ClassLoader,但是毕竟还有很多代码不归你管(备注:想起一个关于UNIX名字来源的笑话)。
某些应用服务器使用不同的ClassLoder作为Context ClassLoader和当前ClassLoader,并且这些ClassLoader有着相同的ClassPath,但没有父子关系,这使得情况更复杂。请列位看官,花几秒钟时间想一想,为什么这样不好?被载入的类在虚拟机内部有一个全名称,不同的ClassLoader载入的相同名称的类是不一样的,这就隐藏了类型转换错误的隐患。(注:奶奶的 俺就遇到过,JBOSSClassLoader机制蛮挫的)
这种混乱事实上在java类中也有,试着去猜测任何一个包含动态加载的java规范的ClassLoader机制,以下是一个清单:
JNDI uses context classloaders
Class.getResource() and Class.forName() use the current classloader
JAXP uses context classloaders (as of J2SE 1.4)
java.util.ResourceBundle uses the caller's current classloader
URL protocol handlers specified via java.protocol.handler.pkgs system property are looked up in the bootstrap and system classloaders only
Java Serialization API uses the caller's current classloader by default
而且关于这些资源的类加载机制文档时很少。
java开发人员应该怎么做?
如果你的实现是利用特定的框架,那么恭喜你,实现它远比实现框架要简单得多!例如,在web应用和EJB应用中,你仅仅只要使用 Class.getResource()就足够了。
其他的情形下,俺有个建议(这个原则是俺工作中发现的,侵权必究,抵制盗版。),
下面这个类可以在整个应用中的任何地方使用,作为一个全局的ClassLoader(所有的示例代码可以从download下载):
1 public abstract class ClassLoaderResolver {
2 /**
3 * This method selects the best classloader instance to be used for
4 * class/resource loading by whoever calls this method. The decision
5 * typically involves choosing between the caller's current, thread context,
6 * system, and other classloaders in the JVM and is made by the
7 * {@link IClassLoadStrategy} instance established by the last call to
8 * {@link #setStrategy}.
9 *
10 * @return classloader to be used by the caller ['null' indicates the
11 * primordial loader]
12 */
13 public static synchronized ClassLoader getClassLoader() {
14 final Class caller = getCallerClass(0);
15 final ClassLoadContext ctx = new ClassLoadContext(caller);
16
17 return s_strategy.getClassLoader(ctx);
18 }
19
20 public static synchronized IClassLoadStrategy getStrategy() {
21 return s_strategy;
22 }
23
24 public static synchronized IClassLoadStrategy setStrategy(
25 final IClassLoadStrategy strategy) {
26 final IClassLoadStrategy old = s_strategy;
27 s_strategy = strategy;
28
29 return old;
30 }
31
32 /**
33 * A helper class to get the call context. It subclasses SecurityManager to
34 * make getClassContext() accessible. An instance of CallerResolver only
35 * needs to be created, not installed as an actual security manager.
36 */
37 private static final class CallerResolver extends SecurityManager {
38 protected Class[] getClassContext() {
39 return super.getClassContext();
40 }
41
42 } // End of nested class
43
44 /*
45 * Indexes into the current method call context with a given offset.
46 */
47 private static Class getCallerClass(final int callerOffset) {
48 return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET
49 + callerOffset];
50 }
51
52 private static IClassLoadStrategy s_strategy; // initialized in <clinit>
53
54 private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if
55 // this class is
56 // redesigned
57 private static final CallerResolver CALLER_RESOLVER; // set in <clinit>
58
59 static {
60 try {
61 // This can fail if the current SecurityManager does not allow
62 // RuntimePermission ("createSecurityManager"):
63
64 CALLER_RESOLVER = new CallerResolver();
65 } catch (SecurityException se) {
66 throw new RuntimeException(
67 "ClassLoaderResolver: could not create CallerResolver: "
68 + se);
69 }
70
71 s_strategy = new DefaultClassLoadStrategy();
72 }
73 } // End of class.
74
75
76
通过ClassLoaderResolver.getClassLoader()方法获得一个ClassLoader的引用,并且利用正常的ClassLoader的api去加载资源,你也可以使用 ResourceLoader API作为备选方案
1 public abstract class ResourceLoader {
2
3 /**
4 * @see java.lang.ClassLoader#loadClass(java.lang.String)
5 */
6 public static Class loadClass (final String name)throws ClassNotFoundException{
7
8 final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
9
10 return Class.forName (name, false, loader);
11
12 }
13
14 /**
15
16 * @see java.lang.ClassLoader#getResource(java.lang.String)
17
18 */
19
20
21 public static URL getResource (final String name){
22
23 final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
24
25 if (loader != null)return loader.getResource (name);
26 else return ClassLoader.getSystemResource (name);
27 }
28 more methods
29
30 } // End of class
而决定使用何种ClassLoader策略是由接口实现的,这是一种插件机制,方便变更。
public interface IClassLoadStrategy{
ClassLoader getClassLoader (ClassLoadContext ctx);
} // End of interface
它需要一个ClassLoader Context 对象去决定使用何种ClassLoader策略。
1 public class ClassLoadContext{
2
3 public final Class getCallerClass (){
4 return m_caller;
5 }
6
7 ClassLoadContext (final Class caller){
8 m_caller = caller;
9
10 }
11
12 private final Class m_caller;
13
14 } // End of class
ClassLoadContext.getCallerClass()返回调用者给ClassLoaderResolver 或者 ResourceLoader,因此能获得调用者的ClassLoader。需要注意的是,调用者是不会变的 (注:作者使用的final修饰字)。俺的方法不需要对现有的业务方法做扩展,而且可以作为静态方法是用。而且,你可以根据自己的业务场景实现独特的ClassLoaderContext。
看出来没,这是一种很熟悉的设计模式,XD ,把获得ClassLoader的策略从业务中独立出来,这个策略可以是"总是用ContextClassLoader"或者"总是用当前ClassLoader"。想预先知道那种策略是正确的比较困难,那么这种模式可以让你简单的改变策略。
俺写了一个默认的实现,基本可以对付95%的场景(enjoy yourself)
1 public class DefaultClassLoadStrategy implements IClassLoadStrategy{
2
3 public ClassLoader getClassLoader (final ClassLoadContext ctx){
4
5 final ClassLoader callerLoader = ctx.getCallerClass ().getClassLoader ();
6
7 final ClassLoader contextLoader = Thread.currentThread ().getContextClassLoader ();
8
9 ClassLoader result;
10 // If 'callerLoader' and 'contextLoader' are in a parent-child
11 // relationship, always choose the child:
12 if (isChild (contextLoader, callerLoader))result = callerLoader;
13 else if (isChild (callerLoader, contextLoader))result = contextLoader;
14 else{
15 // This else branch could be merged into the previous one,
16 // but I show it here to emphasize the ambiguous case:
17 result = contextLoader;
18 }
19 final ClassLoader systemLoader = ClassLoader.getSystemClassLoader ();
20
21
22 // Precaution for when deployed as a bootstrap or extension class:
23 if (isChild (result, systemLoader))result = systemLoader;
24 return result;
25 }
26
27
28
29 more methods
30
31 } // End of class
32
上面的逻辑比较简单,如果当前ClassLoader和Context ClassLoader是父子关系,那就总选儿子,根据委托原则,这个很容易理解。
如果两人平级,选择正确的ClassLoader很重要,运行时不允许含糊。这种情况下,我的代码选择Context ClassLoader(这是俺个人的经验之谈),当然也不要担心不能改变,你能随便根据需要改变。一般而言,Context ClassLoader比较适合框架,而Current ClassLoader在业务逻辑中用的更多。
最后,检查确保选中的ClassLoader不是System ClassLoader的parent,一旦高于System ClassLoader ,请使用System ClassLoader(你的类部署在Ext路径下面,就会出现这种情况)。
请注意,俺故意没关注被载入资源的名称。Java XML API 成为java 核心api的经历告诉我们,根据资源名称过滤是很不cool的idea。而且 我也没有去确认到底哪个ClassLoader被取得了,因为只要清楚原理,这很容易被推理出来。(哈哈,俺是强淫)
尽管讨论java 的ClassLoader不是一个很cool的话题(译者注,当年不cool,但是现在很cool),而且Java EE的ClassLoader策略越发的依赖各种平台的升级。如果这没有一个更好的设计的话,将会变成一个大大的问题。不敢您是否同意俺的观点,俺尊重你说话的权利,所以请给俺分享您的意见经验。
作者介绍:
Vladimir Roubtsov,曾经使用多种语言有超过13年的编程经历(恩 现在应该超过15年了 hoho),95年开始接触java(hoho 俺是99年看的第一本java书)。现在为Trilogy in Austin, Texas开发企业软件。
翻译完了,MMD 翻译还是很麻烦的。 XD ........
从发开人员的角度来看,类加载器有下面3种。
(一)、启动类加载器 (BootstrapClassLoader):存放在<JAVA_HOM/lib>目录中的,或者被-Xbootclasspath 参数所指定的路径中的,并且是虚拟机识别的类库加载到虚拟机内存中(一般是java.lang下的类,和java.io下的类)。启动类加载器无法被Java 程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给引导类加载器,直接使用null 代替。
(二)、扩展类加载器(ExtensionClassloader):由sun.misc.Launcher$ExtClassLoader 实现,负责加载<JAVA_HOME>/lib/ext 目录中的,或者被java.ext.dirs 系统变量所指定的路径中的所有类库,开发者可以直接使用扩展类加器。
(三)、应用程序类加载器(ApplicationClassLoader): 这个类加载器由sun.misc.Launcher$AppClassLoader 来实现. 由于这个类加载器是ClassLoader 中的getSystemC lassLoader()方法的返回值,所以一般也称它为系统类加载器.负责加载用户类路径经( ClassPath )上所指定的类库,开发者可以直接使用这个类加载器,如果应用程序中没有自定义过自己的类加载器,就是程序中默认的类加载器。
双亲委派模型
类加载器之间的层次关系,称为类加载器的双亲委派模型(Parents Delegation Model)。除了顶层的启动类加载器外,其余的类加载器都有父类加载器.
工作过程是: 如果一个类加载器收到了类加载的请求,先不会自己去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,所有的加载请求最终都会传送到顶层的启动类加载器中,只有当父加器无法加载这个类时,子载加载器才自己去加载。
类加载器的三个特性
类加载器有三个特性,分别为委派,可见性和单一性,其他文章上对这三个特性的介绍如下:
* 委托机制是指将加载一个类的请求交给父类加载器,如果这个父类加载器不能够找到或者加载这个类,那么再加载它。
* 可见性的原理是子类的加载器可以看见所有的父类加载器加载的类,而父类加载器看不到子类加载器加载的类。
* 单一性原理是指仅加载一个类一次,这是由委托机制确保子类加载器不会再次加载父类加载器加载过的类。
其中,委派机制是基础,在其他资料中也把这种机制叫做类加载器的双亲委派模型,其实说的是同一个意思。可加性和单一性是依赖于委派机制的。
破坏双亲委派模型
第一次“被破坏”发生在双亲委派模型出现之前。亲委派模型在JDK 1.2 之后才被引入,类加载器和抽象类Java. lang. ClassLoader 则在JDK 1 .0 时代就已经存在,对已经存在的用户自定义类加载器的实现代码, Java 设计者们引入双亲委派模型时做出了一些妥协。
第二次“被破坏”是由这个模型自身的缺陷所导致的,双亲委派很好地解决了各个类加载器的基础类的统一问题(越基础的类由越上层的类加载器进行加载),基础类之所以被称为“基础”,是因为它们总是作为被用户代码词用的API。但是基础类有时又要调用用户代码,如JNDI。此时就引入了线程上下文类加器。
第三次“被破坏”是由于用户对程序动态性的追求而导致的。代码热替换(HotSwap)、模块热部署(Hot Deployment)等
The Dreaded Thread Context Class Loader
引用
Neil Bartlett
The Dreaded Thread Context Class Loader
Tomorrow at OSGi Community Event 2012 I will be talking about “How to make your library OSGi friendly without depending on OSGi”. During this talk I will make reference to Java’s Thread Context Class Loader (TCCL), but since it is only a 25-minute talk I will not have time to go into great detail on this subject. Therefore I thought it would be helpful to write this post with some of the background information.
Much of the technical information and research in this post is derived from an old, unpublished OSGi RFP authored by Peter Kriens. I have used it with his permission.
History
The thread context class loader (TCCL) has a very interesting history in Java.
Java defines a hierarchy of class loaders, and in a typical Java runtime the “application” loader at the bottom is aware of the “classpath” (as supplied via the -classpath/-cp switch), and the boot loader is aware of the JRE classes in rt.jar etc. There is also an “extension” class loader in the middle, which loads from the JRE ext directory. Class loaders delegate upwards to their parent, which means the application loader can see any class offered by the boot loader but the boot loader cannot see the application classes.
Sun first ran into problems with this scheme when implementing Java Serialization, as used in Remote Method Invocation (RMI). Deserializing data from a stream as Java objects required knowledge of the application classes, but the deserialization code was loaded by the boot loader; therefore it would not normally have visibility of the classes. The problem was fixed by adding private native methods to the JRE that would allow inspection of the call stack in order to find the first non-null class loader.
However, many other extension libraries soon ran into the same problem, and they could not (and should not!) all solve it with private native methods in the Sun JVM. At about the same time, J2EE became very important. J2EE is an application model where the Java code runs in a severely restricted environment. Applications run in a “silo”. Each application runs in a separate class loader and threads may not cross application/silo boundaries. Applications can use the extensions provided by the VM and the libraries provided by the container, but they cannot use any code from each other. This model also suffers from the problem that the libraries provided by the container cannot access the application code.
Therefore in Java 1.2 the TCCL was introduced: a class loader that is associated with a thread, as a thread-local variable. This means that any libary can at any time access the “current” context loader by asking the calling thread. This context loader is expected to have visibility to the specific application class loader that is responsible for the call, and therefore visibility of the application classes.
Sun also started to use the TCCL heavily itself, though without proper specification or guidance. The 1.5 JDK has 79 references to Thread.getContextClassLoader(). Since Java 1.4, the Java VM was modified to use the context class loader in JNDI, JAXP, CORBA, JMX, Xalan, Xerces, AWT, Beans, SQL, Logging, Prefs, RMI, Security, Swing, and most of the XML subsystems. Additionally most middleware libraries like Hibernate, Saxon, Jakarta Commons Logging and many others started to use the TCCL as either the first or last resort. Java has provided virtually no specification or guidelines on the use of TCCL. This has caused almost every library to follow a different strategy.
The key questions to answer regarding the correct use of TCCL are:
when should it be set, and by whom?
of what set of classes should it have visibility?
From a J2EE point of view these questions are relatively easy to answer because the programming model is so constrained. The container owns all the entry points; i.e., it controls the HTTP thread running the Servlets and the RMI sockets for EJBs, creation of additional threads is forbidden, etc) and it can therefore ensure that the TCCL is always set on entry into the application code. Since applications are entirely self-contained, the set of visible classes is simply all classes owned by the application.
In a modular runtime such as OSGi these questions are far harder to answer. Bundles are free to create their own threads and entry points, and there is no general way to identify the set of bundles that contribute classes for an “application”; indeed, the very concept of an “application” is much harder to pin down. We could constrain the programming model and require that an application be deployed as a single bundle with no dependencies, but this would negate most of the benefits of using OSGi. Therefore OSGi does not attempt to define what the TCCL should be at any particular time, and typically it is left null.
Alternatives to TCCL
In our own OSGi-compliant code, or in any library with explicit OSGi support, we need not worry about the TCCL, because OSGi Services provide a far cleaner approach than any ClassLoader-based approach to loading extensions. However, legacy third-party libraries are still an issue. Many such libraries attempt to load application classes by name at runtime: for example, Hibernate reads class names from .hbm.xml files and then creates instances of those classes for each database record.
When you move such a library to any kind of modular environment – including OSGi, JBoss Modules or even Jigsaw – you find that it breaks, because the name of a class is not sufficient to uniquely identify it. The identity of a class consists of its fully qualified name AND the class loader that defined it, which in OSGi usually corresponds to the bundle that contains it. So in addition to the name, we need to know the class loader from which to load the class. Due to the wide variety of class loading environments created by various application servers, many libraries attempt to solve this problem with a set of heuristics. Consulting the TCCL is usually one of the heuristics, along with checking the library’s own class loader, the JRE extension class loader, etc.
If a library only consults the TCCL then we are somewhat stuck: we will have to explicitly set the TCCL from our own code before calling into the library. Fortunately this is rarely the case, for example most such libraries will also call Class.forName(), which means the library will consult its own class loader to load the class. This is still far from ideal, but we can work around it by deploying a single fragment rather than scattering calls to setContextClassLoader() throughout our code. Far better is when a library eschews class names entirely and allows Class objects to be passed; or at least offers an API method to set the class loader from which the class names should be loaded.
Unfortunately it’s very hard to predict in advance – at least without closely inspecting the code – what kind of heuristics the library employs, again because of the complete lack of specification and guidance in this area.
Summary
I hope you will attend my talk tomorrow, which is mainly addressed to authors of Java libraries that want their code to work well in OSGi, but not only in OSGi. As you can doubtless infer from this blog post, avoiding the TCCL and other weird ClassLoader-based hacks is a big part of being OSGi-friendly, but I will also talk about service dynamics and configuration issues. See you there!
Posted on 23 October 2012
Find a way out of the ClassLoader maze
中文翻译 引用
引用
[说明]几个关键字将不翻译
ClassLoader
System
Context
Thread
走出ClassLoader的迷宫
System、Current和Context ClassLoader?分别在何种情形下使用?
1、问题:在何种情形下使用thread.getcontextclassloader()?
尽管没经常遇到这个问题,但是想获得准确的答案并不那么容易,特别是在开发应用框架的时候,你需要动态的加载一些类和资源,不可避免的你会被此困扰。一般来说,动态载入资源有三种ClassLoader可以选择,System ClassLoader(也叫App ClassLoader)、当前类的ClassLoader和CurrentThread的Context ClassLoader。那么, 如何选择使用?
首先可以简单排除的是System ClassLoader,这个ClassLoader负责从参数-classpath、-cp、和操作系统CLASSPATH中载入资源。并且,任何ClassLoader的getSystemXXX()方法都是有以上几个路径指定的。我们应该很少需要编写直接使用ClassLoader的程序,否则你的代码将只能在命令行运行,发布你的代码成为ejb、web应用或者java web start应用,我肯定他们会崩溃!
接下来,我们只剩下两个选择了:当前ClassLoader和Thread Context ClassLoader
Current ClassLoader:当前类所属的ClassLoader,在虚拟机中类之间引用,默认就是使用这个ClassLoader。另外,当你使用Class.forName(), Class.getResource()这几个不带ClassLoader参数的方法是,默认同样适用当前类的ClassLoader。你可以通过方法XX.class.GetClassLoader()获取。
Thread Context ClassLoader,没一个Thread有一个相关联系的Context ClassLoader(由native方法建立的除外),可以通过Thread.setContextClassLoader()方法设置。如果你没有主动设置,Thread默认集成Parent Thread的 Context ClassLoader(注意,是parent Thread 不是父类)。如果 你整个应用中都没有对此作任何处理,那么 所有的Thread都会以System ClassLoader作为Context ClassLoader。知道这一点很重要,因为从web服务器,java企业服务器使用一些复杂而且精巧的ClassLoader结构去实现诸如JNDI、线程池和热部署等功能以来,这种简单的情况越发的少见了。
这篇文章中为什么把Thread Context ClassLoader放在首要的位置,别人并没有大张旗鼓的介绍它?很多开发者都对此不甚了解,因为sun没有提供很好的说明文档。
事实上,Context ClassLoader提供一个突破委托代理机制的后门。虚拟机通过父子层次关系组织管理ClassLoader,没有个ClassLoader都有一个Parent ClassLoader(BootStartp不在此范围之内),当要求一个ClassLoader装载一个类是,他首先请求Parent ClassLoader去装载,只有parent ClassLoader装载失败,才会尝试自己装载。
但是,某些时候这种顺序机制会造成困扰,特别是jvm需要动态载入有开发者提供的资源时。就以JNDI为例,JNDI的类是由bootstarp ClassLoader从rt.jar中间载入的,但是JNDI具体的核心驱动是由正式的实现提供的,并且通常会处于-cp参数之下(注:也就是默认的System ClassLoader管理),这就要求bootstartp ClassLoader去载入只有SystemClassLoader可见的类,正常的逻辑就没办法处理。怎么办呢?parent可以通过获得当前调用Thread的方法获得调用线程的Context ClassLoder 来载入类。
顺带补充一句,JAXP从1.4之后也换成了类似JNDI的ClassLoader实现,嘿嘿,刚刚我说什么来着,SUN文档缺乏 ^_^
介绍完这些之后,我们走到的十字路口,任一选择都不是万能的。一些人认为Context ClassLoader将会是新的标准。但是 一旦你的多线程需要通讯某些共享数据,你会发现,你将有一张极其丑陋的ClassLoader分布图,除非所有的线程使用一样的Context ClassLoader。并且委派使用当前ClassLoder对一些方法来说是默认继承来的,比如说Class.forName()。尽管你明确的在任何你能控制的地方使用Context ClassLoader,但是毕竟还有很多代码不归你管(备注:想起一个关于UNIX名字来源的笑话)。
某些应用服务器使用不同的ClassLoder作为Context ClassLoader和当前ClassLoader,并且这些ClassLoader有着相同的ClassPath,但没有父子关系,这使得情况更复杂。请列位看官,花几秒钟时间想一想,为什么这样不好?被载入的类在虚拟机内部有一个全名称,不同的ClassLoader载入的相同名称的类是不一样的,这就隐藏了类型转换错误的隐患。(注:奶奶的 俺就遇到过,JBOSSClassLoader机制蛮挫的)
这种混乱事实上在java类中也有,试着去猜测任何一个包含动态加载的java规范的ClassLoader机制,以下是一个清单:
JNDI uses context classloaders
Class.getResource() and Class.forName() use the current classloader
JAXP uses context classloaders (as of J2SE 1.4)
java.util.ResourceBundle uses the caller's current classloader
URL protocol handlers specified via java.protocol.handler.pkgs system property are looked up in the bootstrap and system classloaders only
Java Serialization API uses the caller's current classloader by default
而且关于这些资源的类加载机制文档时很少。
java开发人员应该怎么做?
如果你的实现是利用特定的框架,那么恭喜你,实现它远比实现框架要简单得多!例如,在web应用和EJB应用中,你仅仅只要使用 Class.getResource()就足够了。
其他的情形下,俺有个建议(这个原则是俺工作中发现的,侵权必究,抵制盗版。),
下面这个类可以在整个应用中的任何地方使用,作为一个全局的ClassLoader(所有的示例代码可以从download下载):
1 public abstract class ClassLoaderResolver {
2 /**
3 * This method selects the best classloader instance to be used for
4 * class/resource loading by whoever calls this method. The decision
5 * typically involves choosing between the caller's current, thread context,
6 * system, and other classloaders in the JVM and is made by the
7 * {@link IClassLoadStrategy} instance established by the last call to
8 * {@link #setStrategy}.
9 *
10 * @return classloader to be used by the caller ['null' indicates the
11 * primordial loader]
12 */
13 public static synchronized ClassLoader getClassLoader() {
14 final Class caller = getCallerClass(0);
15 final ClassLoadContext ctx = new ClassLoadContext(caller);
16
17 return s_strategy.getClassLoader(ctx);
18 }
19
20 public static synchronized IClassLoadStrategy getStrategy() {
21 return s_strategy;
22 }
23
24 public static synchronized IClassLoadStrategy setStrategy(
25 final IClassLoadStrategy strategy) {
26 final IClassLoadStrategy old = s_strategy;
27 s_strategy = strategy;
28
29 return old;
30 }
31
32 /**
33 * A helper class to get the call context. It subclasses SecurityManager to
34 * make getClassContext() accessible. An instance of CallerResolver only
35 * needs to be created, not installed as an actual security manager.
36 */
37 private static final class CallerResolver extends SecurityManager {
38 protected Class[] getClassContext() {
39 return super.getClassContext();
40 }
41
42 } // End of nested class
43
44 /*
45 * Indexes into the current method call context with a given offset.
46 */
47 private static Class getCallerClass(final int callerOffset) {
48 return CALLER_RESOLVER.getClassContext()[CALL_CONTEXT_OFFSET
49 + callerOffset];
50 }
51
52 private static IClassLoadStrategy s_strategy; // initialized in <clinit>
53
54 private static final int CALL_CONTEXT_OFFSET = 3; // may need to change if
55 // this class is
56 // redesigned
57 private static final CallerResolver CALLER_RESOLVER; // set in <clinit>
58
59 static {
60 try {
61 // This can fail if the current SecurityManager does not allow
62 // RuntimePermission ("createSecurityManager"):
63
64 CALLER_RESOLVER = new CallerResolver();
65 } catch (SecurityException se) {
66 throw new RuntimeException(
67 "ClassLoaderResolver: could not create CallerResolver: "
68 + se);
69 }
70
71 s_strategy = new DefaultClassLoadStrategy();
72 }
73 } // End of class.
74
75
76
通过ClassLoaderResolver.getClassLoader()方法获得一个ClassLoader的引用,并且利用正常的ClassLoader的api去加载资源,你也可以使用 ResourceLoader API作为备选方案
1 public abstract class ResourceLoader {
2
3 /**
4 * @see java.lang.ClassLoader#loadClass(java.lang.String)
5 */
6 public static Class loadClass (final String name)throws ClassNotFoundException{
7
8 final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
9
10 return Class.forName (name, false, loader);
11
12 }
13
14 /**
15
16 * @see java.lang.ClassLoader#getResource(java.lang.String)
17
18 */
19
20
21 public static URL getResource (final String name){
22
23 final ClassLoader loader = ClassLoaderResolver.getClassLoader (1);
24
25 if (loader != null)return loader.getResource (name);
26 else return ClassLoader.getSystemResource (name);
27 }
28 more methods
29
30 } // End of class
而决定使用何种ClassLoader策略是由接口实现的,这是一种插件机制,方便变更。
public interface IClassLoadStrategy{
ClassLoader getClassLoader (ClassLoadContext ctx);
} // End of interface
它需要一个ClassLoader Context 对象去决定使用何种ClassLoader策略。
1 public class ClassLoadContext{
2
3 public final Class getCallerClass (){
4 return m_caller;
5 }
6
7 ClassLoadContext (final Class caller){
8 m_caller = caller;
9
10 }
11
12 private final Class m_caller;
13
14 } // End of class
ClassLoadContext.getCallerClass()返回调用者给ClassLoaderResolver 或者 ResourceLoader,因此能获得调用者的ClassLoader。需要注意的是,调用者是不会变的 (注:作者使用的final修饰字)。俺的方法不需要对现有的业务方法做扩展,而且可以作为静态方法是用。而且,你可以根据自己的业务场景实现独特的ClassLoaderContext。
看出来没,这是一种很熟悉的设计模式,XD ,把获得ClassLoader的策略从业务中独立出来,这个策略可以是"总是用ContextClassLoader"或者"总是用当前ClassLoader"。想预先知道那种策略是正确的比较困难,那么这种模式可以让你简单的改变策略。
俺写了一个默认的实现,基本可以对付95%的场景(enjoy yourself)
1 public class DefaultClassLoadStrategy implements IClassLoadStrategy{
2
3 public ClassLoader getClassLoader (final ClassLoadContext ctx){
4
5 final ClassLoader callerLoader = ctx.getCallerClass ().getClassLoader ();
6
7 final ClassLoader contextLoader = Thread.currentThread ().getContextClassLoader ();
8
9 ClassLoader result;
10 // If 'callerLoader' and 'contextLoader' are in a parent-child
11 // relationship, always choose the child:
12 if (isChild (contextLoader, callerLoader))result = callerLoader;
13 else if (isChild (callerLoader, contextLoader))result = contextLoader;
14 else{
15 // This else branch could be merged into the previous one,
16 // but I show it here to emphasize the ambiguous case:
17 result = contextLoader;
18 }
19 final ClassLoader systemLoader = ClassLoader.getSystemClassLoader ();
20
21
22 // Precaution for when deployed as a bootstrap or extension class:
23 if (isChild (result, systemLoader))result = systemLoader;
24 return result;
25 }
26
27
28
29 more methods
30
31 } // End of class
32
上面的逻辑比较简单,如果当前ClassLoader和Context ClassLoader是父子关系,那就总选儿子,根据委托原则,这个很容易理解。
如果两人平级,选择正确的ClassLoader很重要,运行时不允许含糊。这种情况下,我的代码选择Context ClassLoader(这是俺个人的经验之谈),当然也不要担心不能改变,你能随便根据需要改变。一般而言,Context ClassLoader比较适合框架,而Current ClassLoader在业务逻辑中用的更多。
最后,检查确保选中的ClassLoader不是System ClassLoader的parent,一旦高于System ClassLoader ,请使用System ClassLoader(你的类部署在Ext路径下面,就会出现这种情况)。
请注意,俺故意没关注被载入资源的名称。Java XML API 成为java 核心api的经历告诉我们,根据资源名称过滤是很不cool的idea。而且 我也没有去确认到底哪个ClassLoader被取得了,因为只要清楚原理,这很容易被推理出来。(哈哈,俺是强淫)
尽管讨论java 的ClassLoader不是一个很cool的话题(译者注,当年不cool,但是现在很cool),而且Java EE的ClassLoader策略越发的依赖各种平台的升级。如果这没有一个更好的设计的话,将会变成一个大大的问题。不敢您是否同意俺的观点,俺尊重你说话的权利,所以请给俺分享您的意见经验。
作者介绍:
Vladimir Roubtsov,曾经使用多种语言有超过13年的编程经历(恩 现在应该超过15年了 hoho),95年开始接触java(hoho 俺是99年看的第一本java书)。现在为Trilogy in Austin, Texas开发企业软件。
翻译完了,MMD 翻译还是很麻烦的。 XD ........
发表评论
-
charles4.2下载与破解方法以及配置https
2020-02-26 09:03 2有两个抓包工具 一个是fidder,一个是charles,两个 ... -
序列号批量生成算法
2019-12-05 14:11 0业务处理过程当中,经常需要生成订单号、序列号等,简单的可 ... -
使用ANTLR处理文本
2019-08-28 17:32 764引用 使用 Antlr 处理文本 https://www.ib ... -
解决maven-metadata.xml文件下载卡死问题
2019-04-11 14:02 3976http://192.168.1.110:8081/nexus ... -
rsync备份和删除指定文件
2018-01-02 10:23 2044文件异地备份时,需要将本地文件合并到服务器上,且不能删除服务器 ... -
javaLocale格式化日期和数字
2017-08-25 09:26 865public static void main(Strin ... -
centos6 tomcat 启动脚本 tomcat服务
2017-08-23 11:24 1438系统自动启动tomcat 复制该脚本到/etc/init.d/ ... -
win7 命令行改IP和DNS
2016-12-21 18:35 732使用管理员权限运行CMD //改DNS netsh ... -
jenkins中集成sonar,使用findbug、pmd、checkstyle提升代码质量
2016-09-29 14:58 6167实际上jenkins单独也 ... -
jenkins 集成sonar
2016-09-18 10:14 0jenkins集成sonar可以从插件中心直接更新安装 son ... -
activeMQ5.14权限配置
2016-08-17 13:47 2670activeMQ默认的消息队列没有用户名和密码,可以直接通过T ... -
solaris 使用解压版的jdk
2016-07-27 15:17 762solaris上配置jdk其实也很简单 由于solaris有 ... -
solaris tomcat开机启动
2016-07-27 16:17 618创建文件夹/var/svc/manifes ... -
HibernateTemplate Vs HibernateDaoSupport Vs Direct Hibernate Access
2016-07-26 11:07 732http://forum.spring.io/forum/sp ... -
spring mvc mybatis will not be managed by Spring
2016-07-20 17:30 9876项目运行时发现事务提交不完整,回滚时只能回滚一半。 系统配置 ... -
java里判断一点是否在某个区域
2016-06-03 17:47 1830import java.awt.geom.Path2D ... -
12306的技术升级
2016-04-20 16:17 1027升级的核心是余票查询的升级,余票查询使用存储过程,sybase ... -
工作流的123
2016-04-20 12:58 571三分钟了解Activity工作流 工作流一般会给开发人员提供流 ... -
sping mvc 使用@Value注解为controller注入值
2016-04-17 17:39 10831spring mvc 里有两个配置文件, 第一个,为sprin ... -
googleapis.com域名访问慢的解决办法
2016-04-13 12:09 9681、安装火狐 2、安装插件ReplaceGoogleCDN
相关推荐
除了这些基础的类加载器,Java还引入了**线程上下文类加载器(ContextClassLoader)**。每个线程都有一个与之关联的类加载器,可以通过`Thread.getContextClassLoader()`获取。线程上下文类加载器用于在多线程环境下...
线程上下文类加载器(Thread Context ClassLoader)是一个特殊的角色,它允许在多线程环境中控制类的加载。每个线程都有一个与之关联的类加载器,可以通过Thread.currentThread().getContextClassLoader()获取。这在...
本文主要解析Java类加载的原理,分为三个部分:基础的类加载原理解析、插件环境下的类加载和线程上下文类加载器。 首先,Java虚拟机(JVM)内置了三种预定义的类加载器: 1. 启动(Bootstrap)类加载器:这是最基础的...
在Java中,有多种类加载器,包括系统类加载器、当前类加载器和线程上下文类加载器。系统类加载器,通常由`ClassLoader.getSystemClassLoader()`获取,主要负责加载启动应用时由classpath指定的类。由于它与JVM启动...
线程上下文类加载器则允许在特定线程上下文中改变类加载器,以便加载特定的类。 服务器类加载原理则涉及到服务器如何管理类的加载以及OSGI(Open Service Gateway Initiative)的技术介绍。OSGI提供了一个运行时...
本文将详细解析Java类加载原理,分为三篇文章进行阐述,分别是:Java类加载原理解析、插件环境下类加载原理解析和线程上下文类加载器。 首先,我们来了解Java虚拟机(JVM)的类加载器结构。JVM预定义了三种主要的类...
当类的实例化需要时,JVM会遵循双亲委派模型进行类加载:首先,尝试由当前线程的上下文类加载器加载;如果找不到,再由其父类加载器加载,直到到达Bootstrap Classloader。这种模型保证了核心类库的唯一性,例如`...
线程上下文类加载器主要用于解决应用程序类加载问题,尤其是在服务提供者接口(SPI)中,允许用户自定义扩展的加载。 总的来说,"classloader-playground"是一个实践和研究Java类加载机制的实用工具。通过这个项目...
线程上下文类加载器(Thread Context ClassLoader)是Java提供的一种机制,允许线程在运行时指定一个类加载器,确保类由同一个类加载器加载。这对于应用程序服务器和插件系统尤其有用,因为它允许组件使用自己的类...
创建Java线程有两种方式:继承`Thread`类并重写`run()`方法,或者实现`Runnable`接口并提供`run()`方法。当线程对象被创建并调用`start()`方法后,线程进入可运行态,由Java的线程调度器决定何时执行`run()`方法。 ...
Thread.currentThread().getContextClassLoader()获取的类加载器可能和System Class Loader不同,这是因为当前线程的上下文类加载器可能和应用程序类加载器不同。在某些情况下,我们需要使用Thread.currentThread()....
此外,还可以自定义类加载器,比如线程上下文类加载器,用于满足特定加载需求。 在多线程环境下,类的初始化是线程安全的。当多个线程尝试初始化同一类时,只有一个线程会执行()方法,其他线程会被阻塞。这确保了类...
2. 应用场景:Spring框架中,线程上下文类加载器用于加载Bean定义等配置信息,实现灵活的依赖注入。 四、案例分析 文件`ContextClassLoaderTest`和`ContextClassLoaderTest2`可能是用来测试上下文类加载器的示例...
接着检查传入的加载器实例是否为null,如果为null,则使用当前线程的上下文类加载器来加载服务。其加载过程涉及到读取指定路径下的配置文件,解析服务接口的实现类全限定名,并通过类加载器加载对应的类。 总的来说...