JVM类加载:如何手写自定义类加载器,命名空间详解

开发 后端
类加载器是负责加载类的对象。类加载器是一个抽象类。给定类的二进制名,类加载器应该尝试定位或生成构成类定义的数据(回去查找对应的class文件如果没有解析成class文件)。一个典型的策略是将名称转换为文件名,然后从文件中读取该名称的“类文件”系统。

二进制名字

如java.net.URLClassLoader$3$1 表示URLClassLoader的第三个匿名内部类中的第一个匿名内部类。

ClassLoader分析A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system. Every Class object contains a reference to the ClassLoader that defined it. Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.
  • 类加载器是负责加载类的对象。类加载器是一个抽象类。给定类的二进制名,类加载器应该尝试定位或生成构成类定义的数据(回去查找对应的class文件如果没有解析成class文件)。一个典型的策略是将名称转换为文件名,然后从文件中读取该名称的“类文件”系统。(我们编写的java程序类都是这样运行)。
  • 每个类对象都包含对定义它的类加载器的引用(getClassLoader()获取当前类的类加载器)。
  • 数组类的类对象不是由类加载器创建的,而是由类加载器创建的根据Java运行时的要求自动执行(数组类对象是由jvm虚拟机动态创建的)。数组类的类加载器,如 getclassloader()返回的类与它的元素类型的类装入器相同(数组元素类型是什么类加载器就是什么类型);如果 元素类型是基本(原生8种)类型,因此数组类没有类装入器。
  • 应用程序实现的ClassLoader类加载器为了扩展Java虚拟机动态加载的方式类。

示例:

public class Test15 {

    public static void main(String[] args) {

        String[] strings = new String[2];
        System.out.println(strings.getClass().getClassLoader()); //string[]数组元素类型是String 在rt.jar包中是由根类加载器加载的

        System.out.println("----------------");

        Test15[] test15s = new Test15[2];
        System.out.println(test15s.getClass().getClassLoader());//同理 该元素是由AppClassLoader系统类加载器加载的 但是数组本身不是由类加载器加载

        System.out.println("----------------");

        int[] ints = new int[2];//如果 元素类型是基本(原生8种)类型,因此数组类没有类装入器。
        System.out.println(ints.getClass().getClassLoader());
        
    }
}

打印:

/*
null 根类加载器
----------------
sun.misc.Launcher$AppClassLoader@18b4aac2
----------------
null 为空
 */
  • 安全管理器通常会使用类装入器来指示安全域(类加载始终伴随着安全管理器所以类加载是安全的)
  • ClassLoader类使用委托模型进行搜索类和资源。ClassLoader的每个实例都有一个关联的父类装入器。当请求查找一个类或资源,一个类加载器实例将委托父类搜索类或资源,然后再尝试查找类或资源本身。虚拟机的内置类加载器,称为“启动/根类加载器”,本身没有父类,但可以充当类装入器实例的父类。
  • 支持类的并发加载的类加载器称为 并行能力类加载器,需要注册类的初始化时间registerAsParallelCapable。ClassLoader.registerAsParallelCapable这个方法。注意,类装入器类被注册为并行类默认为able。但是,它的子类仍然需要注册它们自己如果他们是并行的能力。
  • 委托模型不严格的环境层次结构,类加载器需要能够并行,否则类加载可能导致死锁,因为加载器锁被持有类加载过程的持续时间(参见{@link #loadClass)方法。
  • 通常,Java虚拟机从本地文件加载类平台相关的系统。例如,在UNIX系统中在CLASSPATH环境变量定义的目录加载类
  • 然而,有些类可能不是起源于一个文件;他们可能会产生从其他来源,如网络,或它们可以由一个应用程序(动态代理)。方法{@link #defineClass(String, byte[], int, int)defineClass}将字节数组转换为类的实例可以使用以下命令创建这个新定义的类的实例 {@link Class#newInstance Class.newInstance}。
  • 类加载器创建的对象的方法和构造函数可以引用其他类。要确定所引用的类即Java虚拟机调用{@link #loadClass loadClass}方法(这个方法解决这个问题)最初创建类的类加载器。
ClassLoader loade = new NetworkClassLoader(host,port);
Object main = loader.loadClass("Main", true).newInstance();

自定义加载器子类必须定义两个方法{@link#findClass findClass}和loadClassData加载类来自网络。一旦它下载了构成类的字节,应该使用方法{@link #defineClass defineClass} to创建一个类实例。一个示例实现是:

class NetworkClassLoader extends ClassLoader {
          String host;
          int port;
 
          public Class findClass(String name) {
              byte[] b = loadClassData(name);
              return defineClass(name, b, 0, b.length);//通过名字将Class对象返回给调用者
          }
 
          private byte[] loadClassData(String name) {
              // load the class data from the connection
             .
          }
      }

编写自定义类加载器

自定一 此时因为在ClassPath下 所以会调用父类AppClassLoader的系统类加载器 所以自定义的的findClass不会被执行。

public class Test16 extends ClassLoader {

    private String classLoaderName;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中当创建新的类加载器自定义父加载器 如 :
        //a继承b b继承ClassLoader  此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * 查找指定二进制名字的class 这个方法应该被子类加载器实现重写,再检查完对应父加载器之后该方法会被loaderClass()方法调用 ,
     * 在父类中  throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //对应二进制名字对应的字节数组
        byte[] data = this.loadClassData(className);
        
        System.out.println("findClass invoked" + className);
        System.out.println("class loader name" + classLoaderName);
        
        //defineClass(类名,字节数据,起,末) 创建类实例
        return this.defineClass(className, data, 0, data.length);
    }

    //获取文件字节数据
    private byte[] loadClassData(String name) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;
        try {
            //传过来的文件名加上后缀
            is = new FileInputStream(new File(name + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 test16 = new Test16("test16");
        test(test16);
    }

    public static void test(ClassLoader classLoader) throws IllegalAccessException, Exception {
        //改方法会调用我们重写之后的findClass方法
        Class<?> clasz = classLoader.loadClass("com.example.demo.com.jvm.Test1");
        Object o = clasz.newInstance();
        System.out.println(o);
    }
}

打印结果:

//只输出了
com.example.demo.com.jvm.Test1@1eb44e46

基于上例重构 新增自定义路径将class字节码路径放在其他位置 此时父类加载器appClassLoader无法加载 此时就会调用自己的findClass() 需要将classpath 下的需要加载的.class删除。

public class Test16 extends ClassLoader {

    private String classLoaderName;

    //路径
    private String path;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中当创建新的类加载器自定义父加载器 如 :
        //a继承b b继承ClassLoader  此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * 查找指定二进制名字的class 这个方法应该被子类加载器实现重新,再检查完对应父加载器之后该方法会被loaderClass()方法调用 ,
     * 在父类中  throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //对应二进制名字对应的字节数组
        byte[] data = this.loadClassData(className);
        System.out.println("findClass invoked" + className);
        System.out.println("class loader name= " + classLoaderName);

        //defineClass(类名,字节数据,起,末) 创建类实例
        return this.defineClass(className, data, 0, data.length);
    }

    //获取文件字节数据
    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "\\");
        try {
            //传过来的文件名加上后缀
            is = new FileInputStream(new File(this.path + className + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 test16 = new Test16("test16");

//        test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");

        test16.setPath("E:\\cx\\");

        //改方法会调用我们重写之后的findClass方法
        Class<?> clasz = test16.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//对象内存地址有哈希值
        System.out.println(o);
    }
}
此时findClass中的打印语句执行了 
findClass invokedcom.example.demo.com.jvm.Test1
class loader name= test16

class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a

当我们在编写完自定义类加载器时重写了loadClassData和findClass方法。在main方法中实例化Test16对象调用返回系统类加载器的构造函数,因为在classpath路径以上(双亲委托下并没有找到对应的.class文件 所以自定义加载器去加载 此时调用classLoader的loadClass方法获取对应的Class实例 此时自定义类加载器并没有直接调用findClass方法 而是在loadClass方法中ClassLoader帮我们直接调用了我们自己重写好的findclass方法。

调用findClass

方法只是抛出了一个异常所有我们在自定义类加载器时必须重写对应方法。

重写方法

当我们调用对应的方法完毕。

调用完毕

重写loadClassData方法将获取对应二进制类名文件字节数组。

重写loadClassData

在通过方法获取对应二进制名称的Class对象。

通过defineClass

而在ClassLoader中的defineClass方法调用了重载的defineClass方法多加了个ProtectionDomainProtectionDomain 类封装域的特征,域中包装一个类集合,在代表给定的主体集合执行这些类的实例时会授予它们一个权限集合。主要是支持支持动态安全策略。

在这个方法里面才是真正获取对应二进制名字的Class对象。

protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
        //前置处理
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
        //此时调用底层本地C++代码获取Class 
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
        //后置处理拼接对象后缀名
        postDefineClass(c, protectionDomain);
        return c;
    }

自此程序运行结束 返回Class对象。

ClassLoader 中loadClass 详解

  • classLoader.lordClass和forName的区别(主动加载和被动加载的区别)。
  • class.forName()除了将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块。
  • 而classLoader.lordClass只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。(不初始)。
  • Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法才用调用构造函数,来创建类的对象(初始)。

ClassLoader 中loadClass 此时获取的Class还没有链接 只是刚加载到JVM中。

加载指定的二进制名的类此方法的实现会默认按照以下的顺序寻找类

  • 调用{@link #findLoadedClass(String)}检查类是否已经加载(一个类只能被加载一次)。
  • 调用父类的{@link #loadClass(String) loadClass}方法,如果父类是null 就会使用虚拟机内置的根类加载器。
  • 调用{@link #findClass(String)}方法查找。

如果类被发现使用上述步骤,和解析标志为真,此方法将调用{@link#resolveClass(Class)}方法的结果类对象。
子类ClassLoader被鼓励重写{@link#findClass(String)},而不是这个方法。

在整个类装入过程中除非被覆盖,否则此方法对的结果进行同步{@link #getClassLoadingLock getClassLoadingLock}方法。

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 检查类是否已经加载(一个类只能被加载一次)
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                       //如果父类不是null 就会使用虚拟机内置的根类加载器去加载二进制名(name对应的数据),
                       //子类ClassLoader被鼓励重写
                        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();
                }
            }
           // 如果类被发现使用上述步骤,和解析标志为真,此方法将调用{@link#resolveClass(Class)}方法的结果类对象。
            if (resolve) {
                resolveClass(c);
            }
            //返回Class
            return c;
        }
    }

基于上例Test16继续重构。

public class Test16 extends ClassLoader {

    private String classLoaderName;

    //路径
    private String path;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中当创建新的类加载器自定义父加载器 如 :
        //a继承b b继承ClassLoader  此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }
    /**
     * 查找指定二进制名字的class 这个方法应该被子类加载器实现重新,再检查完对应父加载器之后该方法会被loaderClass()方法调用 ,
     * 在父类中  throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //对应二进制名字对应的字节数组
        byte[] data = this.loadClassData(className);
        System.out.println("findClass invoked:" + className);
        System.out.println("class loader name: " + classLoaderName);

        //defineClass(类名,字节数据,起,末) 创建类实例
        return this.defineClass(className, data, 0, data.length);
    }

    //获取文件字节数据
    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "\\");
        try {
            //传过来的文件名加上后缀
            is = new FileInputStream(new File(this.path + className + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 test16 = new Test16("test16");

//        test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");

        test16.setPath("E:\\cx\\");

        //改方法会调用我们重写之后的findClass方法
        Class<?> clasz = test16.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//对象内存地址有哈希值
        System.out.println(o);

        Test16 test162 = new Test16("test17");
        Class<?> clasz2 = test16.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz2.hashCode());
        Object o2 = clasz2.newInstance();//对象内存地址有哈希值
        System.out.println(o2);
    }
}
/*
当classPath下有对应的加载的.class时 第二次交给父类加载器发现已经加载所以字节拿过来用 所以此时获取的Class类时一致的
class: 515132998
com.example.demo.com.jvm.Test1@6504e3b2
class: 515132998
com.example.demo.com.jvm.Test1@515f550a

当classPath下没有对应的加载的.class 制定了对应的路径 此时类获取几次就会加载几次 涉及到了命名空间的问题
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: test16
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a
--------------两个不同的命名空间------------------
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: test17
class: 932583850
com.example.demo.com.jvm.Test1@cac736f

 */

总结:同一个命名空间不会出现两个完全相同的类,不同的命名空间会出现两个完全相同的类,父加载器加载的类不可以看到子类加载器加载的类,但是子类加载器加载的类可以看到父类加载器加载的类。

解释:

命名空间

上例继续改造://将loader1作为loader2的父类加载器。

public class Test16 extends ClassLoader {

    private String classLoaderName;

    //路径
    private String path;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中当创建新的类加载器自定义父加载器 如 :
        //a继承b b继承ClassLoader  此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }
    /**
     * 查找指定二进制名字的class 这个方法应该被子类加载器实现重新,再检查完对应父加载器之后该方法会被loaderClass()方法调用 ,
     * 在父类中  throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //对应二进制名字对应的字节数组
        byte[] data = this.loadClassData(className);
        System.out.println("findClass invoked:" + className);
        System.out.println("class loader name: " + classLoaderName);

        //defineClass(类名,字节数据,起,末) 创建类实例
        return this.defineClass(className, data, 0, data.length);
    }

    //获取文件字节数据
    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "\\");
        try {
            //传过来的文件名加上后缀
            is = new FileInputStream(new File(this.path + className + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 loader1 = new Test16("loader1");

//        test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");

        loader1.setPath("E:\\cx\\");

        //改方法会调用我们重写之后的findClass方法
        Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//对象内存地址有哈希值
        System.out.println(o);

//        System.out.println("------------两个不同的命名空间--------------------");
        Test16 loader2 = new Test16(loader1,"loader2");//将loader1作为loader2的父类加载器

        loader2.setPath("E:\\cx\\");
        Class<?> clasz2 = loader2.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz2.hashCode());
        Object o2 = clasz2.newInstance();//对象内存地址有哈希值
        System.out.println(o2);
    }
}
-------------------------------------------
当classPath下没有对应的加载的.class时
Test16 loader2 = new Test16(loader1,"loader2");//将loader1作为loader2的父类加载器

findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a  //此时父加载器loader1已经加载完毕 loader2直接拿来使用

class: 1365202186
com.example.demo.com.jvm.Test1@5e91993f

通过上例继续改造: 新增一个类加载器 父类设置为loader2。

public class Test16 extends ClassLoader {

    private String classLoaderName;

    //路径
    private String path;

    private final String fileExtension = ".class";

    public Test16(String classLoaderName) {
        // this(checkCreateClassLoader(), getSystemClassLoader());
        // ClassLoader中当创建新的类加载器返回的的是系统类加载器, 所以当创建新的类加载器 默认父加载器为系统类加载器
        super();//可加可不加
        this.classLoaderName = classLoaderName;
    }

    public void setPath(String path) {
        this.path = path;
    }

    public Test16(ClassLoader parent, String classLoaderName) {
        // this(checkCreateClassLoader(), parent);
        //ClassLoader中当创建新的类加载器自定义父加载器 如 :
        //a继承b b继承ClassLoader  此时a可以拿这个构造方法将b作为自己的双亲 不一定都交给系统类加载器
        super(parent);
        this.classLoaderName = classLoaderName;
    }


    /**
     * 查找指定二进制名字的class 这个方法应该被子类加载器实现重新,再检查完对应父加载器之后该方法会被loaderClass()方法调用 ,
     * 在父类中  throw new ClassNotFoundException(name); 只是抛出来一个异常必须重写
     * 如:
     * java.lang.String
     *
     * @param className
     * @return
     * @throws ClassNotFoundException
     */
    @Override
    protected Class<?> findClass(String className) throws ClassNotFoundException {
        //对应二进制名字对应的字节数组
        byte[] data = this.loadClassData(className);
        System.out.println("findClass invoked:" + className);
        System.out.println("class loader name: " + classLoaderName);

        //defineClass(类名,字节数据,起,末) 创建类实例
        return this.defineClass(className, data, 0, data.length);
    }

    //获取文件字节数据
    private byte[] loadClassData(String className) {
        InputStream is = null;
        byte[] data = null;
        ByteArrayOutputStream baos = null;

        className = className.replace(".", "\\");
        try {
            //传过来的文件名加上后缀
            is = new FileInputStream(new File(this.path + className + this.fileExtension));

            baos = new ByteArrayOutputStream();

            int ch = 0;
            while (-1 != (ch = is.read())) {
                baos.write(ch);
            }
            data = baos.toByteArray();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                is.close();
                baos.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return data;
    }

    public static void main(String[] args) throws Exception {
        Test16 loader1 = new Test16("loader1");

//        test16.setPath("D:\\workspaces\\zookeeper\\target\\classes\\");

        loader1.setPath("E:\\cx\\");

        //改方法会调用我们重写之后的findClass方法
        Class<?> clasz = loader1.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz.hashCode());

        Object o = clasz.newInstance();//对象内存地址有哈希值
        System.out.println(o);

        System.out.println();

//        System.out.println("------------两个不同的命名空间--------------------");
        Test16 loader2 = new Test16(loader1, "loader2");//将loader1作为loader2的父类加载器

        loader2.setPath("E:\\cx\\");
        Class<?> clasz2 = loader2.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz2.hashCode());
        Object o2 = clasz2.newInstance();//对象内存地址有哈希值
        System.out.println(o2);

        System.out.println();

        Test16 loader3 = new Test16(loader2,"loader3");
        loader3.setPath("E:\\cx\\");

        //改方法会调用我们重写之后的findClass方法
        Class<?> clasz3 = loader3.loadClass("com.example.demo.com.jvm.Test1");
        System.out.println("class: " + clasz3.hashCode());

        Object o3 = clasz3.newInstance();//对象内存地址有哈希值
        System.out.println(o3);
    }
}

命名空间一致。

命名空间一致
findClass invoked:com.example.demo.com.jvm.Test1
class loader name: loader1
class: 1365202186
com.example.demo.com.jvm.Test1@626b2d4a loader1 先去加类加载

class: 1365202186
com.example.demo.com.jvm.Test1@5e91993f loader2 交给父类父类交给appClassLoader加载发现已经加载直接拿来用

class: 1365202186
com.example.demo.com.jvm.Test1@1c4af82c loader3 同上
责任编辑:姜华 来源: 今日头条
相关推荐

2022-08-08 08:17:43

类隔离加载器自定义类

2021-07-05 06:51:43

Java机制类加载器

2023-10-19 09:14:34

Java开发

2020-10-26 11:20:04

jvm类加载Java

2024-03-12 07:44:53

JVM双亲委托机制类加载器

2023-08-02 08:38:27

JVM加载机制

2023-10-31 16:00:51

类加载机制Java

2024-03-08 08:26:25

类的加载Class文件Java

2021-01-29 06:03:29

JDK15JVM类加载器

2012-02-09 10:31:17

Java

2011-12-16 14:23:51

Java

2023-10-18 18:23:58

2017-09-20 08:07:32

java加载机制

2021-04-29 11:18:14

JVM加载机制

2017-03-08 10:30:43

JVMJava加载机制

2012-03-13 14:41:41

JavaJVM

2024-08-09 11:50:00

2023-04-03 06:36:59

macOS内存加载器

2020-07-23 07:26:49

JVM类加载器

2022-10-08 08:34:34

JVM加载机制代码
点赞
收藏

51CTO技术栈公众号