学习任何一门技术或者原理都需要明白一个公式4W+1H
What(是什么)?
ClassLoader是用来加载Class到java虚拟机中的一种类加载器,负责将Class的字节码形式转换成内存形式的Class对象,字节码可以来自于磁盘文件 *.class,也可以是 jar 包里的 *.class,也可以来自远程服务器提供的字节流,其实字节码的本质就是一个字节数组byte[]啦。但是呢与普通程序不同的是java程序并不是本地可以执行的程序,所以运行java程序的时候,需要先运行JVM(java虚拟机),然后再把java class文件加载到JVM中运行,负责加载java class的这部分就叫做ClassLoader。

classloader json 中文乱码 java java中classloader使用_JVM

图1

Where(在哪里用)?

首先我们要知道,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 的子类,它们都是从本地文件系统里加载类库。

classloader json 中文乱码 java java中classloader使用_JVM_02

图2

Why(为什么会有这么多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 包。

classloader json 中文乱码 java java中classloader使用_加载_03

图3

How(怎么去使用)?

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方法,要不然会导致加载不到核心类库

classloader json 中文乱码 java java中classloader使用_JVM_04

3.对比Class.forName 和ClassLoader.loadClass :Class.forName() 方法可以获取原生类型的 Class,而 ClassLoader.loadClass() 则会报错。

总的来讲,ClassLloader相当于类的命名空间,起到了类的隔离作用,位于同一个 ClassLoader 里面的类名是唯一的,不同的 ClassLoader 可以持有同名的类。ClassLoader 是类名称的容器,是类的沙箱。具体是怎么隔离的需要涉及到源码分析,但是概念先搞懂,看懂源码就不远了。