一、类加载器分类
- 引导类加载器由于是C/C++语言编写的,因此获取到的是null
数组类的Class对象,不是由类加载器去创建的,而是在java运行期间JVM根据需要自动创建的,对于数组类的类加载器来说,是通过Class.getClassLoader()返回的,与数组当中的元素类型的类加载器是一样的;如果数组的元素类型是基本数据类型,数组类是没有类加载器的。
1. 引导类加载器
这个加载器是由C/C++语言实现的,嵌套在JVM内部
- 用来加载java的核心库(JAVA_HOME/jre/lib/rt.jar或sun.boot.class.path路径下的内容)。用于提供Java自身需要的类
- 并不继承ClassLoader,没有父加载器
出于安全考虑,Bootstrap启动类加载器只加载包名为java、Javax、sun等开头的类·
2. 扩展类加载器
java语言编写,由sun.misc.Launcher$ExtClassLoader实现
- 间接继承于ClassLoader类
- 父类加载器为启动类加载器
从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下加载类库。如果用户创建的JAR放在此目录下,也会自动由扩展类加载器加载
3. 应用类加载器
java语言编写,由sun.misc.Launcher$AppClassLoader实现
- 间接继承于ClassLoader类
- 父类加载器为扩展类加载器
- 它负责加载环境变量classpath或系统变量属性java.class.path 指定路径下的类库
- 应用程序中的类默认使用系统类加载器加载
- 它是用户自定义类加载器的默认父类加载器
通过ClassLoader的
getSystemClassLoader()
方法可以获取到该类加载器4. 自定义类加载器
通过类加载器可以实现非常绝妙的插件机制
- 自定义加载器能够实现应用隔离
- 自定义类加载器通常需要继承ClassLoader类
二、ClassLoader源码解析
1. 主要方法
Ⅰ loadClass()
- 加载名称为name的类,返回该类的实例。如果找不到类,则返回
ClassNotFoundException
- 该方法中的逻辑实现就是双亲委派机制的实现
Ⅱ
实现类加载的主要逻辑代码,会调用findClass()
defineClass()
方法。一般实现自定义加载器要重写此方法
Ⅲ defineClass()
defineClass()
通常与findClass()
方法一起使用,defineClass()
生成类的Class对象
2. SecureClassLoader & URLClassLoader
SecureClassLoader
扩展了ClassLoader,新增了几个与使用相关的代码源(对代码源的位置及其证书的验证)和权限定义类验证(主要指对class源码的访问权限)的方法,一般我们不会跟它打交道,更多的是与URLClassLoader有所关联
URLClassLoader
实现了ClassLoader中的findClass()
方法,新增了URLClassPath类协助取得Class字节码流等功能。如果没有过于复杂的需求,可以直接继承此类实现自定义类,这样代码更加简洁
3. Class.forname()
& classLoader.loadClass()
Class.forname()
:
是一个静态方法,该方法在将Class文件加载到内存的同时,会执行类的初始化
classLoader.loadClass()
:
是一个实例方法,该方法将Class文件加载到内存时,并不会执行类的初始化
三、双亲委派模型
1. 优势与劣势
Ⅰ 优势
- 避免类重复加载
- 保护程序安全,防止核心API被随意修改
Ⅱ 劣势
由于双亲委派机制是单向的,上层的classLoader无法访问访问下层classLoader加载的类
2. 破坏双亲委派机制
⚪ 破坏双亲委派会不会影响核心API?
JDK为核心类库提供了一层保护机制,不管是什么类加载器,最终都会调用**defineClass()**
方法,而该方法会执行**preDefineClass()**
方法,该方法提供了对JDK核心类库的保护
Ⅰ 第一次破坏
第一次“破坏”发生在双亲委派机制出现之前——即JDK1.2之前。双亲委派机制是在JDK1.2引入的,但是类加载器的概念和抽象类ClassLoader则在java的第一个版本就存在了,为了兼容这些已有的代码,无法再以技术的手段避免**loadClass()**
被子类覆盖的可能性。只能在JDK1.2之后新增findClass()
方法
Ⅱ 第二次破坏
由于双亲委派机制的缺陷,引入了一个不太优雅的设计:线程上下文类加载器,这个类加载器同过java.lang.Thread类的**setContextClassLoader()**
方法进行设置,如果创建线程时还未设置,它将会从父线程中继承一个,如果在应用程序的全局作用范围都没有设置过的话,那这个类加载器默认就是应用程序类加载器
Ⅲ 第三次破坏
代码热替换和模块热部署的需求
IBM公司主导的JSR-291(即OSGi R4.2)实现模块化热部署的关键是它自定义的类加载机制的实现,每一个程序模块(OSGi 中称为Bundle)都有一个自己的类加载器,当需要更换一个Bundle时,就把Bundle连同类加载器一起换掉以实现代码的热替换。在OSGi环境下,类加载器的结构是一种网状结构
四、沙箱安全机制
- 保护程序安全
- 保护java原生的JDK代码
五、自定义类加载器
1. 为什么要自定义类加载器
- 隔离加载类
- 修改类加载的方式
- 扩展加载源
比如从数据库、网络、甚至是电视机机顶盒进行加载
- 防止源码泄漏
进行编译加密,那么类夹加载器需要自定义,还原加密的字节码
2. 实现自定义加载器
Ⅰ 实现方式
- 继承classLoader类,重写
loadClass()
方法
会破坏双亲委派机制
- 继承classLoader类,重写
findClass()
方法(建议用这个方式)
编写好自定义的类加载器后,在程序中调用loadClass()
方法就可以实现类加载操作了
Ⅱ 代码实现
public class MyClassLoader extends ClassLoader{
// 字节码文件路径
private String byteCodePath;
public MyClassLoader(String byteCodePath) {
this.byteCodePath = byteCodePath;
}
public MyClassLoader(ClassLoader parent, String byteCodePath) {
super(parent);
this.byteCodePath = byteCodePath;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
BufferedInputStream bis = null;
ByteArrayOutputStream baos = null;
try {
// 获取字节码文件的完整路径
String fileName = byteCodePath + name + ".class";
bis = new BufferedInputStream(new FileInputStream(fileName));
baos = new ByteArrayOutputStream();
int len;
byte[] data = new byte[1024];
while ((len = bis.read(data)) != -1) {
baos.write(data, 0, len);
}
// 获取内存中的完整的字节数组中的数据
byte[] byteCodes = baos.toByteArray();
// 调用defineClass(),将字节数组的数据转换为Class实例
return defineClass(null, byteCodes, 0, byteCodes.length);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (bis != null)
bis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
if (baos != null)
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
}
// 测试
public class MyClassLoaderTest {
public static void main(String[] args) {
MyClassLoader loader = new MyClassLoader("d:/");
try {
Class clazz = loader.loadClass("Demo2");
// 获取Demo2的类加载器
System.out.println(clazz.getClassLoader().getClass().getName());
System.out.println(clazz.getClassLoader().getParent().getClass().getName());
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
六、jdk9的新特性
在双亲委派之前,先判断该类是否能够归属到某个系统模块中,如果可以找到这样的归属关系,就要优先委派那个模块的类加载器完成加载