Java类加载器机制-双亲委派模型详细的答疑

概要

这两天看了大量关于java类加载器的文章,有很多疑问一只困扰着我,今天折腾了半天,又通过自己写了一个自定义加载器,总算基本搞清楚了。在这里总结一下,若有错误的地方,还希望各位指正。

为什么java需要多个类加载器?

  • 保证同一个类不同版本同时存在。例如tomcat中两个应用用到了同一个class,但是版本不同,此时使用不同的加载器就可以保证两个不同版本的类同时存在了;
  • 可以对类进行增强。例如aop中,我们通过cglib对类A进行增强,但又不能影响其他类对A的使用。这时候我们可以使用另一个加载器来加载增强后的类A;
  • 保证安全。类中的方法和属性要具有包访问权限的前提是,必须是由同一个类加载器加载的,比如lang包下的类都由bootstrap加载,但bootstrap不会加载rt.jar以外的class文件,就避免了其他文件对lang包下包访问权限的属性的更改了。
    • jdk中默认的三种加载器代表了三种不同的信任级别。最可信的级别是java核心API类。然后是安装的拓展类,最后才是在类路径中的类;

加载器如何实现Class对象的隔离?

  • 首先有一点要说明:所谓的不同加载器是指不同的加载器实例对象,不是指不同的加载器类。例如:AppClassLoader加载器有两个实例对象loader1和loader2,那么这两个实例对象加载的Class文件也是互相隔离;
  • 可以这样理解:每个加载器的实例在方法区中都有自己的一个唯一的命名空间,它加载的所有class文件的Class对象都存放在该命名空间中。通过该命名空间就实现了不同加载器实例对同一个calss文件加载所生成的Class对象的隔离。
  • 为什么父类加载器加载的类无法访问子类加载器加载的类?
    • 首先了解一下jvm如何去寻找一个Class对象,根据下面一段代码来说明:Java类加载器机制-双亲委派模型详细的答疑_第1张图片
    • 首先由bootstrap加载基础类,由ExtClassLoader加载扩展类,由AppClassLoader加载应用类,这里的应用类也就是Main类;
      • 注意ExtClassLoader、AppClassLoader均由java自己实现的加载器,它们都继承了ClassLoader,这是一个抽象类,实现了加载器的基本功能。我们自定义的加载都需要继承ClassLoader;
    • 接着jvm对Main.class进行解析,此时发现需要ManuallyLoading.class,这时候开始加载ManuallyLoading.class;
    • jvm会获取加载Main.class的加载器(因为Main.class是ManuallyLoading.class的调用者)AppClassLoader,使用此加载器去加载ManuallyLoading.class;
    • AppClassLoader加载ManuallyLoading.class时,会首先去AppClassLoader的命名空间中寻找该类是否已经被加载,若已经被加载,则直接返回。没有,则调用父类加载器进行加载。若父类加载器没有加载到该类,则AppClassLoader才会尝试自己去类路径下加载;下面是ClassLoader中加载类的实现:Java类加载器机制-双亲委派模型详细的答疑_第2张图片
    • 通过上面的了解,我们应该知道,在A类中访问B类时(例如:new B();),jvm会使用加载A的加载器来加载B类。换句话说,A类所需要的Class对象,由加载A类的加载器及其父加载器来提供!这是jvm查找Class对象的规则!

什么是打破双亲委派模型?为什么需要打破双亲委派模型?

  • 什么是双亲委派模型? 网上虽然有很多博客讲这个,但很多都忽略或者没有说清楚它的一个思想:一个类所需要的Class对象,应由该类的加载器或父加载器来提供! 所谓的打破双亲委派模型,就是没有遵守这个设计思想;
  • 为什么需要打破双亲委派模型? 因为我们有时候需要 一个类去访问它的子类加载器所加载的类。举一个例子:
    • 有两个web应用,它们都是通过Spring来管理bean,这时候Spring的jar包放在commons目录下,由tomcat的CommonClassLoader来加载。而各自应用的类都放在/WebApp/WEB-INF/下,它们由各自的WebappClassLoader实例来加载,WebappClassLoader是CommonClassLoader的子类加载器;
    • 在启动应用的时候,上面提到的所有的类都由各自的类加载器加载到了方法区的各自的命名空间中,由于Spring的jar包是由CommonClassLoader来加载的,所以当Spring要获取应用中某一个Class对象时是无法通过CommonClassLoader加载器来获取的,因为应用的jar包是由WebappClassLoader来加载的;
    • 当然,有人可能会说,既然无法获取,那我就自己用CommonClassLoader来加载呗。是可以自己加载,但会出现两个问题:同一个class文件在虚拟机中出现了两个Class对象;没有与另一个应用的Class隔离开,因为CommonClassLoader加载的类,其子类加载器加载的类也可以访问;
    • 所以,此时就出现了打破双亲委派模型的需求!
  • 怎么打破双亲委派模型?
    • 上面我们找到了需求,那怎么来实现呢?其实很简单:哪个加载器加载的类,就由这个加载器来查找它加载的Class对象呗!
    • 具体到上面的例子中,现在唯一的问题就是:怎么告诉Spring它需要的类是由哪个加载器来提供的呢?为了解决这个问题JDK在Thread类中增加了一个属性:contextClassLoader在这里插入图片描述
    • 这个属性我们称为上下文加载器,它的存在就是为了告诉父加载器:你需要的类是由哪个加载器来加载的,你可以通过Thread对象来获取这个加载器,然后通过这个加载器来寻找你需要的Class对象;
    • 在上面的例子中,应用程序的jar包是由WebappClassLoader来加载的,所以tomcat会把上下文加载器设置为WebappClassLoader的实例,这样当Spring需要应用程序中的某一个Class时,就可以通过该加载器和类的限定名称来获取;
  • 简单介绍一下上下文加载器
    • jvm在创建一个Thread对象的时候就会设置上下文加载器,设置的值来源于父线程的上下文加载器。
      若没有父线程或父线程的上下文加载器为null,则默认为AppClassLoader;

    • 我们可以通过Thread的set方法手动设置自己需要的上下文加载器。Java类加载器机制-双亲委派模型详细的答疑_第3张图片

    • 每个线程的上下文加载器都可以不同;

    • tomcat正是通过多个线程对应多个不同的上下文加载器来完成对多个应用的Class加载,同时保证了隔离性;

Class.forName于ClassLoader的loadClass方法的不同

  • forName是Class类的静态方法,可以通过指定加载器来加载指定的对象,同时对加载的Class对象进行初始化(调用静态代码块和对静态变量赋值);该方法最终调用本地方法forName0来完成加载;在这里插入图片描述
  • loadClass是java自己实现的类加载器的核心方法,通过它加载的类,不会进行初始化;loadClass方法中,寻找该加载器所加载的Class的方法是通过本地方法findLoadedClass0来完成的。Java类加载器机制-双亲委派模型详细的答疑_第4张图片

你可能感兴趣的:(jdk)