类加载器子系统作用
- 类加载器子系统负责从文件系统中或者网络中加Class文件,class文件在文件开头有特殊的文件标识,
- ClassLoader 只负责 class文件的加载,至于它是否可以运行,则有 Execution 执行引擎来决定。
- 加载的类信息存放于一块成为方法去的内存空间,除了类的信息, 方法区中还会存放运行时常量池信息,可能还包括字符串字面量和数字常量, (这部分常量信息是Class文件中常量池部分的内存映射)
字节码文件 -> 加载阶段 (引导类加载器(Bootstrap), 扩展类加载器(),系统类加载器()) -> 链接阶段(验证 , 准备 , 解析) -> 初始化阶段。
- class flie 存在于本地硬盘上,可以理解为设计师画在纸上的模板, 而最终这个模板在执行的时候,是要加载到jvm中,根据这个文件实例化处 n 个 一摸一样的实例
- class file 加载到JVM中, 被称为 DNA 元数据模板, 存放在方法去
- 在 .class 文件 -> JVM -> 最终成为数据模板,此过程 就要一个运输工具(类装载器 ClassLoader ) 扮演一个快递员的角色
类的加载过程
加载(Loading) -> 链接(Lingking (验证 Verification),准备(Preparation),解析(Resolution)) -> 初始化(Initialization)
加载
- 通过一个类的权限定名获取定义此类的二进制字节流
- 将这个字节流所代表的静态存储结构转化为方法去的运行时数据结构
- 在内存中生成一个代表这个类的 java.lang.Class 对象, 作为方法区这个类的各种数据的访问入口
补充:加载.class 文件的方式
- 从本地系统中直接加载
- 通过网络获取
- 从zip压缩包中读取, jar 和 war 的基础
- 运行时计算生成: 动态代理技术
- 由其他文件生成: JSP 技术
- 数据库中提取 .class 文件
- 从加密文件中获取, 防止Class 文件被反编译保护措施
链接
- 验证(Verify)
- 目的在于确保class文件的字节流中包含信息是否符合当前虚拟机要求,保证加载类的正确性,不会危害虚拟机自身安全
- 包含四种验证, 文件格式验证, 元数据验证, 字节码验证, 符号引用验证
- 准备(Prepare)
- 为类变量分配内存并且设置该类变量的默认初始值,引用数据为Null, 基本数据 为 0 或者 false
- 这里不会为实例变量分配初始值,类变量会分配在方法区,而实例变量会随着对象一起分配到 Java 中
- 解析(Resolve)
- 将常量池内的符号引用转换为直接引用的过程
初始化
- 初始化阶段是执行类构造器方法 ()的过程
- 此方法不需要定义,是 javac编译器自动收集类中的所有类变量的复制动作和静态代码块中的语句合并而来
- 构造器方法中指令按语句在源文件中出现的顺序执行
- () 不同于类的构造器 (关联: 构造器是虚拟机视角下的 ())
- 若该类具有父类 , jvm 会保证子类的 () 执行前,父类的() 已经执行完毕。
- 虚拟机必须保证一个类的 () 方法在多线程下被同步枷锁。
类加载器分类
- JVM 支持两种类型的类加载器,分别为引导类加载器(Bootstrap ClassLoader) 和 自定义类加载器(User-Defined CLassLoader),
- 从概念上来讲, 自定义类加载器一般指的是 程序中由开发人员自定义的一类类加载器, 但是 JAVA虚拟机规范没有这么定义,而是将所有的派生于抽象类的 ClassLoader 的类加载器都规划为 自定义类加载器
- 无论类加载器的类型如何划分,在程序中我们最常见的类加载器始终只有3个。如图所示
关于 ClassLoader
- 获取系统类加载器 CLassLoder.getSystemClassLoader()
- 获取其上层:拓展类加载器 systemClassLoader.getParent()
- 在获取其上层 extClassLoader.getParent() 发现值为null 获取不到引导类加载器
- 对于用户自定义类来说: 默认使用 系统类加载器进行加载 ClassLoaderTest.class.getClassLoader()
- String类 使用 引导类加载器进行加载 -> Java 的核心类库都是通过引导类加载器 会发现使用 String.class.getClassLoader() 获取的值为null。
虚拟机自带的加载器:
启动类加载器(引导类加载器 BootStrap ClassLoader)
- 这个类加载器 使用 C/C++ 语言实现的,嵌套在 jvm 内部
- 它用来加载 Java的核心库, (JAVA_HOME/jre/lib/rt.jar , resource.jar 或者 sun.boot.class.path 路径下的内容) 用于提供JVM自身的自身需要的类
- 并不继承自 Java.lang.ClassLoader 没有父加载器
- 加载拓展类和应用程序类加载器,并制定为他们的父类加载器
- 处于安全考虑, BootStrap 启动类 加载器只加载报名为 java, javax, sun 等开头的类
拓展类加载器(Extension ClassLoader)
- Java 语言编写,由 sun.misc.Launcher$ExtClassLoader 实现
- 原生于ClassLoader 类
- 父加载器为启动类加载器
- 从 java.ext.dirs 系统属性所指定的目录中加载类库,或者从JDK的安装目录的jre/lib/ext 子目录(拓展目录) 下加载类库,如果用户创建的jar放在此目录下,也会由此扩展类加载器加载。
应用程序类加载器(系统类加载器AppClassLoader) - Java语言编写,由 sun.misc.Launcher$AppClassLoader 实现
- 派生于ClassLoader类
- 父类加载器为扩展类加载器
- 它负责加载环境变量classpath 或系统属性 Java.class.path 指定路径下的类库
- 该类加载是程序中默认的类加载器, 一般来说,Java应用的类都是有它来加载
- 通过ClassLoader#getSystemClassLoader() 方法获取到该类加载器
System.out.println("***********启动类加载器****************");
URL[] urLs = Launcher.getBootstrapClassPath().getURLs();
for (URL urL : urLs) {
System.out.println(urL);
}
// 从JAVA_HOME/jre/lib/中找到一个class类查看他的加载器中
ClassLoader classLoader = Provider.class.getClassLoader();
System.out.println(classLoader);
System.out.println("*********扩展类加载器************");
String property = System.getProperty("java.ext.dirs");
for (String path : property.split(";")){
System.out.println(path);
}
在JAVA的日常应用程序开发中,类的加载几乎是由上述三种类加载器互相配合执行的,在必要时,我们可以自己定义类加载器。来定制类的加载方式
为什么要自定义类加载器
- 隔离加载类
- 修改类加载的方式
- 拓展类加载源
- 防止源码泄露
如何自己实现一个类加载器
- 开发人员可以通过继承抽象类 java.lang.ClassLoader 的方式,实现自己的类加载器,用来满足一些特殊需求
- 在JDK1.2,在自定义类加载器,总是会去继承ClassLoader并重写loadClass方法,从而实现自定义的类加载器。但是在jdk1.2之后,不在建议用户去覆盖loadCLass方法,而是建议自定义类加载逻辑写在 findClass() 方法中
- 在编写自定义类加载器的时候,如果没有太过复杂的需求,可以直接继承 URLClassLoader 类,这样就可以避免自己去编写findClass() 方法以及获取字节码流的方式, 让自定义加载器编写更加简洁。
public class CustomCla
t5ssLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
byte[] result = getClassFromCustomPath(name);
try {
if (result == null) {
throw new FileNotFoundException();
} else {
return defineClass(name, result, 0, result.length);
}
}catch (Exception e){
e.printStackTrace();
throw new ClassNotFoundException();
}
}
private byte[] getClassFromCustomPath(String name) {
// 从自定义路径中加载指定类
// 如果有加密 则需要解密
return new byte[0];
}
}
关于ClassLoader
ClassLoader 是一个抽象类,其后所有的类加载器都是继承自ClassLoader (不包括启动类加载器)
方法名称 | 描述 |
---|---|
getParent() | 返回该类加载器的父类加载器 |
froName(String name) | 加载名称为name的类,返回结果为 java.lang.Class 类的实例 |
findClass(String naem) | 查找名称为name的类,返回结果为java.lang.Class 类的实例 |
findLoadedClass(String name) | 查找名为name的已经被加载过的类,返回结果为java.lang.Class的实例 |
resolveClass(Class<?> c) | 连接指定的一个Java类 |