类加载器详解
回顾一下类加载过程
开始介绍类加载器和双亲委派模型前,简单回顾一下类加载的过程
- 类加载过程:加载->连接->初始化。
- 连接过程又可分为三步:验证->准备->解析。
加载是类加载过程的第一步,主要完成下面三件事:
- 通过全类名获取定义此类的二进制字节流
- 将字节流锁代表的静态存储结构转化为方法去的运行时数据结构
- 再内存中生成一个代表该类的
Class
对象,作为犯法去这些数据的访问入口。
类加载器
类加载器介绍
类加载器从 JDK 1.0 就出席那了,最初芝士为了满足 Java Applet (已经被淘汰)的需要。后来慢慢成为 Java 程序中的一个重要组成部门,赋予了 Java 类可以被动态加载到 JVM 中并执行的能力。
根据官方 API 文档的介绍:
A class loader is an object that is responsible for loading classes. The class ClassLoader is an abstract class. Given the binary name of a class, a class loader should attempt to locate or generate data that constitutes a definition for the class. A typical strategy is to transform the name into a file name and then read a “class file” of that name from a file system.
Every Class object contains a reference to the ClassLoader that defined it.
Class objects for array classes are not created by class loaders, but are created automatically as required by the Java runtime. The class loader for an array class, as returned by Class.getClassLoader() is the same as the class loader for its element type; if the element type is a primitive type, then the array class has no class loader.
翻译过来的大致意思是
类加载器是一个负责加载类的对象。ClassLoader
是一个抽象类。给定类的二进制名称,类加载器应尝试定位或生成构成类定义的数据。典型的策略是将名称转化为文件名,然后从文件系统中读取该名称的 ”类文件“ 。
每个 Java 类都有一个引用指向加载它的 ClassLoader
。不过,数组类不是通过 ClassLoader
创建的,而是JVM 再需要的时候自动创建的,数组类通过 getClassLoader()
方法获取 ClassLoader
的时候和该数组元素的 ClassLoader
是一致的
从上面的介绍可以看出:
- 类加载器是一个负责加载类的对象,它用于实现类加载过程中的加载这一步
- 每个 Java 类都有一个引用指向加载它的
ClassLoader
- 数组类不是通过
ClassLoader
实现的,是由 JVM 直接实现的
简单来说,**类加载器的主要作用就是加载 Java 类的字节码( .class
文件)到 JVM 中 (在内存中生成一个代表该类的 Class
对象)。 ** 字节码可以是 Java 源程序 ( .java
文件) 经过 javac
编译得来,也可以是通过工具动态生成或者通过网络下来得来
其实除了加载类之外,类加载还可以加载 Java 应用所需的资源如文本、图像、配置文件、视频等等文件资源
类加载器加载规则
JVM 启动的时候,并不会一次加载所有的类,而是根据需要去动态加载。也就是说,大部分类在具体用到的时候才回去加载,这样对内存更友好。
对于已经加载的类会被放在 ClassLoader
中。 在类加载的时候,系统会先首先判断当前类是否被加载过。已经被加载的类回直接返回,否则才会尝试加载。也就是说,对于一个类加载器来说,相同二进制名称的类只会被加载一次
类加载器总结
JVM 中内置了三个重要的 ClassLoader
BootstrapClassLoader
( 启动类加载器 ):最顶层的加载器,有 C++实现,通常表现为 null,并且没有父级,主要用来加载 JDK 内部的核心类库(%JAVA_HOME%/lib
目录下的rt.jar
、resources.jar
、charsets.jar
等 jar 包和类)以及被-Xbootclasspath
参数指定的路径下的所有类。ExtensionClassLoader
(扩展类加载器):主要负责加载%JRE_HOME%/lib/ext
目录下的 jar 包和类以及被java.ext.dirs
系统变量锁指定的路径下的类。AppClassLoader
(应用程序类加载器):面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类
除了这三种类加载器之外,用户还可以加入自定义的类加载器来进行扩展,以满足自己的特殊需求。就比如,我们可以对 Java 类的字节码( .class
文件)进行加密,加载时再利用自定义的类加载器对其解密。
除了 `BootstrapClassLoader` 是 JVM 自身的一部分之外,其他所有的类加载器都是在 JVM 外部实现的,并且全部继承自 `ClassLoader` 抽象类。这样做的好处是用户可以自定义类加载器,以便让应用程序自己去决定如何去获取所需的类。
每个 ClassLoader
可以通过 getParent()
获取其父 ClassLoader
,如果获取到的父类加载器为 null 的话,那么该类是通过启动类加载器直接加载的。
自定义类加载器
我们前面也说说了,除了 BootstrapClassLoader
其他类加载器均由 Java 实现且全部继承自java.lang.ClassLoader
。如果我们要自定义自己的类加载器,很明显需要继承 ClassLoader
抽象类。
ClassLoader
类有两个关键的方法:
protected Class loadClass(String name, boolean resolve)
:加载指定二进制名称的类,实现了双亲委派机制 。name
为类的二进制名称,resolve
如果为 true,在加载时调用resolveClass(Class<?> c)
方法解析该类。protected Class findClass(String name)
:根据类的二进制名称来查找类,默认实现是空方法。
官方 API 文档中写到:
Subclasses of
ClassLoader
are encouraged to overridefindClass(String name)
, rather than this method.建议
ClassLoader
的子类重写findClass(String name)
方法而不是loadClass(String name, boolean resolve)
方法。
的子类重写
findClass(String name)方法而不是
loadClass(String name, boolean resolve)` 方法。
如果我们不想打破双亲委派模型,就重写 ClassLoader
类中的 findClass()
方法即可,无法被父类加载器加载的类最终会通过这个方法被加载。但是,如果想打破双亲委派模型则需要重写 loadClass()
方法。