# 类加载器
# 一、Class对象
- 无符号数是一种基本数据类型,以 u1,u2,u4,u8 来分别代表 1个字节、2个字节、4个字节、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或按照 UTF-8 编码构成的字符串值。
- 表是由多个无符号数或其它表作为数据项所构成的复合数据类型,所有表都习惯行的以“_info”结尾。表用于描述有层次关系的复合结构数据。
# 二、类加载机制
Java虚拟机把描述类的数据从 Class 文件加载到内存,并进行数据校验、转换解析和初始化的过程,被称为类加载机制。
Java类从加载到卸载的整个生命周期图:
(opens new window)
加载-- 连接{验证,准备,解析} --初始化-- 使用 -- 卸载
- 加载、验证、准备、初始化和卸载的顺序是确定的
- 解析阶段某些情况下可以在初始化阶段之后再开始,这是为了支持Java语言的运行时绑定特性
按顺序开始不限制某阶段结束后下一阶段才能开始,不同阶段的执行可能相互交叉
# 运行时绑定
运行时绑定(也称动态绑定或晚期绑定):通过将类的解析延迟到初始化之后进行而实现
# 运行时加载
java类的加载、连接和初始化过程都是在程序运行期间完成的。
# 缺点
- 提前编译会面临额外的困难
- 类加载时增加一些性能开销
# 优点
- 提供了极高的扩展性和灵活性
Java天生可以动态扩展的语言特性就是依赖运行期动态加载和动态连接这个特点实现的。
只有加载阶段对于开发人员来说才是可见的,开发人员可以通过自己编写类加载器来参与这个步骤中。
# 2.1 加载
加载二进制文件,并完成三件事
- 通过类的全限定名来获取其定义的二进制字节流;
- 将字节流所代表的静态存储结构转化为方法区的运行时数据结构;
- 在 Java 堆中生成一个代表这个类的 java.lang.Class 对象,作为对方法区中这些数据的访问入口。
# 2.2 验证
确保被加载的类的正确性。
- 文件格式验证:检验字节流是否符合Class文件格式的规范
- 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的内容符合Java语言规范的要求
- 字节码验证: 通过数据流和控制流分析,确定程序语义是合法、符合逻辑的。
- 符号引用验证:对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验
# 2.3 准备
为类的静态变量分配内存,并将其初始化为默认值。
准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中分配。
静态变量在此阶段会初始化为默认值,但为了实现某些语言特性,编译器可能会对变量对应的字节码的字段表增加ConstantValue属性,则初始化为ConstantValue的值。 如final修饰的静态变量。
# 2.4 解析
把类中的符号引用转换为直接引用。
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程,解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符 7 类符号引用进行。
# 2.5 初始化
类变量进行初始化
为类的静态变量赋予正确的初始值,JVM 负责对类进行初始化,主要对类变量进行初始化。
触发初始化的5中情景
- 遇到 new、getstatic、putstatic 或 invokestatic 这 4 条字节码指令;
- 使用 java.lang.reflect 包的方法对类进行反射调用的时候;
- 当初始化一个类的时候,发现其父类还没有进行初始化的时候,需要先触发其父类的初始化;
- 当虚拟机启动时,用户需要指定一个要执行的主类,虚拟机会先初始化这个类;
- 当使用 JDK 1.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果 REF_getStatic、REF_putStatic、REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有初始化。
「有且只有」以上 5 种场景会触发类的初始化,这 5 种场景中的行为称为对一个类的主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。比如如下几种场景就是被动引用:
- 通过子类引用父类的静态字段,不会导致子类的初始化;
- 通过数组定义来引用类,不会触发此类的初始化;
- 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化;
# 三、类加载器
全限定名:java类的全限定名是包名+类名,如:java.lang.String
- 启动类加载器(Bootstrap Class Loader)
\lib 和-Xbootclasspath所指定的路径中的特定类
- /a: 以Bootstrap默认路径下的类为准
- /p: 以指定路径下的类为准
- 扩展类加载器(Extention Class Loader)
jre/lib/ext 和java.ext.dirs指定的目录
这个加载器的加载路径只有一个, 3. 应用程序类加载器(Application Class Loader)
加载环境变量classpath或者系统属性java.class.path指定路径下的类库。getSystemClassLoader()方法的返回值,也称系统加载类。
主要实现在Launcher类。
# 类加载时机
- 创建类的实例,也就是new一个对象
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 反射(Class.forName("com.lyj.load"))
- 初始化一个类的子类(会首先初始化子类的父类)
- JVM启动时标明的启动类,即文件名和类名相同的那个类
final修饰的静态变量,如果变量的值在编译时就可以确定下来,那么这个变量相当于“宏变量”。Java编译器会在编译时直接把这个变量出现的地方替换成它的值,因此即使程序使用该静态变量,也不会导致该类的初始化。如果编译时无法确定,运行时通过该类来访问它的静态变量,则会导致该类被初始化。
Class.forName()和ClassLoader.loadClass()都能实现动态加载,区别:
- Class.forName(): 将类的.class文件加载到jvm中之外,还会对类进行解释,执行类中的static块;
- ClassLoader.loadClass(): 只干一件事情,就是将.class文件加载到jvm中,不会执行static中的内容,只有在newInstance才会去执行static块。
- Class.forName(name, initialize, loader)带参函数也可控制是否加载static块。并且只有调用了newInstance()方法采用调用构造函数,创建类的对象 。
# 自定义类加载器
- 继承Classloader类,覆盖其查找类的函数
protected Class<?> findClass(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
- 通过自定义方法找到类文件的字节数组,调用父类方法定义类
public Class findClass(String name) {
byte[] data = loadClassData(name);
return defineClass(name, data, 0, data.length);
}
# 主要用途
# 加载其他来源的类
# 类隔离
# 类重载
# 类加密
# 双亲委派机制
保证类的唯一性和API的安全性
parent 还表达了一种共享关系,当多个子 ClassLoader 共享同一个 parent 时,那么这个 parent 里面包含的类可以认为是所有子 ClassLoader 共享的。
# 线程上下文件类加载器
程序就可以把原本需要由启动类加载器进行加载的类,由应用程序类加载器去进行加载了。
线程将继承其父线程的上下文类加载器。Java 应用运行的初始线程的上下文类加载器是系统类加载器。在线程中运行的代码可以通过此类加载器来加载类和资源。
# 实现
JVM生成的第一个类是Launcher类,Launcher的类加载器是启动类加载器
ClassLoader classLoader = Launcher.class.getClassLoader();
JVM在启动的时候,就生成了BootstrapClassLoader加载器,而BootrapClassLoader加载器会在JVM启动的第一时间就会去创建Launcher类的实例。
Launcher类的执行构造方法会创建ExtClassLoader加载器和AppClassLoader加载器,并且AppClassLoader设置为线程的上下文类加载器。
应用加载器获取:
ClassLoader classLoader = Launcher.getLauncher().getClassLoader();
实际加载路径:
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
Enumeration<URL> enums = appClassLoader.getResources("");
ClassLoader extClassLoader = appClassLoader.getParent();
URL[] urls = extClassLoader.getURLs();
URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
# URLClassLoader
ClassLoader只能加载classpath下面的类,而URLClassLoader则可以加载任意路径下的类。
# 后续
自定义的类和现有类如何屏蔽使用上的差异?
实现类热加载的整体设计应该是怎样的?