类加载器入门级应用

1、类加载器负责加载 Java 类的字节代码到 Java 虚拟机中。最初是为了满足 Java Applet的需要而开发出来的,
  Java Applet需要从远程下载Java类文件到浏览器中并执行。类加载器使得Java类可以被动态加载到Java虚拟机中并执行。


2、基本上所有的类加载器都是java.lang.ClassLoader类的一个实例,java.lang.ClassLoader类的基本职责
就是根据一个指定的类的名称,找到或者生成其对应的字节代码,然后从这些字节代码中定义出一个Java类,
即java.lang.Class类的一个实例。除此之外,ClassLoader还负责加载Java应用所需的资源,
如图像文件和配置文件等。
ClassLoader中与加载类相关的方法如下:
(1)getParent() 返回该类加载器的父类加载器。 
(2)loadClass(String name) 加载名称为 name的类。
(3)findClass(String name) 查找名称为 name的类。
(4)findLoadedClass(String name) 查找名称为 name的已经被加载过的类。
(5)defineClass(String name, byte[] b, int off, int len) 把字节数组 b中的内容转换成 Java类。
(6)resolveClass(Class<?> c) 链接指定的 Java 类。

 

3、java虚拟机中可以安装多个类加载,系统默认三个主要类加载器,每个类负责加载特定位置的类:
(1)引导类加载器(BootStrp):
   它用来加载 Java的核心库,是用C++来实现的,并不继承自 java.lang.ClassLoader,
   主要加载目录JRE/lib/rt.jar
(2)扩展类加载器(ExtClassLoader):
    它用来加载 Java 的扩展库。主要加载目录JRE/lib/ext/*.jar。
(3)系统类加载器(AppClassLoader):
   它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类,主要加载目录CLASSPATH指定的所有jar或目录。
  Java 应用的类都是由它来完成加载的,可以通过 ClassLoader.getSystemClassLoader()来获取它。


4. 除了引导类加载器(BootStrp)之外,所有的类加载器都有一个父类加载器。可通过getParent()方法得到父类加载器。
   JDK 的实现对于父类加载器是引导类加载器(BootStrp)的情况,getParent()方法返回 null。例:

Java代码 复制代码   收藏代码
  1. public class ClassLoaderTree {   
  2.     public static void main(String[] args) {    
  3.         ClassLoader loader = ClassLoaderTree.class.getClassLoader();    
  4.         while (loader != null) {    
  5.             System.out.println(loader.toString());    
  6.             loader = loader.getParent();    
  7.         }    
  8.     }    
  9. }  
public class ClassLoaderTree {
	public static void main(String[] args) { 
        ClassLoader loader = ClassLoaderTree.class.getClassLoader(); 
        while (loader != null) { 
            System.out.println(loader.toString()); 
            loader = loader.getParent(); 
        } 
    } 
}

 输出结果如下:

Java代码 复制代码   收藏代码
  1. sun.misc.Launcher$AppClassLoader@187c6c7  
  2. sun.misc.Launcher$ExtClassLoader@10b62c9  
sun.misc.Launcher$AppClassLoader@187c6c7
sun.misc.Launcher$ExtClassLoader@10b62c9

 

5、类加载器的委托机制
   当Java虚拟机要加载一个类时,首先当前线程的类加载器去加载线程中的第一个类,如果类A中引用了类B,
Java虚拟机将使用加载类A的加载器来加载类B.也可以直接使用loadClass()方法直接指定某个类加载器去加载某个类。
每个类加载器加载类时,会先委托给其上级类加载器,当所有上级类加载器没有加载到类时,回到发起者类加载器,
如果发起者类加载器还加载不了,则抛出ClassNotFoundException。

 

6、Java 虚拟机是如何判定两个 Java 类是相同的,Java 虚拟机不仅要看类的全名是否相同,
还要看加载此类的类加载器是否一样。只有两者都相同的情况,才认为两个类是相同的。即便是同样的字节代码,
被不同的类加载器加载之后所得到的类,也是不同的。
为了保证Java核心库的类型安全。所有 Java 应用都至少需要引用 java.lang.Object类,
也就是说在运行的时候,java.lang.Object这个类需要被加载到 Java 虚拟机中。如果这个加载过程由Java应用
自己的类加载器来完成的话,很可能就存在多个版本的 java.lang.Object类,而且这些类之间是不兼容的。
通过代理模式,对于Java核心库的类的加载工作由引导类加载器来统一完成,保证了 Java 应用所使用的
都是同一个版本的 Java 核心库的类,是互相兼容的。

 

7、Class.forName
Class.forName是一个静态方法,同样可以用来加载类。该方法有两种形式:

Java代码 复制代码   收藏代码
  1. Class.forName(String name, boolean initialize, ClassLoader loader)  
Class.forName(String name, boolean initialize, ClassLoader loader)

 第一种形式的参数 name表示的是类的全名;initialize表示是否初始化类;loader表示加载时使用的类加载器。

Java代码 复制代码   收藏代码
  1. Class.forName(String className)  
Class.forName(String className)

 第二种形式则相当于设置了参数 initialize的值为 true,loader的值为当前类的类加载器。

Class.forName的一个很常见的用法是在加载数据库驱动的时候。如

Java代码 复制代码   收藏代码
  1. Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()  
Class.forName("org.apache.derby.jdbc.EmbeddedDriver").newInstance()

 用来加载 Apache Derby 数据库的驱动。

 

8、编写自己的类加载器
一般来说,自己开发的类加载器只需要覆写 findClass(String name)方法即可。
java.lang.ClassLoader类的方法 loadClass()封装了前面提到的代理模式的实现,
该方法会首先调用 findLoadedClass()方法来检查该类是否已经被加载过;如果没有加载过的话,
会调用父类加载器的 loadClass()方法来尝试加载该类;如果父类加载器无法加载该类的话,
就调用 findClass()方法来查找该类。因此,为了保证类加载器都正确实现代理模式,在开发自己的类加载器时,
最好不要覆写 loadClass()方法,而是覆写 findClass()方法。例:

Java代码 复制代码   收藏代码
  1. public class FileSystemClassLoader extends ClassLoader {    
  2.     private String rootDir;    
  3.     public FileSystemClassLoader(String rootDir) {    
  4.         this.rootDir = rootDir;    
  5.     }    
  6.     protected Class<?> findClass(String name) throws ClassNotFoundException {    
  7.         byte[] classData = getClassData(name);    
  8.         if (classData == null) {    
  9.             throw new ClassNotFoundException();    
  10.         }    
  11.         else {    
  12.             return defineClass(name, classData, 0, classData.length);    
  13.         }    
  14.     }    
  15.     private byte[] getClassData(String className) {    
  16.         String path = classNameToPath(className);    
  17.         try {    
  18.             InputStream ins = new FileInputStream(path);    
  19.             ByteArrayOutputStream baos = new ByteArrayOutputStream();    
  20.             int bufferSize = 4096;    
  21.             byte[] buffer = new byte[bufferSize];    
  22.             int bytesNumRead = 0;    
  23.             while ((bytesNumRead = ins.read(buffer)) != -1) {    
  24.                 baos.write(buffer, 0, bytesNumRead);    
  25.             }    
  26.             return baos.toByteArray();    
  27.         } catch (IOException e) {    
  28.             e.printStackTrace();    
  29.         }    
  30.         return null;    
  31.     }    
  32.     private String classNameToPath(String className) {    
  33.         return rootDir + File.separatorChar    
  34.                 + className.replace('.', File.separatorChar) + ".class";    
  35.     }    
  36. }  
public class FileSystemClassLoader extends ClassLoader { 
    private String rootDir; 
    public FileSystemClassLoader(String rootDir) { 
        this.rootDir = rootDir; 
    } 
    protected Class<?> findClass(String name) throws ClassNotFoundException { 
        byte[] classData = getClassData(name); 
        if (classData == null) { 
            throw new ClassNotFoundException(); 
        } 
        else { 
            return defineClass(name, classData, 0, classData.length); 
        } 
    } 
    private byte[] getClassData(String className) { 
        String path = classNameToPath(className); 
        try { 
            InputStream ins = new FileInputStream(path); 
            ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
            int bufferSize = 4096; 
            byte[] buffer = new byte[bufferSize]; 
            int bytesNumRead = 0; 
            while ((bytesNumRead = ins.read(buffer)) != -1) { 
                baos.write(buffer, 0, bytesNumRead); 
            } 
            return baos.toByteArray(); 
        } catch (IOException e) { 
            e.printStackTrace(); 
        } 
        return null; 
    } 
    private String classNameToPath(String className) { 
        return rootDir + File.separatorChar 
                + className.replace('.', File.separatorChar) + ".class"; 
    } 
}

 

8.Tomcat6的类加载器(从上至下)
(1)BootStrapClassLoader,Java的核心库,实际没有这个类
(2)ExtensionClassLoader,对于Sun JVM,是sun.misc.Launcher$ExtClassLoader,加载 Java 的扩展库。                     
(3) SystemClassLoader,对于Sun JVM,是sun.misc.Launcher$AppClassLoader,加载java的应用库。                     
(4) CommonClassLoader,对于Tomcat 6,是org.apache.catalina.loader.StandardClassLoader,
加载的类目录通过{tomcat}/conf/catalina.properties中的common.loader指定,
以SystemClassLoader为parent(目前默认定义是common.loader=${catalina.base}/lib,
${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar)
(5) CatalinaClassLoader,加载的类目录通过{tomcat}/conf/catalina.properties中server.loader指定,
 以CommonClassLoader为parent,如果server.loader配置为空,
 则ServerClassLoader 与CommonClassLoader是同一个(默认server.loader配置为空)
(6) SharedClassLoader:加载的类目录通过{tomcat}/conf/catalina.properties中share.loader指定,
 以CommonClassLoader为parent,如果server.loader配置为空,
 则CatalinaClassLoader 与CommonClassLoader是同一个(默认share.loader配置为空)
(7) WebappClassLoader:每个Context一个WebappClassLoader实例,
 负责加载context的/WEB-INF/lib和/WEB-INF/classes目录,
 context间的隔离就是通过不同的WebappClassLoader来做到的。
 由于类定义一旦加载就不可改变,因此要实现tomcat的context的reload功能,
 实际上是通过新建一个新的WebappClassLoader来做的,
 因此reload的做法实际上代价是很高昂的,需要注意的是,JVM内存的Perm区是只吃不拉的,
 抛弃掉的WebappClassLoader加载的类并不会被JVM释放,
 因此tomcat的reload功能如果应用定义的类比较多的话,reload几次就OutOfPermSpace异常了。
(8)JasperLoader:每个JSP一个JasperLoader实例,与WebappClassLoader做法类似,
JSP支持修改生效是通过丢弃旧的JasperLoader,建一个新的JasperLoader来做到的,
同样的,存在轻微的PermSpace的内存泄露的情况。

猜你喜欢

转载自leowzy.iteye.com/blog/1555468