《Java高并发编程详解-多线程架构与设计》JVM类加载器

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

总结

  1. 内置类加载器 bootstrap ClassLoader,Ext ClassLoader ,App ClassLoader。分别加载jre\lib,jre\lib\ext,-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获得跟加载器加载的资源
jie\lib

在这里插入图片描述

1.2 扩展类加载器 Ext ClassLoader

Java编写,URLClassLoader的子类
用于加载 JAVA_HOME下的jre\lib\ext 里面的类库

java.ext.dirs可以获得扩展类加载器加载的类
jre\lib\ext

在这里插入图片描述

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

在这里插入图片描述

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
    在这里插入图片描述
    在这里插入图片描述在这里插入图片描述
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
├─第一阶段 │      源码+ppt.rar │      并发编程第一阶段01讲、课程大纲及主要内容介绍.wmv │      并发编程第一阶段02讲、简单介绍什么是线程.wmv │      并发编程第一阶段03讲、创建并启动线程.mp4 │      并发编程第一阶段04讲、线程生命周期以及start方法源码剖析.mp4 │      并发编程第一阶段05讲、采用多线程方式模拟银行排队叫号.mp4 │      并发编程第一阶段06讲、用Runnable接口将线程的逻辑执行单元从控制中抽取出来.mp4 │      并发编程第一阶段07讲、策略模式在Thread和Runnable中的应用分析.mp4 │      并发编程第一阶段08讲、构造Thread对象你也许不知道的几件事.mp4 │      并发编程第一阶段09讲、多线程JVM内存结构的关系,虚拟机栈实验.mp4 │      并发编程第一阶段10讲、Thread构造函数StackSize详细讲解.mp4 │      并发编程第一阶段11讲、Thread构造函数StackSize详细讲解-续.mp4 │      并发编程第一阶段12讲、Daemon线程的创建以及使用场景分析.mp4 │      并发编程第一阶段13讲、线程ID,优先级讲解.mp4 │      并发编程第一阶段14讲、Thread的join方法详细介绍,结合一个典型案例.mp4 │      并发编程第一阶段15讲、Thread中断Interrupt方法详细讲解.mp4 │      并发编程第一阶段16讲、采用优雅的方式结束线程生命周期.mp4 │      并发编程第一阶段17讲、Thread API综合实战,编写ThreadService实现暴力结束线程的综合实战.mp4 │      并发编程第一阶段18讲、数据同步的引入与Synchronized的简单介绍.mp4 │      并发编程第一阶段19讲、结合jconsole,jstack以及汇编指令认识synchronized关键字.mp4 │      并发编程第一阶段20讲、同步代码块以及同步方法之间的区别和关系.mp4 │      并发编程第一阶段21讲、通过实验分析This锁的存在.mp4 │      并发编程第一阶段22讲、通过实验分析Class锁的存在.mp4 │      并发编程第一阶段23讲、多线程死锁分析,案例介绍.mp4 │      并发编程第一阶段24讲、线程间通信快速入门,使用wait和notify进行线程间的数据通信.mp4 │      并发编程第一阶段25讲、多Produce多Consume之间的通讯导致出现程序假死的原因分析.mp4 │      并发编程第一阶段26讲、多线程下的生产者消费者模型,以及详细介绍notifyAll方法.mp4 │      并发编程第一阶段27讲、wait和sleep的本质区别是什么,深入分析(面试常见问题).mp4 │      并发编程第一阶段28讲、线程生产者消费者的综合实战结合Java8语法.mp4 │      并发编程第一阶段29讲、如何实现一个自己的显式锁Lock精讲上.mp4 │      并发编程第一阶段30讲、如何实现一个自己的显式锁Lock精讲下(让锁具备超时功能).mp4 │      并发编程第一阶段31讲、如何给你的应用程序注入钩子程序,Linux下演示.mp4 │      并发编程第一阶段32讲、如何捕获线程运行期间的异常.mp4 │      并发编程第一阶段33讲、ThreadGroup API介绍之一.mp4 │      并发编程第一阶段34讲、ThreadGroup API介绍之二.mp4 │      并发编程第一阶段35讲、线程池原理与自定义线程池.mp4 │      并发编程第一阶段36讲、自定义个简单的线程池并且测试.mp4 │      并发编程第一阶段37讲、给线程池增加拒绝策略以及停止方法.mp4 │      并发编程第一阶段38讲、给线程池增加自动扩充线程数量,以及闲时自动回收的功能.mp4 │      并发编程第一阶段39讲、课程结束,内容回顾,下季内容预告.mp4 │ ├─第二阶段 │       Java并发编程.png │       ppt+源码.rar │       并发编程第二阶段01讲、课程大纲及主要内容介绍.wmv │       并发编程第二阶段02讲、介绍四种Singleton方式的优缺点在多线程情况下.wmv │       并发编程第二阶段03讲、介绍三种效优雅的Singleto

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值