对类加载的理解

@

对类加载的理解

自定义类加载器

/**
 * 加载器的工作流程
 * 1.先使用findLoadedClass去检查该类是否已经被加载
 * <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
 *    has already been loaded.  </p></li>
 * 2.然后尝试使用父类加载器去加载
 * <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
 *    on the parent class loader.  If the parent is <tt>null</tt> the class
 *    loader built-in to the virtual machine is used, instead.  </p></li>
 * 3.调用findClass方法加载类
 * <li><p> Invoke the {@link #findClass(String)} method to find the
 *  class.  </p></li>
 */
public class Test16 extends ClassLoader {

    private String classLoaderName;
    private String path;
    private final String fileExtension = ".class";
    public Test16(String classLoaderName){
        super();//将系统类加载器当作该类的父加载器
        this.classLoaderName = classLoaderName;
    }
    public Test16(ClassLoader parent,String classLoaderName){
        super(parent);//指定父加载器
        this.classLoaderName = classLoaderName;
    }
    public void setPath(String path) {
        this.path = path;
    }
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        System.out.println("findClass invoked: " + className);
        byte[] data = loadClassData(className);
        return super.defineClass(className,data,0,data.length);
    }
	//也就是将.class字节文件读进数据流中
    private byte[] loadClassData(String name){
        InputStream inputStream = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        try {
            name = name.replace(".",File.separator);
            inputStream = new FileInputStream(new File(this.path + name + this.fileExtension));
            baos = new ByteArrayOutputStream();
            int ch = 0;
            while (-1 != (ch = inputStream.read())){
                baos.write(ch);
            }
            data = baos.toByteArray();
        }catch (Exception e){

        }finally {
            try {
                inputStream.close();
                baos.close();
            }catch (Exception e){}
        }
        return data;
    }

    public static void test(ClassLoader classLoader) throws Exception {
        Class<?> clazz = classLoader.loadClass("com.chen.jvm.Test1");
//        Object o = clazz.newInstance();
        System.out.println("hashCode: " + clazz.hashCode());
        //classLoader: sun.misc.Launcher$AppClassLoader@18b4aac2
        /**
        	我们发现这里输出的是AppClassLoader,也就是这里并没有与我们预想中一样,采用的自定义类加载对该类加载
        	原因分析:
        	双亲委派机制,它会先委托给父类加载器对其加载,然后com.chen.jvm.Test1.class是在
        	工作空间中,所以系统类加载器发现自己是可以对Test1.class进行加载的,所以系统类加载器
        	对其进行加载,而不会采用自定义类加载器加载。如果我们将工作空间下的Test.class复制到I:\
        	对应的目录下,我们就可以调用自定义类加载器
        */
        System.out.println("classLoader: " + clazz.getClassLoader());
    }
    public static void main(String[] args) throws Exception {
        Test16 loader = new Test16("MyClassLoaderName");
        loader.setPath("I:\");
        Test16.test(loader);
    }    
}

命名空间

命名空间:每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成在同一个命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类 在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类。
注意:不同的命名空间是相互不可见的
所以,在运行期,一个Java类是由该类的完全限定吗(binary name, 二进制名) 和 用于加载该类的定义类加载器(defining loader)所共同决定的。

//测试前提:将工作空间下的com.chen.jvm.Test1.class删除
//输出hashCode: 410424423 classLoader: com.chen.jvm.Test16@3d71d552
Test16 loader2 = new Test16("MyClassLoaderName2");
loader2.setPath("I:\");
Test16.test(loader2);

//loader3会先交给父类加载器loader2进行加载,但是loader2已经对该类进行加载过了
//所以该类直接会返回loader2加载的
//输出hashCode: 410424423   classLoader: com.chen.jvm.Test16@3d71d552
Test16 loader3 = new Test16(loader2,"MyClassLoaderName3");
loader3.setPath("I:\");
Test16.test(loader3);

测试不同命名空间是相互不可见的

public class MyPerson {
    private MyPerson myPerson;
    public void setMyPerson(Object object){
        this.myPerson = (MyPerson) object;
    }
}
/**
 * 类加载双亲委培模型的好处:
 * 1.可以确保Java核心库的类型安全:所有的Java应用都至少会引用到java.lang.Object类,也就是说在运行期,
 *   java.lang.Object这个类会被加载到Java虚拟机中;如果这个加载过程是Java引用自己的类加载器所完成的,
 *   那么很可能就会在JVM中存在多个版本的java.lang.Object类,而且这些类之间还是互不兼容的,相互不可见的
 *   (正是因为命名空间的作用)。借助于双亲委派机制,Java核心类库中的类的加载工作都是由启动类加载器来统一
 *   完成的,从而确保了Java应用所使用的都是同一个版本的
 * 2.可以确保Java核心类库所提供的类不会被自定义的类所替代
 * 3.不同的类加载器可以为相同名称(binary name)的类创建额外的命名空间。相同名称的类可以并存在java虚拟机中,
 *  只需要用不同类加载器来加载它们即可。不同类加载所加载的类之间互不兼容的,这就相当于在Java虚拟机内存创建了
 *  一个由一个相互隔离的Java类空间,这类技术在很多框架中都得到了实际应用
 */
public class Test21 {
    public static void main(String[] args) throws Exception {
        Test16 loader1 = new Test16("loader1");
        Test16 loader2 = new Test16("loader2");

        loader1.setPath("I:\");
        loader2.setPath("I:\");

        Class<?> clazz1 = loader1.loadClass("com.chen.jvm.MyPerson");
        Class<?> clazz2 = loader2.loadClass("com.chen.jvm.MyPerson");
        /**
            如果工作空间中的MyPerson.class没有删除的话,返回结果为true
            原因分析:
                因为工作空间中存在MyPerson.class那么MyPerson会被系统类加载器加载,
                当loader2.loadClass("com.chen.jvm.MyPerson");时,系统类加载器发现已经加载,
                那么将不会再对其进行加载。clazz1 == clazz2为true
         */
        /**
             如果工作空间中的MyPerson.class删除的话,返回结果为fasle,
             且mthod.invoke(o,o1);会出现异常java.lang.ClassCastException:
             com.chen.jvm.MyPerson cannot be cast to com.chen.jvm.MyPerson
             这个异常时转换异常,我们发现其提示是:MyPerson不能被转换为MyPerson
             原因分析:
                由于工作空间中不存在MyPerson.class(将MyPerson.class拷贝到I:comchenjvm目录下),
                这样的话我们会采用自定义加载器对其进行加载,然后clazz1是由loader1加载,class2是由loader2加载
                loader1与loader2之间没有继承关系,那么他们拥有各自的独立空间,且相互之间是不可见的。
                那么clazz1==clazz2肯定为false
         */
        System.out.println(clazz1 == clazz2);  //false

        Object o = clazz1.newInstance();
        Object o1 = clazz2.newInstance();
        Method mthod = clazz1.getMethod("setMyPerson", Object.class);
        /**
         * 但是这里会报错,因为clazz1与clazz2是不同的命名空间
         * 对于两个加载器之间没有直接或间接的父子关系,那么它们各自加载的类是相互不可见的
         * 对于this.myPerson = (MyPerson) object;会出现异常
         * java.lang.ClassCastException: com.chen.jvm.MyPerson cannot be cast to com.chen.jvm.MyPerson
         * 这个异常时MyPerson不能转换成MyPerson,根据上面的分析,我们可以直到,这两个类是不可见的,所以会出现转换异常
         */
        mthod.invoke(o,o1);
    }
}

拓展类加载器的特殊

/**
 * 关于拓展类加载器有一个说明
 *     如:我们java -Djava.ext.dirs=xxx/xxx 也就是我们修改拓展类的目录,
 *     并把Test1.class放置在xxx/xxx目录中,但是Test.class并不是由拓展类加载器加载,
 *     这是不是与我们的结论是否矛盾?这是因为拓展类加载器是需要加载jar包的,所以当
 *     我们把Test1.class打包成jar包就可以了
 */
public class Test22 {
    static {
        System.out.println("Test22 initializer...");
    }
    public static void main(String[] args) {
        System.out.println(Test22.class.getClassLoader());
        System.out.println(Test1.class.getClassLoader());
    }
}

问题

对于类加载器,AppClassLoader与ExtClassLoader是由什么加载的呢?
其是由内建于JVM中的启动类加载器加载,启动类加载器会加载java.lang.ClassLoader以及其他的Java平台类,当JVM启动时,一块特殊的机器码会运行,它会加载拓展类加载器与系统类加载器,这块特殊的机器码叫做启动类加载器(Bootstrap)
启动类加载器并不是Java类,而其他的加载器则都时Java类,启动类加载器是特定于平台的机器指令,他负责开启整个加载过程

启动类加载器还会赋值加载JRE正常运行所需要的基本组件,如java.util和java.lang包中的类等
Launcher源码

public Launcher() {
   Launcher.ExtClassLoader var1;
    try {
    	//ExtClassLoader是Launcher的一个内部类,getExtClassLoader()获取拓展类加载器
    	//getExtClassLoader()方法里返回的拓展类加载器实例,并且通过系统属性java.ext.dirs加载其需要加载的类
        var1 = Launcher.ExtClassLoader.getExtClassLoader();
    } catch (IOException var10) {
        throw new InternalError("Could not create extension class loader", var10);
    }

    try {
    	//获取系统类加载器,并且将扩展类加载器作为他的委托传入
        this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
    } catch (IOException var9) {
        throw new InternalError("Could not create application class loader", var9);
    }
	//将系统类加载器作为当前线程上下文的加载器
    Thread.currentThread().setContextClassLoader(this.loader);
    String var2 = System.getProperty("java.security.manager");
    if (var2 != null) {
        SecurityManager var3 = null;
        if (!"".equals(var2) && !"default".equals(var2)) {
            try {
                var3 = (SecurityManager)this.loader.loadClass(var2).newInstance();
            } catch (IllegalAccessException var5) {
            } catch (InstantiationException var6) {
            } catch (ClassNotFoundException var7) {
            } catch (ClassCastException var8) {
            }
        } else {
            var3 = new SecurityManager();
        }

        if (var3 == null) {
            throw new InternalError("Could not create SecurityManager: " + var2);
        }

        System.setSecurityManager(var3);
    }
}
原文地址:https://www.cnblogs.com/liuligang/p/10517811.html