需要动态加载很多类和资源时(经常出现在编写框架代码) .至少有三个 ClassLoader 可以选择 :
1、系统类加载器或叫作应用类加载器 (system classloader or application classloader)
2、当前类加载器
3、当前线程类加载器
system classloader这个类加载器处理 -classpath 下的类加载工作 , 可以通过 ClassLoader.getSystemClassLoader() 方法调用 . ClassLoader 下所有的 getSystemXXX() 的静态方法都是通过这个方法定义的 . 在你的代码中 , 你应该尽量少地调用这个方法 , 以其它的类加载器作为代理 . 否则你的代码将只能工作在简单的命令行应用中 , 这个时候系统类加载器 (system classloader) 是 JVM 最后创建的类加载器 . 一但你把代码移到 EJB, Web 应用或 Java Web Start 应用中 , 一定会出问题 .
当前类加载器就是你当前方法所属的类的加载器 . 在运行时类之间动态联编 , 及调用 Class.forName,() Class.getResource() 等类似方法时 , 这个类加载器会被隐含地使用
线程上下文类型加载器是在Java 2平台上被引入的. 每一个线程都有一个类加载器与之对应(除非这个线程是被本地代码创建的). 这个类加载器是通过Thread.setContextClassLoaser()方法设置的. 如果你不在线程构造后调用这个方法, 这个线程将从它的父线程中继承相应的上下文类加载器. 如果在整个应用中你不做任何特殊设置, 所有的线程将都以系统类加载器(system classloader)作为自己的线程上下文类加载器. 自从Web和J2EE应用服务器使用成熟的类加载器机制来实现诸如JNDI, 线程池, 组件热部署等功能以来, 这种在整个应用中不做任何线程类加载器设置的情况就很少了.
上下文类加载器提供了类加载机制的后门, 这一点也在J2SE中被引入了. 通常, 在JVM中的所有类加载器被组织成了有继承层次的结构, 每一个类加载器(除了引导JVM的原始类加载器)都有一个父加载器. 每当被请示加载类时, 类加载器都会首先请求其父类加载器, 只有当父类加载器不能加载时, 才会自己进行类加载.
有时候这种类加载的顺序安排不能正常工作, (此处的意思是:正常情况下都是从子类加载器到根类加载器请求,万一有根类里需要加载子类时,这种顺序就不能满足要求,就要有一条反向的通道,即得到子类加载器,这样就用到了thread context classloader,因为通过thread.getcontextclassloader()可以得到子类加载器).通常当必须动态加载应用程序开发人员提供的资源的时候.
以JNDI为例: 它的内容(从J2SE1.3开始)就在rt.jar中的引导类中实现了, 但是这些JNDI核心类需要动态加载由独立厂商实现并部署在应用程序的classpath下的JNDI提供者. 这种情况就要求一个父classloader(本例, 就是引导类加载器)去加载对于它其中一个子classloader(本例, 系统类加载器)可见的类. 这时通常的类加载代理机制不能实现这个要求. 解决的办法(workaround)就是, 让JNDI核心类使用当前线程上下文的类加载器, 这样, 就基本的类加载代理机制的相反方向建立了一条有效的途径.
另外, 上面一段可能让你想起一些其它的事情: XML解析Java API(JAXP). 是的, 当JAXP只是J2SE的扩展进, 它很自然地用当前类加载器来引导解析器的实现. 而当JAXP被加入到J2SE1.4的核心类库中时, 它的类加载也就改成了用当前线程类加载器, 与JNDI的情况完全类似(也使很多程序员很迷惑). 明白为什么我说来自Sun的指导很缺乏了吧?
在以上的介绍之后, 我们来看关键问题: 这两种选择(当前类加载器和当前线程类加载器)都不是在所有环境下都适用. 有些人认为当前线程类加载器应该成为新的标准策略. 但是, 如果这样, 当多个线程通过共享数据进行交互的时, 将会呈现出一幅极其复杂的类加载的画面, 除非它们全部使用了同一个上下文的类加载器. 进一步说, 在某些遗留下来的解决方案中, 委派到当前类加载器的方法已经是标准. 比如对Class.forName(String)的直接调用(这也是我为什么推荐尽量避免对这个方法进行调用的原因). 即使你努力去只调用上下文相关的类加载器, 仍然会有一些代码会不由你控制. 这种不受控制的类加载委派机制是混入是很危险的.
更严重的问题, 某些应用服务器把环境上下文及当前类加载器设置到不同的类加载器实例上, 而这些类加载器有相同的类路径但却没有委派机制中的父子关系. 想想这为什么十分可怕. 要知道类加载器定义并加载的类实例会带有一个JVM内部的ID号. 如果当前类加载器加载一个类X的实例, 这个实例调用JNDI查找类Y的实例, 些时的上下文的类加载器也可以定义了加载类Y实例. 这个类Y的定义就与当前类加载器看到的类Y的定义不同. 如果进行强制类型转换, 则产生异常.
这种混乱的情况还将在Java中存在一段时间. 对于那些需要动态加载资源的J2SE的API, 我们来猜想它们的类加策略. 例如:
JNDI 使用线程上下文类加载器
Class.getResource() 和Class.forName()使用当前类加载器
JAXP(J2SE 1.4 及之后)使用线程上下文类加载器 2012/11/11 10:26:55
java.util.ResourceBundle 使用调用者的当前类加载器
URL protocol handlers specified via java.protocol.handler.pkgs system property are looked up in the bootstrap and system classloaders only
Java 序列化API默认使用调用者当前的类加载器
这些类及资源的加载策略问题, 肯定是J2SE领域中文档最及说明最缺乏的部分了.
当执行java命令的时候,JVM会先使用bootstrap classloader载入并初始化一个Launcher,Launcher源代码:
extclassloader = ExtClassLoader.getExtClassLoader();
loader = AppClassLoader.getAppClassLoader(extclassloader);
Thread.currentThread().setContextClassLoader(loader);
当此线程运行的时候,我们可以通过getContextClassLoader方法来获得此context classloader,就可以用它来载入我们所需要的Class。默认的是system classloader。利用这个特性,我们可以“打破”classloader委托机制了,父classloader可以获得当前线程的context classloader,而这个context classloader可以是它的子classloader或者其他的classloader,那么父classloader就可以从其获得所需的Class,这就打破了只能向父classloader请求的限制了。这个机制可以满足当我们的classpath是在运行时才确定,并由定制的classloader加载的时候,由system classloader(即在jvm classpath中)加载的class可以通过context classloader获得定制的classloader并加载入特定的class(通常是抽象类和接口,定制的classloader中是其实现),例如web应用中的servlet就是用这种机制加载的.
出于安全性的考虑,试想如果system classloader“亲自”加载了一个具有破坏性的“java.lang.System”类的后果吧。这种委托机制保证了用户即使具有一个这样的类,也把它加入到了类路径中,但是它永远不会被载入,因为这个类总是由bootstrap classloader来加载的
每个ClassLoader加载Class的过程是:
1.检测此Class是否载入过(即在cache中是否有此Class),如果有到8,如果没有到2
2.如果parent classloader不存在(没有parent,那parent一定是bootstrap classloader了),到4
3.请求parent classloader载入,如果成功到8,不成功到5
4.请求jvm从bootstrap classloader中载入,如果成功到8
5.寻找Class文件(从与此classloader相关的类路径中寻找)。如果找不到则到7.
6.从文件中载入Class,到8.
7.抛出ClassNotFoundException.
8.返回Class.
classloader加载类用的是全盘负责委托机制。
全盘负责:当一个classloader加载一个Class的时候,这个Class所依赖的和引用的所有Class也由这个classloader负责载入,除非是显式的使用另外一个classloader载入;
委托:先让parent(委托父)类加载器(而不是super,它与parent classloader类不是继承关系)寻找,只有在parent找不到的时候才从自己的类路径中去寻找。
System ClassLoader(AppClassLoader)-->ExtClassLoader-->BootstrapClassLoader。三者只是运行时委托父子关系,无任何继承关系
loadClass,findClass,defineClass
类加载还采用了cache机制,也就是如果cache中保存了这个Class就直接返回它,如果没有才从文件中读取和转换成Class,并存入cache,这就是为什么我们修改了Class但是必须重新启动JVM才能生效的原因。
ClassLoader Tree & Delegation Model不要翻译成双亲委托,直接叫委托。
每个ClassLoader都维护了一份自己的名称空间, 同一个名称空间里不能出现两个同名的类
.
大部分java app服务器(jboss, tomcat..)也是采用contextClassLoader来处理web服务。通过线程上下文来加载第三方库jndi实现, 而不依赖于委派模型
Webapp1,Webapp2 ... -->Common --> System --> Bootstrap
一个树状结构,相信大家差不多都知道tomcat默认是以child first装载class,优先载入web中的class类,找不到才会去装载公用类。
tomcat启动入口,通过分析StandardContext.start()
Java代码 收藏代码
public synchronized void start() {
.....
if (getLoader() == null) {
WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
webappLoader.setDelegate(getDelegate());
setLoader(webappLoader);
}
.....
}
如果getLoader为空,则创建一个新的WebappLoader,注意这也是jboss设置tomcat loader的入口,从而替换默认的tomcat classloader。
WebappLoader通过在startInternal启动了一个新的WebappClassLoader
Java代码 收藏代码
// Construct a class loader based on our current repositories list
try {
classLoader = createClassLoader();
classLoader.setResources(container.getResources());
classLoader.setDelegate(this.delegate);
classLoader.setSearchExternalFirst(searchExternalFirst);
.....
for (int i = 0; i < repositories.length; i++) {
classLoader.addRepository(repositories[i]);
}
}<span style="white-space: normal;"> </span>
最后就是WebappClassLoader的loadClass方法了,具体的类装载策略。
下面来看一下,loadClass的相关逻辑:
(0.1)先检查class是否已经被装载过,findLoadedClass
(0.2)通过system classloader装载系统class,所以这里会优先装载jdk的相关代码,这个很重要,也很特别。
如果是delegate=true并且不是filterPackage列表,则采用parent first,否则采用child first。
tomcat类加载:http://yjhexy.iteye.com/blog/668334
public class MyCalssLoader {
public static void main(String[] args) throws IOException {
//引导(原始)类加载器BootstrapClassLoader,不是ClassLoader的子类,JVM自身用C++实现的。
System.out.println(System.getProperty("sun.boot.class.path"));
URLClassPath urlClassPath = Launcher.getBootstrapClassPath();
URL[] urls = urlClassPath.getURLs();
for(URL url : urls){
//我机器上(和平台相关的)jre/lib/下的6个包和一个calsses。
//这就是不需要在系统属性CLASSPATH中指定这些类库的原因
System.out.println(url.toExternalForm()+"--"+url.getProtocol());
}
//类加载器sun.misc.Launcher$ExtClassLoader@addbf1
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println(ClassLoader.getSystemClassLoader().getParent());
//类加载器sun.misc.Launcher$AppClassLoader@19821f (即SystemClassLoader)
System.out.println(System.getProperty("java.class.path"));
System.out.println(ClassLoader.getSystemClassLoader());
//得到本类的类加载器
System.out.println(System.class.getClassLoader());//null,bootstrap classloader(不是ClassLoader实例)
System.out.println(Config.class.getClassLoader());
System.out.println(MyCalssLoader.class.getClassLoader());
}
}
相关推荐
创建自定义类加载器通常需要继承`java.lang.ClassLoader`,重写`findClass()`或`loadClass()`方法,从而控制类的查找和加载过程。这使得开发者能够在运行时根据需要加载特定的类,比如从网络、数据库或其他非传统...
Java 类加载器(ClassLoader)是Java虚拟机的重要组成部分,它负责将Java字节码(.class文件)加载到JVM中并转化为`java.lang.Class`的实例,使得我们能够运行Java程序。Java的类加载机制遵循双亲委托模型,这是一种...
Java 类加载器是Java虚拟机(JVM)的...总之,Java类加载器是Java平台的核心机制之一,它不仅关乎程序的运行,还直接影响到代码的组织、安全和性能。理解和掌握类加载器的工作原理,对于提升Java开发能力具有重要意义。
Java加载器(Loader)是Java虚拟机(JVM)的核心组成部分,主要负责将类的字节码文件加载到JVM中并转换为运行时的数据结构。在深入理解这个概念之前,我们首先要明白Java的类加载机制。Java的类加载过程包括加载、...
可以看到Test类由AppClassLoader加载,AppClassLoader的父加载器是ExtClassLoader,而ExtClassLoader的父加载器是BootStrap Loader,但由于BootStrap Loader是用C++实现且在JVM内部,所以在Java代码中其表示为null。...
- **引导类加载器(Bootstrap Class Loader)**: 是系统级的类加载器,负责加载Java核心库中的类。 - **父类加载器与子类加载器**: 类加载器之间遵循双亲委派模型,即子类加载器会先委托父类加载器尝试加载类,如果...
- **启动类装载器(Bootstrap Class Loader)**:这是系统级的类装载器,用于装载Java的核心类库,如`java.lang.*`等,它是所有其他类装载器的父类装载器。 - **扩展类装载器(Extension Class Loader)**:用于装载扩展...
在Java编程语言中,类加载器(Class-Loader)是一个至关重要的组件,它负责加载类到JVM(Java虚拟机)中。这个“class-loader测试工程”可能是一个专门设计用于研究和理解Java类加载机制的项目。在Java中,类加载...
- **Bootstrap ClassLoader**:这是最顶层的类加载器,负责加载Java核心库(如`rt.jar`),并无法被Java程序直接引用。 - **Extension ClassLoader**:位于Bootstrap ClassLoader之上,负责加载扩展目录(如`lib/ext...
1. **Bootstrap Class Loader(启动类加载器)**:该类加载器使用C++编写,是JVM自身的一部分,用于加载位于`JAVA_HOME/jre/lib/rt.jar`中的类库,以及其他一些核心类库(如`java.lang.*`等)。Bootstrap Class ...
在Java中,反射机制的核心类是`java.lang.Class`,它代表了运行时的类信息。通过Class对象,我们可以获取到类的结构信息,如类名、方法、字段等,并能在运行时创建和访问这些类的对象。 一、Java反射的基本使用 1....
- **Extension Class Loader**:加载扩展库,如位于JAVA_HOME/lib/ext目录下的类或由-Djava.ext.dirs指定的路径。 - **System Class Loader**:也称为应用程序类加载器,加载classpath环境变量指定的类或jar包,通常...
#### 二、核心概念 1. **LoadRunner**:由Micro Focus公司开发的一款性能测试工具,它通过模拟实际用户的操作行为来评估系统的性能。 2. **测试脚本**:是LoadRunner用于模拟用户行为并执行特定任务的一系列命令或...
#### 二、Class.forName 的基础使用 `Class.forName` 的基本语法如下: ```java Class<?> clazz = Class.forName("全限定类名"); ``` 这里的“全限定类名”指的是包含包名的类名,例如 `"java.lang.String"` 或者...
在 Java 语言中,类加载器(ClassLoader)是 Java 运行时环境的核心组成部分之一,它负责将编译后的 `.class` 文件加载到 JVM 中执行。从 JDK 1.0 开始,随着 Java Applet 的出现以及网络应用的需求增加,类加载机制...
本资源整理了JAVA面试中经常涉及的知识点,涵盖JVM、线程、Java内存区域、垃圾回收机制等方面,旨在帮助开发者快速掌握JAVA核心知识。 JVM JVM(Java Virtual Machine)是JAVA虚拟机,负责运行JAVA程序。JVM分为...
通常,这样的程序会有一个按钮,用户点击后触发上述Java代码,通过图形化的方式方便用户上传数据文件和控制文件,然后调用SQL*Loader进行数据导入。 总的来说,SQL*Loader是Oracle数据库管理中的重要工具,而通过...
1. **启动类装载器(Bootstrap Class Loader)**:它是JVM的一部分,负责加载Java核心库,如`rt.jar`。这些库通常位于`$JAVA_HOME/jre/lib`目录下。 2. **扩展类装载器(Extension Class Loader)**:由`sun.misc....