Java类加载/ClassLoader

(1) JVM中ClassLoader的类型

JVM预定义了三种类型类加载器,当一个 JVM 启动的时候,Java 缺省开始使用如下三种类型类装入器:

Bootstrap类加载器:引导类加载器是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib 下面的类库加载到内存中。由于引导类加载器涉及到虚拟机本地实现细节,开发者无法直接获取到启动类加载器的引用,所以不允许直接通过引用进行操作。

Extension类加载器:扩展类加载器是由 Sun 的 ExtClassLoader(sun.misc.Launcher$ExtClassLoader) 实现的。它负责将 < Java_Runtime_Home >/lib/ext 或者由系统变量 java.ext.dir 指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。

System类加载器:系统类加载器是由 Sun 的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径(CLASSPATH)中指定的类库加载到内存中。开发者可以直接使用系统类加载器。可以通过 ClassLoader.getSystemClassLoader() 来获取它。

ExtClassLoader和AppClassLoader都是继承自URLClassLoader。

(2) 类加载的规则

类加载规则有两个:

1 双亲委托

2 类A引用到类B,则由类A的加载器去加载类B,保证引用到的类由同一个加载器加载

JVM在加载类时默认采用的是双亲委托机制。通俗的讲,就是某个特定的类加载器在接到加载类的请求时,首先将加载任务委托给父类加载器,依次递归,如果父类加载器可以完成类加载任务,就成功返回;只有父类加载器无法完成此加载任务时,才自己去加载。

ClassLoader在运行期会以父/子的层次结构存在,每个classLoader 实例都有其父ClassLoader的引用,而父ClassLoader并没有持有子ClassLoader的引用,从而形成一条单向链,当一个类装载请求提交到某个ClassLoader时,默认的类装载过程如下:

1. 检查这个类有没有被装载过,如果已经装载过,返回

2. 调用父ClassLoader去装载类,如果装载成功返回.

3. 调用自身的装载类方法,如果装载成功则返回

4. 如查都没有成功,抛出ClassNotFoundException.

简单说,当ClassLoader链上的某一ClassLoader收到类装载请求时,会按顺序向上询问其所有父节点,直到boot classLoader.任何一个节点成功受理了此请求,则返回,如果所有父节点都不能受理,这个时候才由请求的ClassLoader自身来装载这个类,如果仍不能装载,则抛出异常.

可以使用JVM参数-verbose:class来打印类的加载log.

(3) 类加载的方法

(4) 代码实例

在eclipse工程的当前目录下建立一个文件./cp/test/MyClass.java

package test;

public class MyClass {
}

进入cp目录下,编译此文件,生成MyClass.class

javac test/MyClass.java

运行下面的代码

package learning.classloader;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        try {
            Class.forName("test.MyClass");
        } catch (ClassNotFoundException e) {
            System.out.println("Failed to load MyClass");
        }

        File myClassDir = new File("./cp");
        URLClassLoader sysCl = (URLClassLoader) ClassLoader.getSystemClassLoader();
        Method addUrlMethod = URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{URL.class});
        addUrlMethod.setAccessible(true);
        addUrlMethod.invoke(sysCl, myClassDir.toURI().toURL());

        Class c = Class.forName("test.MyClass");
        System.out.println("Loaded MyClass successfully");

        URLClassLoader clNew = new URLClassLoader(sysCl.getURLs(), sysCl.getParent());
        Class c1 = clNew.loadClass("test.MyClass");

        System.out.println(c.equals(c1));
        System.out.println(c.getClassLoader());
        System.out.println(c1.getClassLoader());

        System.out.println();
        InputStream is1 = c.getResourceAsStream("MyClass.java");
        BufferedReader reader = new BufferedReader(new InputStreamReader(is1));
        String s;
        while ((s = reader.readLine()) != null) {
            System.out.println(s);
        }
        reader.close();

        System.out.println();
        InputStream is2 = c.getResourceAsStream("/test/MyClass.java");
        reader = new BufferedReader(new InputStreamReader(is2));
        while ((s = reader.readLine()) != null) {
            System.out.println(s);
        }
        reader.close();

        System.out.println();
        InputStream is3 = sysCl.getResourceAsStream("test/MyClass.java");
        reader = new BufferedReader(new InputStreamReader(is3));
        while ((s = reader.readLine()) != null) {
            System.out.println(s);
        }
        reader.close();
    }
}

分析:

运行上面的例子,不要把cp目录加到classpath中,可以看到第一次调用Class.forName("test.MyClass")会失败。

之后把cp目录加到系统类加载器(sun.misc.Launcher$AppClassLoader)中,或者重新new一个类加载器,设置其classpath中包含cp目录,都能够正常加载MyClass类。

1)不同的类加载器加载指定类型得到的class对象不同

在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中一个类用其全名和一个加载类ClassLoader的实例作为唯一标识,不同类加载器加载的类将被置于不同的命名空间.

上面的例子中,c是用系统类加载器(sun.misc.Launcher$AppClassLoader)加载,c1是用new出来的URLClassLoader加载,可以看到c.equals(c1))的结果是false。

2)创建类加载器必须正确设置父加载器

URLClassLoader clNew = new URLClassLoader(sysCl.getURLs(), sysCl.getParent());

上面创建新的类加载器的时候指定父类加载器为sysCl.getParent(), 即系统类加载器的parent,亦即extension类加载器(sun.misc.Launcher$ExtClassLoader)。

如果改成

URLClassLoader clNew = new URLClassLoader(sysCl.getURLs());

不指定其parent, 那么默认parent为系统类加载器。这样c.equals(c1))的结果将是true。这是因为类加载规则1-双亲委托造成的。

3)getResource/getResourceAsStream的用法

ClassLoader和Class上都可以调用getResource/getResourceAsStream。区别在于

* Class.getResourceAsStream(path) 指定的path如果以"/"开头,表示查找路径是classpath,如果不以"/"开头,查找路径是Class所在的位置。

c.getResourceAsStream("MyClass.java");                   --〉在test.MyClass所在位置查找 MyClass.java

c.getResourceAsStream("/test/MyClass.java");          --〉在classpath指定的路径中寻找 ./test/MyClass.java

* ClassLoader.getResourceAsStream(path)  查找路径是classpath.

(5) ContextClassLoader

ContextClassLoader是Thread的一个属性,可以这样获得Thread.currentThread().getContextClassLoader(),也可以调用setContextClassLoader()设置。

ContextClassLoader只是一个逻辑上的概念,并没有一个叫ContextClassLoader的类。

如果创建一个新的线程,会从创建线程继承ContextClassLoader。

在线程里面new一个对象时,使用的是系统类加载器,并不会使用线程的ContextClassLoader。

那么为什么还需要ContextClassLoader呢?这其实是因为加载Class的默认规则在某些情况下不能满足要求。

比如jdk中的jdbc API 和具体数据库厂商的实现类SPI的类加载问题。在jdbc API的类是由BootStrap加载的,那么如果在jdbc API需要用到spi的实现类时,根据默认规则2,则实现类也会由BootStrap加载,但是spi实现类却没法由BootStrap加载,只能由Ext或者App加载。

当DriverManager需要加载SPI中的实现类时,可以获取ContextClassLoader,然后用它来加载spi中的类。

当然也可以不使用ContextClassLoader,自己保存一个classLoader,需要用到的地方使用此classLoader也可以。

参考文章

1)http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html

2)http://shangjava.iteye.com/blog/1097096

3)   http://blog.csdn.net/v1v1wang/article/details/6864573

4)  http://www.tuicool.com/articles/A7neua

时间: 2024-10-31 06:41:52

Java类加载/ClassLoader的相关文章

java类加载器——ClassLoader

Java的设计初衷是主要面向嵌入式领域,对于自定义的一些类,考虑使用依需求加载原则,即在程序使用到时才加载类,节省内存消耗,这时即可通过类加载器来动态加载. 如果你平时只是做web开发,那应该很少会跟类加载器打交道,但如果你想深入学习tomcat服务器的架构,它是必不可少的.所谓类加载器,就是用于加载Java类到Java虚拟机中,它负责读取Java字节码,并转换成java.lang.Class类的一个实例,使字节代码.class文件得以运行.一般类加载器负责根据一个指定的类找到对应的字节代码,然

Java类加载器ClassLoader总结

JAVA类装载方式,有两种: 1.隐式装载, 程序在运行过程中当碰到通过new 等方式生成对象时,隐式调用类装载器加载对应的类到jvm中. 2.显式装载, 通过class.forname()等方法,显式加载需要的类 类加载的动态性体现: 一个应用程序总是由n多个类组成,Java程序启动时,并不是一次把所有的类全部加载后再运行,它总是先把保证程序运行的基础类一次性加载到jvm中,其它类等到jvm用到的时候再加载,这样的好处是节省了内存的开销,因为java最早就是为嵌入式系统而设计的,内存宝贵,这是

java笔记--理解java类加载器以及ClassLoader类

类加载器概述: java类的加载是由虚拟机来完成的,虚拟机把描述类的Class文件加载到内存,并对数据进行校验,解析和初始化,最终形成能被java虚拟机直接使用的java类型,这就是虚拟机的类加载机制.JVM中用来完成上述功能的具体实现就是类加载器.类加载器读取.class字节码文件将其转换成java.lang.Class类的一个实例.每个实例用来表示一个java类.通过该实例的newInstance()方法可以创建出一个该类的对象. 类的生命周期: 类从加载到虚拟机内存到被从内存中释放,经历的

浅析java类加载器ClassLoader

作为一枚java猿,了解类加载器是有必要的,无论是针对面试还是自我学习. 本文从JDK提供的ClassLoader.委托模型以及如何编写自定义的ClassLoader三方面对ClassLoader做一个简要的总结. JDK中提供的ClassLoader 1. Bootstrap ClassLoader Bootstrap加载器是用C++语言写的,它是在Java虚拟机启动后初始化的,它主要负责加载%JAVA_HOME%/jre/lib以及%JAVA_HOME%/jre/classes中的类,是最顶

[Java类加载器]Java中classLoader浅析.

本文为在公司内部TD上写的一篇小文, 主要讲解java中classLoader基础知识, 现在拿来这里分享一下. 一.问题 请在Eclipse中新建如下类,并运行它: 1 package java.lang; 2 3 public class Long { 4 public static void main(String[] args) { 5 System.out.println("Hi, i am here"); 6 } 7 } 你能猜到它的运行如果吗? 不错,就是如下这个样子!

潜水 java类加载器ClassLoader

类加载器(class loader)用于装载 Java 类到 Java 虚拟机中.一般来说.Java 虚拟机使用 Java 类的方式例如以下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件).类载入器负责读取 Java 字节代码.并转换成java.lang.Class类的一个实例.每一个这种实例用来表示一个 Java 类. 通过此实例的 newInstance()方法就能够创建出该类的一个对象,也就是万能的Class对象.

深入源码看java类加载器ClassLoader

ClassLoader类加载器是负责加载类的对象.ClassLoader 类是一个抽象类.如果给定类的二进制名称(即为包名加类名的全称),那么类加载器会试图查找或生成构成类定义的数据.一般策略是将名称转换为某个文件名,然后从文件系统读取该名称的"类文件".java.lang.ClassLoader类的基本职责就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个 Java 类,即 java.lang.Class类的一个实例.除此之外,ClassLoad

深入理解 java类加载器ClassLoader

类加载器(class loader)用来加载 Java 类到 Java 虚拟机中.一般来说,Java 虚拟机使用 Java 类的方式如下:Java 源程序(.java 文件)在经过 Java 编译器编译之后就被转换成 Java 字节代码(.class 文件).类加载器负责读取 Java 字节代码,并转换成java.lang.Class类的一个实例.每个这样的实例用来表示一个 Java 类.通过此实例的 newInstance()方法就可以创建出该类的一个对象,也就是万能的Class对象. Jav

Java类加载器 ClassLoader的解析

//参考 : http://www.ibm.com/developerworks/cn/java/j-lo-classloader/ 类加载器基本概念 类加载器是 Java 语言的一个创新,也是 Java 语言流行的重要原因之一.它使得 Java 类可以被动态加载到 Java 虚拟机中并执行.类加载器从 JDK 1.0 就出现了,最初是为了满足 Java Applet 的需要而开发出来的.Java Applet 需要从远程下载 Java 类文件到浏览器中并执行.现在类加载器在 Web 容器和 O