java学习 - 类加载- class load

类加载- 双亲委派

对于双亲委派的理解:在loadClass过程中,会首先查找当前ClassLoader是否已经加载当前class,如果未加载,则会一直递归查找当前ClassLoader的parentClassLoader,直到parentClassLoader为nun或找到已加载的class;在该递归过程中的查找就称为双亲委派;

在这里需要特殊注意,由于允许自定义classLoader进行加载class(defineClass),因此会出现 class != class 的存在

  public static void main(String[] args) throws ClassNotFoundException {
        // 加载 user.class文件
        String classPath = System.getProperty("user.dir").concat("/deep-in-java/fifth-stage/java-storage-config/target/classes/");
        log.info("{}", classPath);
        // 对于 User.class 当前操作实际也会将User进行加载, 因此替换为 手动路径
        String classReferenceName = "com.xing.level.User";
        log.info("{}", classReferenceName);
        loadClassBeforeParent(classPath, classReferenceName);
//        loadClassAfterParent(classPath, classReferenceName);

    }

    /**
     * 这里可以体现出双亲委派的特点:
     * 对于classLoader 而言,在执行loadClass时,会首先判断当前classLoader是否已加载过当前class;如果未加载,则首先判断是否有parentClassLoader,如果存在,则会递归调用parent loadClass 如果当前class为首次加载则最终会执行到findBootstrapClass使用bootstrapClassLoader执行class 加载
     *
     * @param classPath
     * @param classReferenceName
     * @throws ClassNotFoundException
     */
    private static void loadClassAfterParent(String classPath, String classReferenceName) throws ClassNotFoundException {
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        // 父加载器加载 class
        Class<?> aClass1 = contextClassLoader.loadClass(classReferenceName);
        MyClassLoader myClassLoader = new MyClassLoader();
//        Class<?> aClass = myClassLoader.defineClass(classReferenceName, new File(classPath.concat(classReferenceName.replaceAll("\\.", "/").concat(".class"))));
        Class<?> aClass = myClassLoader.loadClass(classReferenceName);
        log.info("{}", aClass == aClass1);
        log.info("{} load {} equals {} load {};{}; \n{}'s parent is {} that equals {};{} ", contextClassLoader, aClass1, myClassLoader, aClass, aClass == aClass1, myClassLoader, myClassLoader.getParent(), contextClassLoader, myClassLoader.getParent() == contextClassLoader);
        myClassLoader.loadClass(classReferenceName);
    }

    /**
     * 在父加载器之前加载class
     *
     * @param classPath
     * @param classReferenceName
     * @throws ClassNotFoundException
     */
    private static void loadClassBeforeParent(String classPath, String classReferenceName) throws ClassNotFoundException {
        MyClassLoader myClassLoader = new MyClassLoader();
        Class<?> aClass2 = myClassLoader.loadClass(classReferenceName);
        Class<?> defineClass = myClassLoader.defineClass(classReferenceName, new File(classPath.concat(classReferenceName.replaceAll("\\.", "/").concat(".class"))));
        log.info("{}; aClass2 == defineClass :{}", defineClass,aClass2==defineClass);
        Stream.of(defineClass.getDeclaredFields()).forEach(field -> {
            log.info("字段:{}", field);
        });

        // 获取线程上下文的classLoader
        ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader();
        Class<?> aClass = contextClassLoader.loadClass(classReferenceName);
        log.info("{}", aClass == aClass2);
        /**
         * 对于 myClassLoader 是将当前线程的classLoader作为parent
         * 对于相同的class由于是使用用的不同的ClassLoader进行加载,因此就会存在 User.class != User.class的情况
         */
        log.info("{}是否等于{}加载的相同的class:{}", myClassLoader, contextClassLoader, defineClass == aClass);
        Thread.currentThread().setContextClassLoader(myClassLoader);
        // 重新设置当前线程的classLoader
        Class<?> aClass1 = Thread.currentThread().getContextClassLoader().loadClass(classReferenceName);
        // 根据结果可以得到,对于相同的classLoader 如果已经加载过的class,则不会再次重新创建新的class对象
        // 最终调用的为 "native Class<?> findLoadedClass0(String name)"
        log.info("当重新设置当前线程的classLoader为myclassLoader时:{}", defineClass == aClass1);
    }

    static class MyClassLoader extends ClassLoader {

        /**
         * 指定当前自定义classLoader的parent
         */
        public MyClassLoader() {
            super(Thread.currentThread().getContextClassLoader());
        }

        /**
         * 加载一个.class文件变为class对象
         *
         * @param name
         * @param classFile
         * @return
         */
        public Class<?> defineClass(String name, File classFile) {
            byte[] classBytes = fileToBytes(classFile);
            return super.defineClass(name, classBytes, 0, classBytes.length);
        }

        private byte[] fileToBytes(File classFile) {
            byte[] bytes = null;
            try {
                bytes = FileUtils.readFileToByteArray(classFile);
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
            return bytes;
        }
    }

 

 

类加载-classPath 和 classLoader

在上述双亲委派过程中,会存在直到parent为null也没有查找到当前指定的Class对象,此时就需要查找当前工程的文件数据 有可能是 文件夹也有可能是 jar 等文件

对于这里要查找的指定module,在项目启动时就已经根据 类的全路径维护了一个本地缓存("jdk.internal.loader.BuiltinClassLoader#packageToModule"),key为类的全路径,value为指定的module地址;

根据当前module 地址来进行实际的class文件查找,如果能找到当前文件,则将文件流 解析为 Class对象这里利用了native java.lang.ClassLoader#defineClass1;如果找不到文件则会抛出ClassNotFoundException

如果没有找到指定的module, 则按照双亲委派的逻辑进行查找,如果找不到,则在当前工程按照路径进行查找,如果还是找不到则抛出ClassNotFoundException

 

对于文件查找 在jdk原生提供了一些内置的协议,并提供对应的Handler来进行文件查找; 可以查看 "sun.net.www.protocol." 包;

 

  public static void main(String[] args) throws ClassNotFoundException {
        // 在启动命令中增加 -verbose:class 打印当前类启动记载的所有class所在的文件夹
        /**
         * [0.137s][info][class,load] com.xing.level.ClassPathAndClassLoaderDemo source: file:/Users/xingguo/sifou-learning-java/deep-in-java/fifth-stage/java-storage-config/target/classes/
         * 对于当前类可以看到其class所处的文件夹
         */
        /**
         * file:/Users/xingguo/sifou-learning-java/deep-in-java/fifth-stage/java-storage-config/target/classes/
         * 可以看到这里也会打印出当前class文件路径
         * 其打印结果还代表 file 协议,表示其为一个file协议的URL
         * 在 sun.net.www.protocol 包下存在不同的内置协议的Handler
         * 对于所有的不同的协议的实际都属于URL因此在java.net.URL#openConnection(java.net.Proxy)根据不同的handler实现来实现
         */
        printClassPath(ClassPathAndClassLoaderDemo.class);
        /**
         * 对于 使用BootStrap classLoader 加载的class 是不能找到其codeSource 路径的
         */
        printClassPath(Object.class);

        /**
         * 对于  jdk.internal.loader.ClassLoaders$AppClassLoader#loadClass的过程实际为
         *  AppClassLoader#loadClass(java.lang.String, boolean) -> BuiltinClassLoader#loadClass(java.lang.String, boolean)
         *  -> BuiltinClassLoader#loadClassOrNull(java.lang.String, boolean) (如果当前classLoader为BootClassLoader则调用BootClassLoader#loadClassOrNull(java.lang.String, boolean))
         *  -> ClassLoader#findLoadedClass(java.lang.String) 校验当前ClassLoader是否已加载过当前class -> 如果已加载则直接返回
         *  ->反之,则会执行BuiltinClassLoader#findLoadedModule(java.lang.String) 模块化加载查找 或 递归查找parentClassLoader 直到找到已加载的class或parent为空 -> 如果 找到已加载的class则会直接返回
         *  -> 反之,则 BuiltinClassLoader#findClassOnClassPathOrNull(java.lang.String) 根据classPath进行URL查找,如果能查找到指定的class文件,则会解析文件并生成Class对象,反之则返回空
         */
        /**
         * 由于当前工程为module化工程, 以下代码的执行顺序为 首先获取到 AppClassLoader
         * jdk.internal.loader.ClassLoaders.AppClassLoader#loadClass(java.lang.String, boolean)
         * -> jdk.internal.loader.BuiltinClassLoader#loadClass(java.lang.String, boolean)
         * -> jdk.internal.loader.BuiltinClassLoader#loadClassOrNull(java.lang.String, boolean)
         * -> jdk.internal.loader.BuiltinClassLoader#findClassInModuleOrNull(jdk.internal.loader.BuiltinClassLoader.LoadedModule, java.lang.String)  由于当前项目为module(jdk),且为第一次加载
         * -> jdk.internal.loader.BuiltinClassLoader#defineClass(java.lang.String, jdk.internal.loader.BuiltinClassLoader.LoadedModule) 在这里会将当前 .class文件转换为 byte[],并查找到 当前.class文件所在的文件夹
         * -> java.lang.ClassLoader#defineClass(java.lang.String, java.nio.ByteBuffer, java.security.ProtectionDomain)
         * -> java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int, java.security.ProtectionDomain)
         * -> java.lang.ClassLoader#defineClass1(java.lang.ClassLoader, java.lang.String, byte[], int, int, java.security.ProtectionDomain, java.lang.String) (native 调用 jvm)
         *
         */
        ClassLoader.getSystemClassLoader().loadClass("com.xing.level.User");
        /**
         * 如果最终在当前工程中都找不到当前class则会抛出 ClassNotFoundException
         */
//        ClassLoader.getSystemClassLoader().loadClass("com.xing.level.User1");
        /**
         * 这里依赖了 spring-core:5.2.8 并在module-info中添加了 " requires spring.core;"
         */
        // file:/Users/xingguo/.m2/repository/org/springframework/spring-core/5.2.8.RELEASE/spring-core-5.2.8.RELEASE.jar
        // 加载当前jar中的class文件
        ClassLoader.getSystemClassLoader().loadClass("org.springframework.util.CollectionUtils");
    }

    public static void printClassPath(Class<?> klass) {
        CodeSource codeSource = klass.getProtectionDomain().getCodeSource();
        // classLoader 为 null 表示 其是bootstrapClassLoader
        log.info("classLoader is {}, {}'s codeSource is {}", klass.getClassLoader(), klass, codeSource);
    }

 

对于ClassLoader.java实际也是属于一个受jvm管理的一个类, 因此也是存在生命周期的;

   // The classes loaded by this class loader. The only purpose of this table
    // is to keep the classes from being GC'ed until the loader is GC'ed.
    private final ArrayList<Class<?>> classes = new ArrayList<>();

可以看到在 当前类中也存在一个classes字段表示当前classLoader所有加载的class对象,当前集合会保存当前数据从存储的class对象发生gc到当前classloader发生gc

posted @ 2020-10-19 15:17  郭星  阅读(565)  评论(0)    收藏  举报