ClassLoader 加载机制


0.0.0.0.JVMJVMJVMJVM JVM 是JRE 里面一个动态链接库,JDK 里面的jre 一般用于运行java 本身的程序,比 如javac 等。JRE 下 面的 bin\client 或者bin\server 的jvm.dll 就是JVM。 Java -verbose:class XXXX 显 示调 用的 详细 信息 1.1.1.1.JavaJavaJavaJava ClassLoaderClassLoaderClassLoaderClassLoader 1.1 1.1 1.1 1.1 什么是什么是什么是什么是ClassLoaderClassLoaderClassLoaderClassLoader Java 程 序不 是本 地的 可执 行程 序 ,在 运行 java 程 序时 ,首 先运 行 JVM,然 后把 java class 加 载到 JVM 里 面运 行, 负责 加载 java class 的 这部 分就 叫 Class Loader。 JVM 本身包含了一个ClassLoader 称为引导类加载器:Bootstrap ClassLoader,和JVM 一样,Bootstrap ClassLoader 是 由本 地代 码实 现的 ,另外JVM 还 提供 了另 外两 个ClassLoader: 扩 展类 加载 器 --Extension ClassLoader 和 应用 程序 类加 载器 ---ApplicationClassLoader。 Bootstrap ClassLoader:主 要负 责 jdk_home/lib 目 录下 的核 心 api 或-Xbootclasspath 选 项 指定 的 jar 包 装入 工作 Extension ClassLoader:主要负责jdk_home/lib/ext 目录下的jar 包或-Djava.ext.dirs 指 定 目录 下的 jar 包 装入 工作 Application ClassLoader:主要负责java -classpath/-Djava.class.path 所指的目录下的类与 jar 包 装入 工作 . User Custom ClassLoader/ 用户自定义类加载器(java.lang.ClassLoader 的子类): 在 程序 运行 期间 , 通过java.lang.ClassLoader 的 子类 动态 加载 class 文件, 体现java 动 态实 时 类 装入 特性 . ClassLoader 有 两种 载入 方式 : � Pre-loading 预 先载 入, 载入 的基 础类 � Load-on-demand 按 需载 入 ,只 有实 例化 一个 类才 会被 classloader 载入,仅 仅声 明 不 会被 载入 。 Static 块 在什 么时 候执 行? � 当调用forName(String)载入class 时 执行 ,如 果调 用ClassLoader.loadClass 不 会执 行 , forName(String,false,ClassLoader)也 不会 被执 行。 � 如果在载入class 时没有执行static 块,则在第一次实例化时执行,比如new, Class.newInstance()操作 � Static 块 仅执 行一 次 图01 JVM 启 动流 程 1.2 1.2 1.2 1.2 什么时候加载类文件?什么时候加载类文件?什么时候加载类文件?什么时候加载类文件? 当 使用 java 去 执行 一个 类的 时候 ,JVM 使用ApplicationClassLoader 加 载这 个类 ,如果 A类 引用 了 B类,不 管是 直接 引用 还是 用 Class.forName()引用,JVM 会 找到 加载 A类的 classLoader, 并使 用这 个 ClassLoader 加载B类。 1.3 1.3 1.3 1.3 类加载器实例类加载器实例类加载器实例类加载器实例 Java 类 加载 器在 装载 类的 时候 是需 要加 载的 ,只 有当 一个 类要 使用 的时 候 ,类 加载 器才 会 加载 这个 类并 实例 化。 下面 以例 子为 例, 说明 类加 载的 过程 : 类Main: public class Main { public static void main(String args[]){ A a = new A(); a.print(); B b = new B(); b.print(); new A(); } } 类A: public class A{ static{ System.out.println("I am A......"); } public void print(){ System.out.println("Using A......."); } public A(){ System.out.println("gou zao A......."); } } 类B: public class B{ static{ System.out.println("I am B......"); } public void print(){ System.out.println("Using B......."); } } 在编译过三个类文件之后,使用Java -verbose:class Main 运行此程序,可以看到首先由 Bootstrap ClassLoader 加载java.*类 ,然 后由 Application ClassLoader 加 载应 用程 序类 , 1.41.41.41.4 java java java java 程序加载的方法程序加载的方法程序加载的方法程序加载的方法 1.4.1 1.4.1 1.4.1 1.4.1 隐式加载 发 生在 由于 引用 、实 例化 或继 承导 致需 要装 载类 的时 候。 隐式 类装 载是 在幕 后启 动的 , JVM 会 解析 必要 的引 用并 装载 类。 1.4.2 1.4.2 1.4.2 1.4.2 显示加载 1.由java.lang.Class 的forName( )方 法加 载 Class.forName()方 法具 有两 个重 载的 方法 : +-public static Class forName(String className) | +-public static Class forName(String className, boolean initialize,ClassLoader loader) 参 数说 明: className - 所 需类 的完 全限 定名 initialize - 是 否必 须初 始化 类 (静 态代 码块 的初 始化 ) loader - 用 于加 载类 的类 加载 器 调 用只 有一 个参 数的 forName()方 法等 效于 Class.forName(className, true, loader)。 这 两个 方法 ,最 后都 要连 接到 原生 方法 forName0(), 其定 义如 下: private static native Class forName0(String name, boolean initialize,ClassLoader loader) throws ClassNotFoundException; 只 有一 个参 数的 forName()方 法, 最后 调用 的是 : forName0(className, true, ClassLoader.getCallerClassLoader()); 而 三个 参数 的 forName(), 最后 调用 的是 : forName0(name, initialize, loader); 所以,不 管使 用的 是 new 來 实例 化某 个类 、或 是使 用只 有一 个参 数的 Class.forName()方法, 内 部都 隐含 了 “载 入类 +运 行静 态代 码块 ”的 步骤 。而 使用 具有 三个 参数 的 Class.forName() 方法时,如果第二个参数为false,那么类加载器只会加载类,而不会初始化静态代码块, 只 有当 实例 化这 个类 的时 候 ,静 态代 码块 才会 被初 始化 ,静 态代 码块 是在 类第 一次 实例 化的 时 候才 初始 化的 。 2.由java.lang.ClassLoader 的loadClass( )方 法加 载 直 接使 用类 加载 器 +— 获 得对 象所 属的 类 : getClass()方法 | +— 获 得该 类的 类加 载器 : getClassLoader()方法 1.4.3 1.4.3 1.4.3 1.4.3 委派模型 当classloader 有 类需 要载 入时 ,先 让其 parent 搜 寻路 径帮 忙载 入, 如果 parent 找 不到 , 再由自己搜寻路径进行载入。ClassLoader 在运行期会以父/子的层次结构存在,每个 ClassLoader 实 例都 有其 父 ClassLoader 的 引用 ,而 父 ClassLoader 并 没有 持有 子 ClassLoader 的 引用 ,从 而形 成一 条单 向链 ,当 一个 类装 载请 求被 提交 到某 个 ClassLoader 时 ,默 认的 类 装 载过 程如 下: � 检 查这 个类 有没 有被 装载 过, 如果 已经 装载 过, 则直 接返 回 � 调 用父 ClassLoader 去 装载 类, 如果 装载 成功 则返 回 � 调 用自 身的 装载 类方 法, 如果 装载 成功 则返 回 � 上 述所 有步 骤都 没有 成功 装载 到类 ,抛 出 ClassNotFoundException 每 一层 次的 ClassLoader 都 重复 上述 动作 。 简单说,当Classloader 链上的某一Classloader 收到类装载请求时,会按顺序向上询问 其所有父节点,直至最顶端(BootstrapClassLoader),任何一个节点成功受理了此请求,则 返回,如果所有父节点都不能受理,这时候才由被请求的Classloader 自身来装载这个类, 如 果仍 然不 能装 载, 则抛 出异 常。 1.4.4 1.4.4 1.4.4 1.4.4 异常 NoClassDefFoundError 和ClassNotFoundException NoClassDefFoundError:当java 源文件已编译成.class 文件,但是ClassLoader 在运行期间 在 其搜 寻路 径 load 某 个类 时 ,没 有找 到 .class 文 件则 报这 个错 。 ClassNotFoundException:试图通过一个String 变量来创建一个Class 类时不成功则抛出 这 个异 常 。 1.51.51.51.5 CallerCallerCallerCaller Classloader Classloader Classloader Classloader 和线程上下文和线程上下文和线程上下文和线程上下文ClassloaderClassloaderClassloaderClassloader 在 动态 加载 资源 时, 往往 有三 种 ClassLoader 可 选择 : � System ClassLoader (Application ClassLoader) � Caller ClassLoader � 当 前线 程的 上下 文 ClassLoader 1.5.11.5.11.5.11.5.1 CallerCallerCallerCaller ClassloaderClassloaderClassloaderClassloader Caller ClassLoader 是指当前所在的类装载时所使用的ClassLoader,它可能是System ClassLoader,也可能是一个自定义的ClassLoader。可以通过getClass().getClassLoader()来得 到Caller Classloader。例如,存在A类,是被AClassLoader 所加载,A.class.getClassLoader() 为AClassLoader 的 实例 ,它 就是 A.class 的Caller Classloader。 如果在A类中使用new 关键字,或者Class.forName(String className) 和 Class.getResource(String resourceName) 方法,那么这时也是使用Caller Classloader 来 装载 类和 资源 。比 如在 A类 中初 始化 B类: /** *A.java */ public void foo() { Bb=new B(); b.setName("b"); } 那么,B类 由当 前 Classloader,也 就是 AClassloader 装载。同 样的 ,修 改上 述的 foo 方 法 ,其 实现 改为 : Class clazz = Class.forName("foo.B"); 最 终获 取到 的 clazz, 也是 由 AClassLoader 所 装载 。 那么,如何使用指定的Classloader 去完成类和资源的装载呢?或者说,当需要去实 例 化一 个 Caller Classloader 和 它的 父 Classloader 都 不能 装载 的类 时, 怎么 办呢 ? 一 个很 典型 的例 子是 JAXP,当 使用 xerces 的SAX 实 现时 ,我 们首 先需 要通 过 rt.jar 中的javax.xml.parsers.SAXParserFactory.getInstance()得到xercesImpl.jar 中的 org.apache.xerces.jaxp.SAXParserFactoryImpl 的实例。由于JAXP 的框架接口的 class 位于JAVA_HOME/lib/rt.jar中,由Bootstrap Classloader装载,处于Classloader 层次结构中的最顶层,而xercesImpl.jar 由低层的Classloader 装载,也就是说 SAXParserFactoryImpl 是在SAXParserFactory 中实例化的,如前所述,使用 SAXParserFactory 的Caller Classloader(这里是Bootstrap Classloader)是完成不了 这 个任 务的 。 这 时, 我们 就需 要了 解一 下线 程上 下文 Classloader 了。 1.5.21.5.21.5.21.5.2 线程上下文ClassLoaderClassLoaderClassLoaderClassLoader 每 个线 程都 有一 个关 联的 上下 文 Classloader。如 果使 用 new Thread()方 式生 成新 的 线程,新 线程 将继 承其 父线 程的 上下 文 Classloader。如 果程 序对 线程 上下 文 Classloader 没有任何改动的话,程序中所有的线程将都使用System Classloader 作为上下文 Classloader。 当使用Thread.currentThread().setContextClassLoader(classloader)时,线程 上 下文 Classloader 就 变成 了指 定的 Classloader 了 。此 时, 在本 线程 的任 意一 处地 方, 调用Thread.currentThread().getContextClassLoader(),都可以得到前面设置的 Classloader。 1.61.61.61.6 JVM JVM JVM JVM 工作流程工作流程工作流程工作流程 ClassLoader hierachy: � jvm 建立->初 始化 动作 ->产 生第 一个 ClassLoader,即bootstrap Loader � bootstrap loader 在sum.misc.Launcher 类 里面 的ExtClassLoader,并 设定 其 Parent 为null � bootstrap loader 载入sun.misc.Launcher$AppClassLoader,并 设定 其 parent 为ExtClassLoader(但是AppClassLoader 也 是由 bootstrap loader 所载 入的) � AppClassLoader 载 入各 个xx.class,xx.class 也 有可 能 被ExtclassLoader 或者 bootstrap loader 载入 � 自 定义 的 ClassLoader 的getParent()是AppClassLoader.parent 和 他的 加载 器 并没 有关 系 � ExtClassLoader 和AppClassLoader 都是URLClassLoader 的 子类 . � AppClassLoader 的URL 是 由系 统参 数 java.class.path 取 出的 字符 串决 定 ,而 java.class.path 由运行java.exe 时的-cp 或-classpath 或CLASSPATH 环 境变 量 决定 � ExtClassLoader 查 找的 url 是 系统 变量 java.ext.dirs,java.ext.dirs 默认为 jdk\jre\lib\ext � Bootstrap loader 的 查找 url 是sun.boot.class.path � 在 程序 运行 后调 用 System.setProperty()来 改变 系统 变量 并不 能改 变以 上加 载 的 路径 ,因为classloader 读 取在 System.setProperty 之前.sun.boot.class.path 是 在程 序中 写死 的 ,完 全不 能修 改
还剩7页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 8 金币 [ 分享pdf获得金币 ] 1 人已下载

下载pdf

pdf贡献者

robin1979

贡献于2013-09-02

下载需要 8 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf