类加载器 ClassLoder详解

一、类加载器定义

 从作用的角度来认识类加载器:(根据API文档)

1.类加载器是一个负责加载类的对象,而ClassLoader是一个抽象类。类加载器将xxx.class文件加载到JVM,生成xxx的Class对象。(Class详见反射 点击这里)

2.类加载器被安全管理器用来表示安全范围。

二、类加载器种类及工作原理

 主要有三 :

1.Bootstrap Loader(引导加载器):负责加载%JAVA_HOME%/jre/lib下的核心jar

2.Extended Loader(扩展加载器):负责加载%JAVA_HOME%/jre/lib/ext下的扩展jar

3.AppClass Loader(应用加载器):负责加载该应用程序CLASSPATH下的jar和.class文件

 工作原理:

  每一个类加载器都有一个与之关联的上级类加载器,类加载器加载器类时使用委托机制。当需要加载一个类时,它首先将该任务委托给上级加载器,如果上级加载器没能加载到,然后再自行加载,这是一个递归的过程。应用加载器的上级是扩展加载器,扩展加载器的上级是引导加载器,而引导加载无上级加载器。

三、类加载器的安全管理

  JVM为每个类加载器维护了一个名字空间,每个名字空间只能加载一个同名的类,不同名字空间可加载同名的类。在JVM中,同一个名字空间下的类可以直接交互,不同则不可以。如果a加载器和b加载器都加载了A类,那么a加载器下的类要访问A类,那么一定是访问的a加载器下的A类。这样,通过类加载器就可以进行安全管理。

  举个例子:我们自己写了一个类叫java.lang.String和类库里的String的全限定名一模一样。现在在代码中出现了String类,需要加载,过程如下。

    >首先这个任务是应用加载器的,根据委托机制,应用加载器将任务委托给上级扩展加载器。

      >扩展加载器接收到该任务,将任务委托给上级引导加载器。

        >引导加载器加载在核心类库中寻找java.lang.String,找到了,加载,生成Class对象。

        >引导加载器将结果返回给扩展加载器。

      >扩展加载器将结果返回给应用加载器。

    >应用加载器得到结果。

  应用加载器的到的结果是引导加载器从核心类库中加载的java.lang.String 并没有加载到我们自己写的String。这样就保证了安全性。java其他很多类都依赖String 类,如果加载了我们自己写的String就会造成严重的问题。类加载器的委托机制就提供了很好的安全防护。

  在例如:我们写了一个类叫java.lang.Hello,java核心类库中并不存在这个类,但它的所属的包结构却是java.lang,而同一个包下的类具有访问protected成员的权限,(protected详细解析 点击这里)这样就又会造成一些安全问题。分析一下类加载器加载Hello类的过程,按照上面的委托机制可以确定,引导、扩展加载器范围内并没有这个类,最后Hello是被应用加载器加载的,那么这个类是在应用加载器的名字空间下。开头已经给出,不同名字空间下的类不能直接交互,那么此处的java.lang.Hello并不能像核心类库的java.lang包下的类一样拥有protected的访问权限。类加载器的委托机制再一次提供了安全保护。

四、自定义类加载器

   通过反射加载一个不存在的类

@Test
    public void funxx() throws ClassNotFoundException {
        Class.forName("xxx");
    }

  异常栈:

  

  loaderClass源码:

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

 

  分析以上信息,可以发现,ClassLoader加载类是通过loadClass()方法完成的,其工作流程如下:

  1.调用findLoadedClass()查看该类是否被加载过,如果没有,返回null

  2.如果findLoadedClass()返回null,那么使用委托机制进行类加载。

  3.如果getParent().loadClass() != null , 表明加载成功,否则调用本类的findClass()方法进行加载。

  由此可见,我们自定义一个类加载器,只需要继承ClassLoader,并重写其findClass()方法即可。

  下面是个人写的一个ClassLoader

package cn.edu;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;

public class CustomizedClassLoader extends ClassLoader{

    private String classpath;                                                
    
    public CustomizedClassLoader() {
        
    }
    
    public CustomizedClassLoader(String classpath) {
        this.classpath = classpath;
    }
    
    //当loaderClass没有找到该类时,会自动调用此方法寻找类。
    @Override
    public  Class<?> findClass(String name) throws ClassNotFoundException {
        try {
            byte[] data = this.getBytesData(name);                           //获取.class文件的字节数组
            if(data == null) {
                throw new ClassNotFoundException("找不到类:" + name);
            }
            return this.defineClass(name, data, 0, data.length);             //loaderClass继承的方法,该方法通过字节数组生成Class对象
        }catch(IOException e){
            throw new ClassNotFoundException("找不到类:" + name);             
        }
    }
    
    //读取.class文件,返回字节数组,若读取到的内容为空,则返回null
    private byte[] getBytesData(String name) throws IOException{
        name = name.replace(".", "//") + ".class";                           //解析binary name,得到文件绝对路径
        File file = new File(classpath,name);                                
        byte[] res = this.readFile(file);
        if(res.length == 0) {                                                //字节数组为空,返回null
            return null;
        }
        return res;
    }
    
    //读取指定文件,返回字节数组,若找不到该文件,则IO异常
    private byte[] readFile(File file) throws IOException{
        FileReader reader = new FileReader(file);
        BufferedReader bufferedReader = new BufferedReader(reader);
        String str = null;
        String res = "";
        while( (str = bufferedReader.readLine()) != null) {
            res += str;
        }
        return res.getBytes();
    }
}

 

、Tomcat的类加载器

   Tomcat有两类类加载器,一类是服务器类加载器,用于加载${CATALINA_HOME}/lib目录下的jar,这些是web容器所依赖的jar。还有一类是应用类加载器,加载${CONTEXT_PATH}/WEB-INF下的lib和class目录,用于加载该应用所依赖的jar和class。这两种类加载器,不适用委托机制,在应用需要加载类时,首先由应用加载器加载,加载失败再由服务器加载器加载,并且class目录优先于lib目录。并且每一个应用都有一个对应的类加载器加载。所以在给项目导入jar时应该注意不能导入与服务器类库有冲突的jar。

 

    本文个人编写,水平有限,如有错误,恳请指出,欢迎讨论分享。

posted @ 2018-06-08 16:33  随性如风  阅读(607)  评论(0编辑  收藏  举报