学习任何一门技术或者原理都需要明白一个公式4W+1H
What(是什么)?
ClassLoader是用来加载Class到java虚拟机中的一种类加载器,负责将Class的字节码形式转换成内存形式的Class对象,字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流,其实字节码的本质就是一个字节数组byte[]啦。但是呢与普通程序不同的是java程序并不是本地可以执行的程序,所以运行java程序的时候,需要先运行JVM(java虚拟机),然后再把java class文件加载到JVM中运行,负责加载java class的这部分就叫做ClassLoader。
图1Where(在哪里用)?
首先我们要知道,JVM本身包含了一种ClassLoader叫做BootStrap ClassLoader,和JVM一样,这个类加载器是用本地代码实现的,所以我们可以叫它【根加载器】主要是负责加载核心java class(所有以java.*开头的类),这些类位于 $JAVA_HOME/lib/rt.jar 文件中,我们常用内置库 java.xxx.* 都在里面,比如 java.util.、java.nio.、java.lang. 等等。另外JVM还会提供另外两个类加载器Extension ClassLoader(扩展类加载器)和ApplicationClassLoader(应用程序类加载器);这两个都是用java代码实现的,并且由BootStrap ClassLoader负责加载,Extension ClassLoader负责加载扩展的java class(所有javax.*开头的类和存放在jre的ext目录下的类),比如 swing 系列、内置的 js 引擎、xml 解析器等等;ApplicationClassLoader负责加载应用程序自身的类,才是直接面向我们用户的加载器,它会加载 Classpath 环境变量里定义的路径中的 jar 包和目录,我们自己编写的代码以及使用的第三方 jar 包通常都是由它来加载的。其实流程说白了就是这样的,当运行一个程序的时候,JVM启动,运行BootStrap ClassLoader,该ClassLoader加载java核心API(ExtClassLoader和AppClassLoader也在此时被加载),然后调用ExtClassLoader加载扩展API,然后AppClassLoader加载CLASSPATH目录下定义的Class文件。
When(什么时候用)?
换句话说什么时候JVM会使用ClassLoader加载一个类呢?每个 Class 对象的内部都有一个 ClassLoader 字段来标识自己是由哪个 ClassLoader 加载的。ClassLoader 就像一个容器,里面装了很多已经加载的 Class 对象。当我们使用java去执行一个类时,JVM使用Application ClassLoader加载这个类,如果类A引用了类B,不管是直接引用还是用间接引用,JVM就会找到加载类A的ClassLoader,并用这个ClassLoader来加载类B,JVM按照运行时的有效执行语句,来决定是否需要装载新类,从而装载尽可能少的类。那些位于网络上静态文件服务器提供的 jar 包和 class文件,jdk 内置了一个 URLClassLoader,用户只需要传递规范的网络路径给构造器,就可以使用 URLClassLoader 来加载远程类库了。URLClassLoader 不但可以加载远程类库,还可以加载本地路径的类库,取决于构造器中不同的地址形式。ExtensionClassLoader 和 AppClassLoader 都是 URLClassLoader 的子类,它们都是从本地文件系统里加载类库。
图2Why(为什么会有这么多ClassLoader)?
1.延迟加载
首先,JVM是不会一次性加载所需要的全部类的,免得JVM内存爆满,性能下降。程序在运行的过程中会遇到很多不认识的新类,这时候就会调用ClassLoader来加载这些新类,加载完成后就会将Class对象存在ClassLoader中,下次使用就避免重新加载了,而ClassLoader这时候就类似于一个容器。
2.ClassLoader的传递性
当遇到一个未知的类时,是使用哪个ClassLoader去加载呢?JVM的策略就是使用调用者Class对象的ClassLoader来加载当前未知的类。那么何为调用者 Class 对象呢?就是在遇到这个未知的类时,虚拟机肯定正在运行一个方法调用(静态方法或者实例方法),这个方法挂在哪个类上面,那这个类就是调用者的 Class 对象。前面我们提到每个 Class 对象里面都有一个 ClassLoader 属性记录了当前的类是由谁来加载的。
3.双亲委派
我们知道AppClassLoader是只加载ClassPath下面的类库,那么当遇到没有加载的系统类库时该怎么办呢?AppClassLoader 必须将系统类库的加载工作交给 BootstrapClassLoader 和 ExtensionClassLoader 来做,这就是我们常说的「双亲委派模型」,也就是说AppClassLoader 在加载一个未知的类名时,它并不是立即去搜寻 Classpath,它会首先将这个类名称交给 ExtensionClassLoader 来加载,如果 ExtensionClassLoader 可以加载,那么 AppClassLoader 就不用麻烦了。否则的话它才会搜索 Classpath 。而 ExtensionClassLoader 在加载一个未知的类名时,它也并不是立即搜寻 ext 路径,它会首先将类名称交给 BootstrapClassLoader 来加载,如果 BootstrapClassLoader 可以加载,那么 ExtensionClassLoader 也就不用麻烦了。否则它就会搜索 ext 路径下的 jar 包。
图3How(怎么去使用)?
1.Class.forName()去加载驱动类,也是一种类加载器,用过jdbc的童鞋们都知道,获取mysql驱动的时候,mysql 驱动的 Driver 类里有一个静态代码块,它会在 Driver 类被加载的时候执行。这个静态代码块会将 mysql 驱动实例注册到全局的 jdbc 驱动管理器里,forName 方法同样也是使用调用者 Class 对象的 ClassLoader 来加载目标类。不过 forName 还提供了多参数版本,可以指定使用哪个 ClassLoader 来加载,
通过这种形式的 forName 方法可以突破内置加载器的限制,通过使用自定类加载器允许我们自由加载其它任意来源的类库。根据 ClassLoader 的传递性,目标类库传递引用到的其它类库也将会使用自定义加载器加载。
2.自定义类加载器:ClassLoader 里面有三个重要的方法 loadClass()、findClass() 和 defineClass()。用法是这样的过程:loadClass() 方法是加载目标类的入口,它首先会查找当前 ClassLoader 以及它的双亲里面是否已经加载了目标类,如果没有找到就会让双亲尝试加载,如果双亲都加载不了,就会调用 findClass() 让自定义加载器自己来加载目标类。ClassLoader 的 findClass() 方法是需要子类来覆盖的,不同的加载器将使用不同的逻辑来获取目标类的字节码。拿到这个字节码之后再调用 defineClass() 方法将字节码转换成 Class 对象。自定义类加载器不易破坏双亲规则,不要轻易的覆盖loadclass方法,要不然会导致加载不到核心类库
3.对比Class.forName 和ClassLoader.loadClass :Class.forName() 方法可以获取原生类型的 Class,而 ClassLoader.loadClass() 则会报错。
总的来讲,ClassLloader相当于类的命名空间,起到了类的隔离作用,位于同一个 ClassLoader 里面的类名是唯一的,不同的 ClassLoader 可以持有同名的类。ClassLoader 是类名称的容器,是类的沙箱。具体是怎么隔离的需要涉及到源码分析,但是概念先搞懂,看懂源码就不远了。
网友评论