zoukankan      html  css  js  c++  java
  • 《Java高并发编程详解-多线程架构与设计》JVM类加载器

    摘自《Java高并发编程详解-多线程架构与设计》第九章 p158-p176

    总结

    1. 内置类加载器 bootstrap ClassLoader,Ext ClassLoader ,App ClassLoader。分别加载jrelib,jrelibext,-cp或者-classpath对应的classpath

    2. 通过继承ClassLoader重写findClass特殊目录来实现自定义类加载器。特殊目录或者设置父加载器为空,让loadClass时跳过父类加载器(ps,如父类已经加载同名类,父加载器非空则会返回cache)。重写loadClass可以完全绕过双亲委托。

    3. class的实例是被类加载器的【实例】隔离。(当然不同的类加载器类型也会隔离) 因此代码中尽量得到同一个classLoader的实例,避免拿不到缓存,反复 findClass+defineClass。可以设置线程上下文类加载器。

    4. 在不指定parent的情况下,自定义ClassLoader的parent AppClassLoader是指的应用中唯一的AppClassLoader。因此加载classpath下已被加载过的类时,会因调用ClassLoader#getSystemClassLoader()对应AppClassLoader实例去调用findLoadedClass去获取Class缓存
      在这里插入图片描述

    5. 注意URLClassLoader#newInstance会调用到父类的ClassLoader()方法,导致传入系统的类加载器作为parent。而new URLClassLoader(url,null),会使得无parent类加载器。

    ps:loadClass来观察,而不是使用Class.forName来观察。Class.forName在loadClass之前应该还有一次获取缓存的机会。

    1.内置三大类加载器

    在这里插入图片描述

    在这里插入图片描述

    1.1 根加载器 Boostrap ClassLoader

    C++编写
    -Xbootclasspath指定根加载器的路径。

    sun.boot.class.path获得跟加载器加载的资源
    jielib

    在这里插入图片描述

    1.2 扩展类加载器 Ext ClassLoader

    Java编写,URLClassLoader的子类
    用于加载 JAVA_HOME下的jrelibext 里面的类库

    java.ext.dirs可以获得扩展类加载器加载的类
    jrelibext

    在这里插入图片描述

    也可以将自己的类放到扩展类加载器的位置

    在这里插入图片描述

    1.3 系统类加载器 App ClassLoader

    负责加载 -cp/-classpath 指定的类库资源

    在这里插入图片描述

    2.自定义类加载器

    要点

    1. 自定义的类加载器都是ClassLoader的直接或间接在子类

    2. 需要重写抽象ClassLoader的 findClass方法。

    3. 需要指定一个父类类加载器。若不指定则绕过了双亲委派

    4. 需要自定义一个路径加载特殊的class,该目录不能为已经有类加载器使用过的目录。
      可以使用loadClass打破双亲委派后,再使用任意路径。(相同的目录导致被委托给了父类类加载器加载)

    5. 得到类的二进制数据(无论网络/本地读取或动态代理/cglib生存),使用defineClass将其变成Class

    在这里插入图片描述

    案例

    /**
     * @auth thewindkee
     * @date 2018/12/15 0015 21:58
     */
    public class MyClassLoader extends ClassLoader {
        //public static final String LIB_PATH = "C:\Users\gkwind\Desktop";
        // 默认加载的位置
        public static final String LIB_PATH = "E:\gkrep\gitee\test\other\src\main\java\com\gkwind\ClassLoader\demo\";
        /*
         * !不要定义在bootstrap,ext,app类加载器能加载的位置。
         * 因为双亲委派的关系,会导致该类加载器失效。
         * 如下定义必须修改loadClass打破双亲委派
         */
        //public static final String LIB_PATH = MyClassLoader.class.getResource("/").getPath();
        public MyClassLoader() {
            //设置为null可以绕过双亲委派
            //super(null);
        }
    
        public MyClassLoader(ClassLoader parent) {
            super(parent);
        }
    
    
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            try {
                //这里查找多层
                byte[] bytes = getClassBytes(name);
                //defineClass方法可以把二进制流字节组成的文件转换为一个java.lang.Class
                Class<?> c = this.defineClass(name, bytes, 0, bytes.length);
                System.out.println(c);
                return c;
            } catch (Exception e) {
                e.printStackTrace();
            }
    
            return super.findClass(name);
        }
    
        private byte[] getClassBytes(String name) throws Exception {
            // 这里要读入.class的字节,因此要使用字节流
            String resolvedPath = name.replace(".", "/");
            File file = new File(LIB_PATH + resolvedPath + ".class");
            System.out.println(MyClassLoader.class.getName() + "findClass:" + file);
            ByteArrayOutputStream baos = new ByteArrayOutputStream();
            //FileInputStream fis = new FileInputStream(file);
            //FileChannel fc = fis.getChannel();
            //WritableByteChannel wbc = Channels.newChannel(baos);
            //ByteBuffer by = ByteBuffer.allocate(1024);
            //while (true) {
            //    int i = fc.read(by);
            //    if (i == 0 || i == -1)
            //        break;
            //    by.flip();
            //    wbc.write(by);
            //    by.clear();
            //}
            //fis.close();
            //wbc.close();
            java.nio.file.Files.copy(file.toPath(), baos);
            return baos.toByteArray();
        }
    } 
    

    在这里插入图片描述

    自定义的类加载器指定特殊目录或者指定父类加载器为空,避免双亲委派导致该类加载器失效

    classpath 不含有Demo.class
    在这里插入图片描述

    选择父类ClassLoader未加载的目录

    在这里插入图片描述

    在这里插入图片描述

    2.2 双亲委托机制

    为保证基础类不被破坏。加载类的时候优先祖师爷(递归父类)过目(加载)。 ----by 极客时间 jvm

    loadClass 规定了双亲委托,所以可以直接重写loadClass打破双亲委托。
    该方法是同步方法 – synchronized (getClassLoadingLock(name))
    优先返回已经加载过的同名class --findLoadedClass(name)
    loadClass(name,false)
    false 指的是不做【连接(linked)阶段】的操作。这就是为什么加载类,导致类的初始化(【准备阶段】)。

    在这里插入图片描述
    在这里插入图片描述

    ★3.绕过双亲委托

    不需要删除class.
    1,2都是需要父类未加载过同名类,否则返回cache。3可以重复加载同名类

    1. 绕过系统类加载器,直接使用ext类加载器作为父类,并传入特殊的目录的class
      无cache且父类加载不到特殊目录,自动到子类去加载。

    2. 设置父类类加载器为null
      无cache且没有父类,自动到子类去加载

    3. 重写loadClass
      loadClass中不再请求父类去加载
      在这里插入图片描述

    在这里插入图片描述

    4.类加载命名空间、运行时包、类的卸载

    ★类加载器命名空间

    类加载器作为一个命名空间,可以隔离class
    使用不同类加载器/或者同一加载器的不同实例去加载同一个类,会产生多个实例。
    简单来说,class实例被不同的classLoader实例隔离。

    如:★测试类加载器隔离同名类-不同的类加载器实现隔离;在这里插入图片描述

    使用loadClass来观察 获取缓存
    在这里插入图片描述

    在这里插入图片描述

    运行时包

    classloader名+全路径列明

    初始类加载器

    一个类的初始类加载器,包含尝试过加载的所有父类

    在这里插入图片描述

    ★测试类加载器隔离同名类-不同的类加载器实现隔离

    此处使用了不同的类加载器 去隔离。
    Demo.java

    不同的位置的Demo.java输出的内容不同

    MyClassLoader对应的java文件
    在这里插入图片描述

    MyClassLoader2对应的另一个class
    在这里插入图片描述

    demo1与demo2中的class 放置的位置
    在这里插入图片描述

    MyClassLoader.java
    注意加载class的位置不同!在这里插入图片描述

    MyClassLoader2.java
    在这里插入图片描述

    测试类
    两个com.gkwind.Demo都被加载成功

    在这里插入图片描述

    类加载器的实例隔离Class对象

    验证:【同一类加载器的不同实例 产生不同Class 实例】
    在这里插入图片描述

    编写代码证明

    MyClassLoader 继承ClassLoader
    在这里插入图片描述
    已知
    1.Demo.class是自己编译的Class,不存在于
    lib,lib/ext,classpath.在目录X下
    2.A类对应A.class编译在classpath下。
    3.MyClassLoader继承了ClassLoader,自定义了 findClass的目录X(App,Ext类加载器无法加载X目录),没有重写loadClass,更没有打破双亲委派。

    问题:为何MyClassLoader加载Demo的时候,无法通过findLoadedClass获取Class的Cache?
    在这里插入图片描述在这里插入图片描述

    加载A类,注意看两次new MyClassLoader的parent都是同一个实例。

    演示时,造成A类与Demo类不一样的原因是:classpath对应的AppClassLoader 全局唯一

    • new MyClassLoader().loadClass(“A”)多次,
      委托parent->唯一的AppClassLoader去加载,第一次findClass,第二次findLoadedClass

    • new MyClassLoader().loadClass(“Demo”)多次,由于Demo.class不存在classpath对应的目录,导致由new MyClassLoader()实例去加载,每次都是新的ClassLoader实例,因此无法从findLoadedClass中获得缓存的Demo.class

    结论:

    1. 尽量保持ClassLoader的唯一,避免不同ClassLoader实例重复加载Class。
    2. 可以通过线程去传递ClassLoader。
    3. 【同一类加载器的不同实例 产生不同Class 实例】 正确。—由加载Demo类可以看出。
    4. 继承ClassLoader的自定义类加载器默认会调用super()传入默认的AppClassLoader作为parent。super()传入唯一的AppClassLoader
      在这里插入图片描述
      在这里插入图片描述在这里插入图片描述
  • 相关阅读:
    【SqlServer系列】表达式(expression)
    【SqlServer系列】语法定义符号解析
    Docker常用命令<转>
    VMware下的Centos7联网并设置固定IP
    redis的setbit命令
    Java并发编程:并发容器之CopyOnWriteArrayList<转>
    Java并发编程:volatile关键字解析<转>
    Java并发编程:阻塞队列 <转>
    Java并发编程:Lock和Synchronized <转>
    Jackson学习笔记(三)<转>
  • 原文地址:https://www.cnblogs.com/thewindkee/p/12873126.html
Copyright © 2011-2022 走看看