欢迎使用CSDN-markdown编辑器

来源:互联网 发布:淘宝上买dota2饰品条件 编辑:程序博客网 时间:2024/06/18 17:47

众所周知,JVM有4层类加载器,他们是:
这里写图片描述

分别加载自己负责的那个区域内的class文件的加载。
加载的类型由 包名+类型名称和加载他的那个加载器的名称共同确定。
类加载器加载过程:
第一步:判断他的父类是否已经被加载,如果没有,就先加载他的父类。第二步:加载完成后进行连接动作:(验证、准备、解析)。第三步:视情况需要判断是否进行类的初始化。类的初始化由5个动作触发,并且类的生命周期内只会初始化一次,当多个线程共同来进行初始化动作时,JVM会保证初始化动作的线程安全,即只有一个线程可以进行初始化动作,其他线程开始阻塞,当初始化动作完成之后其他线程跳过初始化阶段,直接进行下边的工作,这是由JVM保证的(单例模式的懒加载实现方式就是由于这个原因)。

加载完成之后的结果是:在JVM的堆中形成了一个Class的实例对象。
在方法区中形成了描述类的相关类型信息的数据结构,包括:类型全名,访问修饰符,父类名称,实现的接口列表,类字段列表、方法的字节码信息,一个指向加载这个类的类加载器的引用,一个指向堆中class对象的引用。

双亲委派模型指的是:当一个类加载器的loadClass方法被调用时,他的第一选择是去把这个类型交给他的父类加载器去加载,当他的父类不存在或者无法顺利加载时,才会调用自己的findClass方法进行加载工作,findClass会找到class字节码文件,将其转化为字节流,然后调用defineClass方法(这是一个native方法)他它会自己将流数据转化为JVM中的数据结构。

基本思路就是继承ClassLoader类,或者继承URLClassLoader类,重写他的findClass方法。
目录结构:
这里写图片描述

public class MyClassLoader extends URLClassLoader {    public MyClassLoader(URL[] urls, String baseDir) {        super(urls);        this.baseDir = baseDir;    }    public MyClassLoader(URL[] urls, ClassLoader parent) {        super(urls, parent);    }    private String baseDir = "";    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        byte[] bs  = null;        try {            bs = findClassByte(name);        } catch (IOException e) {            e.printStackTrace();        }        Class c = defineClass(name,bs,0,bs.length);            if (c == null)                 throw new ClassFormatError();            return c;    }    private byte[]  findClassByte(String name) throws IOException {        InputStream in = ClassLoader.getSystemResourceAsStream(getFileName(name));        ByteArrayOutputStream bos = new ByteArrayOutputStream();        byte[] data = new byte[1024];        int length = -1;        while((length = in.read(data))>0){            bos.write(data,0,length);        }        in.close();        bos.close();        return bos.toByteArray();    }    /**     * 做class的路径转换工作     * @param name     * @return     */ /*   private String getFileName(String name){        StringBuilder sb = new StringBuilder(baseDir);        name = name.replace(".", File.separator)+".class";        return sb.append(File.separator+name).toString();    }*/    private String getFileName(String name){        StringBuilder sb = new StringBuilder("");//        name = name.replace(".", File.separator)+".class";        //处理最后一个斜杠       String begin =  name.substring(0,name.lastIndexOf('.'));       String end = name.substring(name.lastIndexOf('.'));       end = end.replace(".",File.separator);        sb.append(begin);        sb.append(end);        sb.append(".class");        System.out.println("name:"+sb.toString());        return sb.toString();    }}

需要注意的地方在于:
在IDEA中,使用getResourceAsStream();方法获取项目中的资源的时候路径是这样的:

InputStream  in =ClassLoader.getSystemResourceAsStream("com.zf.jvm/User.class");

什么要这样写,本人水平有限,请高手来解答。

总之要在实现的ClassLoader中加载资源的时候必须进行名称的转换。
下边是测试代码:

 @Test    public void test3() throws Exception {        URL urls[] = new URL[10];        MyClassLoader myClassLoader = new MyClassLoader(urls,"");        Class c = myClassLoader.loadClass("com.zf.jvm.User");//        System.out.println(c == com.zf.jvm.User.class);        System.out.println(c.getClassLoader());//        com.zf.jvm.User user = new com.zf.jvm.User();//        System.out.println(user.getClass()  == c);    }

最终成功打印出加载他的那个类。可以根据Class类利用反射机制生成对象,也是很有用的,先实现类的动态加载,并且类加载器是根据需要进行动态加载的,并不会把路径上所有的Class文件都进行加载而是根据需要动态加载,Class.forName()方法会导致类加载后进行了初始化工作,但是字面量User.class并不会导致初始化过程。