ClassLoader工作机制

时间:2022-07-25
本文章向大家介绍ClassLoader工作机制,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

classLoader顾名思义就是类加载器。 负责将类加载到JVM中,它就好比开会时门口的接待员,负责给进入会场的嘉宾发放入证明,入会的嘉宾分为VIP会员,黄金会员,白金会员,和普通会员等。都赢的接待室也会分为VIP会员接待室,黄金会员接待室,白金会员接待室和普通会员接待室,不同等级的会员会被分到不同的接待室接待,所有的会员都想进入会场得有入会证明才行,一旦会员进入会场就会根据接待室的等级表示它们,也就是会员的身份由接待室决定。如果你是一位大佬但是你不是VIP接待室接待的,那么对不起,你仍然不是VIP会员。当然对你是不是VIP会有会有严格的审查规定,如果你是也不会冤枉你,但是如果你想还能进来那就另当别论了。 事实上,ClassLoader除了能将Class加载到JVM中之外,还有一个重要的作用就是审查每个类应该由谁加载,它是一种父优先的等级加载机制。ClassLoader除了上述两个作用外还有一个任务就是将Class字节码重新解析成JVM统一要求的对象格式。 所以,

ClassLoader的作用

  • 1.加载类
  • 2.审查每个类应该由谁加载
  • 3.将Class字节码重新解析成JVM统一要求的对象格式

ClassLoader类结构分析

首先ClassLoader是抽象类,下面罗列几个常用的 该类的方法

   protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
  • defineClass方法,用于将byte字节流解析为JVM能够识别的Class对象,有了这个方法意味着我们不仅仅可以通过class文件实例化对象,还可以通过其他方式实例化对象,如我们通过网络接受到一个类的字节码,那这个字节码流直接创建类的class对象形式实例化对象。
protected Class<?> findClass(String name) throws ClassNotFoundException 

findClass方法通常是和defineClass方式一起使用的,我们通过直接覆盖ClassLoader的findClass方法来实现类的加载规则,从而取得加载类的字节码,然后通过defineClass方法生成类的Class对象。 如果你不想重新定义加载类的规则,也没有复杂的处理逻辑,只想在运行时能够加载自己指定的一个类,那么你可以用 this.getClass().getClassLoader().loadClass("class")调用ClassLoader的loadClass方法获取这个类的Class对象。

public Class<?> loadClass(String name) throws ClassNotFoundException
protected final void resolveClass(Class<?> c) 

ClassLoader还有很多子类,我们如果要实现自己的ClassLoader,一般都会继承URLClassLoader这个子类,因为这个类已经帮我们实现了大部分工作,我们只需要在适当的地方修改就好了,就像我们要实现servlet时通常会直接继承HttpServlet一样。

ClassLoader的等级加载机制

在前面的会员进入会场的规则中,如何保证不同等级的会员通过不同的会员接待室进入会场呢? 因为有些会员自己并不能正确地找到接待自己的接待室,也有可能有些会员会冒充更高阶的会员身份混进去,所以必须要有机制能够保证所有会员都被正确的接待室接待进入会场,而且一个会员只能别一个接待室接待,不能出现两个接待室重复接待的情况,也就是不能同时拿到两个入场的证明,从而保证接待的一致性。如何设计这个接待规则呢?

ClassLoader就设计了这样一种接待机制,这个机制就是上级委托接待机制。 它是这样的: 任何一个会员到达任何一个会员接待室时,这个接待室首先会检查这个会员是否已经被自己接待过,如果已经接待过,则拒绝本次接待,也就是不再发入会证明了,如果没有接待过,那么会向上询问这个会员是否应该在上一级的更高级别的接待室接待,上级接待室会根据它们的接地规则,检查这个会员是否已经被接待过,如果已经接待过,同样的处理方法,将已经接待的结果反馈给下一级,如果也没有接待过,再向更高一级(如果有更高一级的话)接待室转发接待请求,更高一级也是同样的处理方法,直到有一级接待室接待或者告诉它下一级这个会员不是自己接待的结果,并且也不应该由他们接待,这个接待室将会正式接待这个会员,并发个它入会证明,这个会员就被定位为这个接待室等级的会员。

这种接待规则看上去有点麻烦,但是它却能保证所有的会员都被正确的接待室接待,会员的身份也不会错。也不存在冒充身份的会员。

这个JVM平台提供三层ClassLoader,这三层ClassLoader可以分为两种类型,可以理解为接待室服务的接待室和为会员服务的接待室。

(1)BootStrap ClassLoader

这个ClassLoader就是接待室服务自身的,它主要加载JVM自身工作需要的类。这个ClassLoader完全是由JVM自己控制的,需要加载哪个类,怎么加载都由JVM自己控制,别人也访问不到这个类,所以这儿ClassLoader是不遵守前面介绍的加载机制的,它仅仅是一个类的加载工具而已,既没有更高以及的父加载器,也没有子加载器。

(2)ExtClassLoader

这个类加载有点特殊,它是JVM自身的一部分,但是它的血统也不是很纯正,它并不是JVM亲自实现的,我们可以理解为这个类加载器是那些与这个大会合作的单位员工会员,这些会员既不是JVM内部的,也和普通的外部会员不同,所以就有这个类加载器来加载。它服务的特定目标在 System.getProperty("java.ext.dirs"); sysout出来后为:

C:Program FilesJavajdk1.8.0_111jrelibext;C:WINDOWSSunJavalibext

我们打开目录看下,

(3)AppClassLoader

这个类加载器就是专门为接待会员服务的,它的父类是ExtClassLoader。它服务的目标是广大普通会员,所有在 System.getProperty("java.class.path")目录下的类都可以被这个类加载,这个目录就是我们经常用到的classpath sysout后:

E:workspaceTestbin;C:Program FilesJavajdk1.8.0_111jrelibcharsets.jar;C:Program 
FilesJavajdk1.8.0_111jrelibdeploy.jar;C:Program FilesJavajdk1.8.0_111jrelibextaccess-
bridge-64.jar;C:Program FilesJavajdk1.8.0_111jrelibextcldrdata.jar;C:Program 

FilesJavajdk1.8.0_111jrelibextdnsns.jar;C:Program 
FilesJavajdk1.8.0_111jrelibextjaccess.jar;C:Program FilesJavajdk1.8.0_111jrelibextjfxrt.jar;C:Program 
FilesJavajdk1.8.0_111jrelibextlocaledata.jar;C:Program 
FilesJavajdk1.8.0_111jrelibextnashorn.jar;C:Program 
FilesJavajdk1.8.0_111jrelibextsunec.jar;C:Program 
FilesJavajdk1.8.0_111jrelibextsunjce_provider.jar;C:Program 
FilesJavajdk1.8.0_111jrelibextsunmscapi.jar;C:Program 
FilesJavajdk1.8.0_111jrelibextsunpkcs11.jar;C:Program 
FilesJavajdk1.8.0_111jrelibextzipfs.jar;C:Program 
FilesJavajdk1.8.0_111jrelibjavaws.jar;C:Program 
FilesJavajdk1.8.0_111jrelibjce.jar;C:Program 
FilesJavajdk1.8.0_111jrelibjfr.jar;C:Program 
FilesJavajdk1.8.0_111jrelibjfxswt.jar;C:Program 
FilesJavajdk1.8.0_111jrelibjsse.jar;C:Program FilesJavajdk1.8.0_111jrelibmanagement-
agent.jar;C:Program FilesJavajdk1.8.0_111jrelibplugin.jar;C:Program 
FilesJavajdk1.8.0_111jrelibresources.jar;C:Program 
FilesJavajdk1.8.0_111jrelibrt.jar;D:IntelliJ IDEA Community Edition 2017.3libidea_rt.jar

如果我们要实现自己的类加载器,不管你是直接实现抽象类ClassLoader,还是继承URLClassLoader,或者其他子类,它的父类加载器都是AppClassLoader,因为不管调用那个父类构造器,创建的对象都必须最终调用ClassLoader.getSystemClassLoader()作为父加载器。而ClassLoader.getSystemClassLoader()方法获取到的正是AppClassLoader。 通常一个类加载器的等级结构如下所示:

ExtClassLoader和AppClassLoader都位于sun.misc.Launcher类中,它们都是Launcher的内部类

ExtClassLoader和AppClassLoader都继承了URLClassLoader类,而URLClassLoader又实现了抽象类ClassLoader,在创建Launcher 对象时,首先会创建ExtClassLoader,然后将ExtClassLoader对象作为父加载器创建AppClassLoader对象,而通过Launcher.getClassLoader()方法获取的ClassLoader就是AppClassLoader对象。所以,如果在java应用中没有定义其他ClassLoader,那么除了 System.getProperty("java.ext.dirs");目录下的类都是由ExtClassLoader加载外,其他类都是由AppClassLoader来加载

JVM加载Class文件到内存中有两种方式

  • 隐式加载:所谓隐式加载就是不通过在代码里调用ClassLoader来加载需要的类,而是通过JVM来自动加载需要的类到内存中的方式。例如,当我们在类中继承或者引用某个类时,JVM在解析当前这个类时发现引用的类不在内存中,那么就会自动将这些类加载到内存中。
  • 显示加载:相反的显示加载就是我们在代码中通过调用ClassLoader类来加载一个类的方式,例如,调用this.getClass().getClassLoader().loadClass(); 或者 Class.forName() 或者我们自己实现的ClassLoader的findClass方法等。

其实,这两种方式时混合使用的,例如,我们通过自定义的ClassLoader显示加载一个类时,这个类中有引用了其他类,那么这些类就是隐式加载到。