类加载器

类加载器

java被编译以后会生成一个.class的文件,这个文件是存放了一些对象的字节码信息,而这些字节码需要被加载到内存中,,这时候就需要我们用到ClassLoade


先看下面的代码

ClassLoader classLoader = ClassLoaderTest.class.getClassLoader();
// sun.misc.Launcher$AppClassLoader
//sun.misc.Launcher$ExtClassLoader
while (classLoader != null) {
    System.out.println(classLoader.getClass().getName());
   classLoader = classLoader.getParent();
}
        
// System类的加载器的名称:null
System.out.println("System类的加载器的名称:"+System.class.getClassLoader());
// List类的加载器的名称:null
System.out.println("List类的加载器的名称:"+ List.class.getClassLoader());
// this类的加载器的名称:sun.misc.Launcher$AppClassLoader
System.out.println("this类的加载器的名称:"+ ClassLoaderTest.class.getClassLoader().getClass().getName());

从打印结果中可以看出来 ClassLoaderTest类时由AppClassLoader类加载器加载的

Java虚拟机中类加载器

java系统默认三个主要的类加载器,每个类负责加载特定位置的类:BootStrap,ExtClassLoader,AppClassLoader;
前两我们我们看到过了,没有见过的就是第一个BootStrap,那么这个是干什么用的,这里我们先思考一个问题,ExtClassLoader,AppClassLoader这两个都是java文件,既然是java文件,那么它们也需要个类加载器,所以可以认为加载它们的应该不能再是一个java类了,那是什么东东?其实就是BootStrapç是用c/c++写的,封装到JVM内核当中了

  • BootStrap 负责将 它负责将/lib下面的核心类库或-Xbootclasspath选项指定的jar包加载到内存中,由于是虚拟机内核中的代码,开发者是无法引用的
  • ExtClassLoader 由Sun的ExtClassLoader(sun.misc.Launcher$ExtClassLoader)实现的。它负责将< Java_Runtime_Home >/lib/ext或者由系统变量-Djava.ext.dir指定位置中的类库加载到内存中。开发者可以直接使用标准扩展类加载器。
  • AppClassLoader 由Sun的 AppClassLoader(sun.misc.Launcher$AppClassLoader)实现的。它负责将系统类路径java -classpath-Djava.class.path变量所指的目录下的类库加载到内存中。开发者可以直接使用系统类加载器。

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

类加载器_第1张图片
ClassLoader

类加载器的委托机制:

当Java虚拟机要加载第一个类的时候,到底派出哪个类加载器去加载呢?

  • 首先当前线程的类加载器去加载线程中的第一个类(当前线程的类加载器:Thread类中有一个get/setContextClassLoader(ClassLoader cl);方法,可以获取/指定本线程中的类加载器)
  • 如果类A中引用了类B,Java虚拟机将使用加载类A的类加载器来加载类B
  • 还可以直接调用ClassLoader.loadClass(String className)方法来指定某个类加载器去加载某个类
    每个类加载器加载类时,又先委托给其上级类加载器当所有祖宗类加载器没有加载到类,回到发起者类加载器,还加载不了,则会抛出ClassNotFoundException,不是再去找发起者类加载器的儿子,因为没有getChild()方法。
  • 如上图所示: CustomClassLoader->AppClassLoader->ExtClassLoader->BootStrap.自定定义的CustomClassLoader首先会先委托给AppClassLoader,AppClassLoader会委托给ExtClassLoader,ExtClassLoader会委托给BootStrap,这时候BootStrap就去加载,如果加载成功,就结束了。如果加载失败,就交给ExtClassLoader去加载,如果ExtClassLoader加载成功了,就结束了,如果加载失败就交给AppClassLoader加载,如果加载成功,就结束了,如果加载失败,就交给自定义的CustomClassLoader类加载器加载,如果加载失败,就报ClassNotFoundException异常,结束。

现在咱们再想一下上面的代码的打印结果,System类,List,Map等这样的系统提供jar类都在rt.jar中,所以由BootStrap类加载器加载,因为BootStrap是祖先类,不是Java编写的,所以打印出class为null,默认情况它的类加载时由AppClassLoader来完成的,如果我们将项目打包成.jar并将文件拷贝到Java的安装目录中的Java/jre7/lib/ext/目录下,这时候就发,这时候就说明了上面的结论是正确的,因为ExtClassLoader加载jre/ext/*.jar,首先AppClassLoader类加载器发请求给ExtClassLoader,然后ExtClassLoader发请求给BootStrap,但是BootStrap没有找到ClassLoaderTest类,所以交给ExtClassLoader处理,这时候ExtClassLoader.jar中找到了ClassLoaderTest类,所以就把它加载了,然后结束了。

自定义类加载器

先看下他的构造方法

     protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }
    
    protected ClassLoader(ClassLoader parent) {
        this(checkCreateClassLoader(), parent);
    }

通过上面的分析,默认的类加载器是AppClassLoader,如果我们自定义CloassLoader的话,主要是自定义加载Class文件的方式,可以查看loadClass里面的源码

    protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 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
                }
            }
            return c;
    }

仔细分析源码发现,只需定义findClass方法即可,findClass这个方法就是根据name来查找到class文件,在loadClass方法中用到,所以我们只能重写这个方法了,只要在这个方法中找到class文件,返回一个Class对象即可。
问题来了,如果将一个class文件变成Class对象呢?我们需要用到下面的这个方法:defineClass,这个方法很简单就是将class文件的字节数组编程一个Class对象,这个方法不能重写,内部实现是在C/C++代码中实现的

下面我们重新定义个ClassLoader,重写里面的findClass方法,我们去加载一个加密的文件,在findClass里面去解密

protected Class findClass(String name) throws ClassNotFoundException {  
        //class文件的路径  
        String classPathFile = classDir + "/" + name + ".class";  
        try {  
            //将class文件进行解密  
            FileInputStream fis = new FileInputStream(classPathFile);  
            ByteArrayOutputStream bos = new ByteArrayOutputStream(); 
            // 这里是解密操作 
            encodeAndDecode(fis,bos);  
            byte[] classByte = bos.toByteArray();  
            //将字节流变成一个class  
            return defineClass(classByte,0,classByte.length);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
        return super.findClass(name);  
    } 

这个时候我们调用我们自定义的那个ClassLoader的话能够调用成功,通过getClass().getClassLoader().getClass().getName()打印出来的ClassLoader就是我们自己定义的ClassLoader,

到此不要以为结束了,这里还有很多的问题呀,看一下问题的结果是没有问题,但是这里面还有很多的东西需要去理解的,首先来看一下,按照我们之前说的类加载机制,应该是先交给父级的类加载器,AppClassLoader->ExtClassLoader->BootStrap,ExtClassLoader和BootStrap没有找到ClassLoaderAttach.class,但是AppClassLoader类加载器应该能找到呀,可以为什么也没有找到呢?这时因为loadClass方法在使用系统类加载器的时候需要传递全称(包括包名),我们传递ClassLoaderAttachment的话,AppClassLoader也是没有找到这个ClassLoaderAttachment,所以还是MyClassLoader处理了,如果我们这么处理后,应该是能够正常加载的

Class classDate = new MyClassLoader("class_temp").loadClass("ClassLoaderAttachment");

改成

Class classDate = new MyClassLoader("class_temp").loadClass("com.ding.demo.ClassLoaderAttachment");

这样修改后就变成AppClassLoader了,这个时候我们如果用了加密后的ClassLoaderAttachment文件,是会报错的,无法加载该文件的.

你可能感兴趣的:(类加载器)