ClassLoader深入学习记录

ClassLoader是什么

  • ClassLoader是Java的类加载机制
    ClassLoader用于动态加载class文件到内存中

Java程序写好后,是由若干个class文件组织而成的一个完整的Java应用程序;程序运行时,会调用改程序的一个入口函数来调用系统的相关功能;这些功能被封装在不同的class文件中;程序启动时,不会一次性加载程序所需要的所有class文件,而是根据需要,通过ClassLoader来动态加载某个class文件到内存中;当class文件被载入到内存后,才能被其他class所引用;

Java提供的ClassLoader

  • Java提供的ClassLoader有三个
    BootStrap ClassLoader(启动类加载器):是Java类加载层最顶层的类加载器,由C++编写而成, 已经内嵌到JVM中了;主要用来加载JDK的核心类库,如:rt.jar、resources.jar、charsets.jar等;
    eg:利用以下代码获得BootStrap ClassLoader加载的jar或class文件
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();  
for (int i = 0; i < urls.length; i++) {  
    System.out.println(urls[i].toExternalForm());  
}  
System.out.println(System.getProperty("sun.boot.class.path"));
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/resources.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/rt.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/sunrsasign.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jsse.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jce.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/charsets.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/lib/jfr.jar
file:/C:/Program%20Files/Java/jre1.8.0_121/classes

Extension ClassLoader(扩展类加载器):负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext目录下的所有jar;

App ClassLoader(扩展系统类加载器):负责加载应用程序classpath目录下的所有jar和class文件


CustomClassLoader:自定义ClassLoader(继承java.lang.ClassLoader类/Extension ClassLoader/App ClassLoader);然而,不能继承Bootstrap ClassLoader,因为Bootstrap ClassLoader由底层C++编写,已迁入JVM内核,当JVM启动后,Bootstrap ClassLoader也随之启动,负责加载核心类库,并后遭Extension ClassLoader和App ClassLoader类加载器;

ClassLoader深入学习记录_第1张图片

查看父类加载器

/**
 * 查看父类加载器
 */
private static void test1() {
    ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
    System.out.println("系统类装载器:" + appClassLoader);
    ClassLoader extensionClassLoader = appClassLoader.getParent();
    System.out.println("系统类装载器的父类加载器——扩展类加载器:" + extensionClassLoader);
    ClassLoader bootstrapClassLoader = extensionClassLoader.getParent();
    System.out.println("扩展类加载器的父类加载器——引导类加载器:" + bootstrapClassLoader);
}

ExtensionClassLoader的parent为null因为bootstrapClassLoader不是一个普通的Java类

ClassLoader加载类的原理

原理

使用双亲委托模型来搜索类;每个ClassLoader实例都有一个父类加载器的引用(是一种包含关系);而虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有福类加载器,但可以用作其他的任务委托给它的福类加载器,这个过程是自上而下一次检查的;

首先有最顶层的类加载器Bootstrap ClassLoader视图加载,若没有加载到;则将任务转交给Extension ClassLoader试图加载,若没有加载到;则转交给App ClassLoader进行加载,若没有加载成功,则返回给委托的发起者,由它到指定的文件系统或网络等URL中加载该类;若都没有加载到这个类,则跳出ClassNotFoundException异常;否则将这个找到的类生成一个类的定义,并将它加载到内存中,最后返回这个类在内存中的实例对象;

为什么使用双亲委托模型

因为这样可以避免重复加载,当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次;考虑到安全因素,若不使用这种委托模式,那我们就可以随时使用自定义的String来动态替代java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的String,除非改变JDK中ClassLoader搜索类的默认算法。

JVM搜索类的时候,如何判定两个class是相同的

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。只有两者同时满足的情况下,JVM才认为这两个class是相同的

自定义ClassLoader

三个重要方法

  • loadClassclassloader加载类的入口,此方法负责加载指定名字的类,ClassLoader的实现方法为先从已经加载的类中寻找,如没有则继续从父ClassLoader中寻找,如仍然没找到,则从BootstrapClassLoader中寻找,最后再调用findClass方法来寻找,如要改变类的加载顺序,则可覆盖此方法,如加载顺序相同,则可通过覆盖findClass来做特殊的处理,例如解密、固定路径寻找等,当通过整个寻找类的过程仍然未获取到Class对象时,则抛出ClassNotFoundException。如类需要resolve,则调用resolveClass进行链接。

  • findClass此方法直接抛出ClassNotFoundException,因此需要通过覆盖loadClass或此方法来以自定义的方式加载相应的类。

  • defineClass此方法负责将二进制的字节码转换为Class对象,这个方法对于自定义加载类而言非常重要,如二进制的字节码的格式不符合JVM Class文件的格式,抛出ClassFormatError;如需要生成的类名和二进制字节码中的不同,则抛出NoClassDefFoundError;如需要加载的class是受保护的、采用不同签名的或类名是以java.开头的,则抛出SecurityException;如需加载的class在此ClassLoader中已加载,则抛出LinkageError。

在自定义ClassLoader时,一般只重写findClass而不是loadClass

loadClass源码

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;
        }
    }

JDK已经在loadClass方法中帮我们实现了ClassLoader搜索类的判断方法,当在loadClass方法中搜索不到类时,loadClass方法就会调用findClass方法来搜索类,所以我们只需重写该方法即可

自定义ClassLoader

来自:http://blog.csdn.net/tonytfjing/article/details/47212291

1.定义一个Person接口

public interface Person {
    public void say();
}

2.再定一个类实现这个接口

public class HighRichHandsome implements Person {

    @Override
    public void say() {
        System.out.println("I don't care whether you are rich or not");
    }

}

3.编写ClassLoader

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

public class MyClassLoader extends ClassLoader{
    /* 
     * 覆盖了父类的findClass,实现自定义的classloader
     */
    @Override
    public Class findClass(String name) {
        byte[] bt = loadClassData(name);
        return defineClass(name, bt, 0, bt.length);
    }

    private byte[] loadClassData(String className) {
        InputStream is = getClass().getClassLoader().getResourceAsStream(
                className.replace(".", "/") + ".class");
        ByteArrayOutputStream byteSt = new ByteArrayOutputStream();
        int len = 0;
        try {
            while ((len = is.read()) != -1) {
                byteSt.write(len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return byteSt.toByteArray();
    }
}

4.测试类

package classloader;

public class LoaderTest {
    public static void main(String args[]) throws Exception{
        test2();
    }
    
    
    private static void test2() throws Exception{
        MyClassLoader loader = new MyClassLoader();
        Class c = loader.loadClass("classloader.HighRichHandsome");
        System.out.println("Loaded by :" + c.getClassLoader());

        Person p = (Person) c.newInstance();
        p.say();

        HighRichHandsome man = (HighRichHandsome) c.newInstance();
        man.say();    
    }

    private static void test3() throws Exception{
    MyClassLoader loader = new MyClassLoader();
    Class c = loader.findClass("com.alibaba.classload.HighRichHandsome");
    System.out.println("Loaded by :" + c.getClassLoader());

    Person p = (Person) c.newInstance();
    p.say();

    //注释下面两行则不报错
    HighRichHandsome man = (HighRichHandsome) c.newInstance();
    man.say();    
}
}

测试结果:

Loaded by :sun.misc.Launcher$AppClassLoader@73d16e93
I don't care whether you are rich or not
I don't care whether you are rich or not

遇到的一些问题

学习ClassLoader的教程是在网上看的;于是在使用sun.misc.Launcher的时候发现编译器找不到这个类;但是sun.misc这个包是存在于rt.jar的,只是apidoc中没有,那是因为sun开头的包不属于J2SE规范,是Sun公司的内部实现;
故,使用一下方法解决:

右键项目-》属性-》java bulid path-》jre System Library-》access rules-》resolution选择accessible,下面填上** 点击确定


参考资料:http://blog.csdn.net/xyang81/article/details/7292380
http://blog.csdn.net/tonytfjing/article/details/47212291

你可能感兴趣的:(ClassLoader深入学习记录)