1. 类加载器
1.1. 类加载器
-
作用
负责将.class文件(存储的物理文件)加载在到内存中
1.2. 类加载的过程
-
类加载时机 (类在第一次被使用的时候被加载,仅仅加载一次)
- 创建类的实例(对象)
- 调用类的类方法(也就是静态方法)
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
-
类加载过程
-
加载
- 通过包名 + 类名,获取这个类,准备用流进行传输
- 在这个类加载到内存中
- 加载完毕创建一个class对象
-
?
-
链接
-
验证
- 1 文件格式验证:验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理,验证点如下:
-
-
是否以魔数 "0XCAFEBABE" 开头。主次版本号是否在当前虚拟机处理范围内。常量池是否有不被支持的常量类型。指向常量的索引值是否指向了不存在的常量。CONSTANT_Utf8_info 型的常量是否有不符合UTF8编码的数据。
- 2 元数据验证:对字节码描述信息进行语义分析,确保其符合 Java 语法规范。-
这个类是否有父类(出了 java.lang.Object 之外,所有的类都应当有父类)。
-
这个类是否继承了不允许被继承的类(被 final 修饰的类)。
-
如果这个类不是抽象类,是否实现了其父类或接口之中要求实现的所有方法。
-
类中的字段、方法是否与父类产生矛盾(例如覆盖了父类的final字段,或者出现不符合规则的方法重载,例如方法参数都一致,但返回值类型却不同等)。
-
-
-
3 字节码验证:本阶段是验证过程中最复杂的一个阶段,是对方法体进行语义分析,保证方法在运行时不会出现危害虚拟机的事件。
- 保证任意时刻操作数栈的数据类型与指令代码序列都能配合工作。
-
保证跳转指令不会跳转到方法体以外的字节码指令上。
-
保证方法体中的类型转换是有效的。
-
4 符号引用验证:本阶段发生在解析阶段,确保解析正常执行。
-
-
-
符号引用中通过字符串描述的全限定名是否能找到对应的类。 包名+类名
-
在指定类中是否存在符合方法的字段描述符以及简单名称所描述的方法和字段。
-
符号引用中的类、字段、方法的访问性(private、protected、public、default)是否可被当前类访问。
-
如果一个类无法通过符号引用验证,那么将会抛出一个
java.lang.IncompatibleClassChangeError
异常的子类,如常见的java.lang.IllegalAccessError
、java.lang.NoSuchFieldError
、java.lang.NoSuchMethodError
等。
-
准备
负责为类的类变量(被static修饰的变量)分配内存,并设置默认值
(初始化静态变量)
class A{ static int a = 10; //在准备阶段 给a分配内存空间 给一个默认值0 Student b; }
?
-
解析
-
将类的二进制数据流中的符号引用替换为直接引用(本类中如果用到了其他类,此时就需要找到对应的类)
-
符号引用以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,只要使用能无歧义地定义到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标不一定已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须都是一致的。符号引用的字面量形式需要明确的定义在 Class 文件格式中。
-
-
初始化
根据程序员通过程序制定的主观计划去初始化类变量和其他资源
(静态变量赋值以及初始化其他资源)
- 初始化阶段是类加载过程中的最后一步,此阶段才是真正执行类中定义的 Java 程序代码。初始化阶段和准备阶段的初始化是不同概念的,准备阶段的初始化是给类字段赋值零值的过程,而类加载过程中的初始化阶段可以看做是类对象的初始化。对于类的初始化反映到字节码中就是类的
<clinit>()
方法。从另外一个角度来讲,可以将初始化阶段理解成是执行类构造器<clinit>()
方法的过程。同时对于<clinit>()
方法,有几个概念要弄清楚。
- 初始化阶段是类加载过程中的最后一步,此阶段才是真正执行类中定义的 Java 程序代码。初始化阶段和准备阶段的初始化是不同概念的,准备阶段的初始化是给类字段赋值零值的过程,而类加载过程中的初始化阶段可以看做是类对象的初始化。对于类的初始化反映到字节码中就是类的
-
小结
- 当一个类被使用的时候,才会加载到内存
- 类加载的过程: 加载、验证、准备、解析、初始化
1.3. 类加载器的分类
-
分类
- Bootstrap classloader:启动 类加载器,虚拟机的内置类加载器,通常表示为null ,并且没有父加载器
- Platform classloader:平台类加载器,负责加载JDK中一些特殊的模块 (Extension ClassLoader)
- System classloader:系统类加载器,负责加载用户类路径上所指定的类库 (Application ClassLoader)
-
类加载器的继承关系
- System的父加载器为Platform
- Platform的父加载器为Bootstrap
-
代码演示
public class ClassLoaderDemo1 { public static void main(String[] args) { //获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); //获取系统类加载器的父加载器 --- 平台类加载器 ClassLoader classLoader1 = systemClassLoader.getParent(); //获取平台类加载器的父加载器 --- 启动类加载器 ClassLoader classLoader2 = classLoader1.getParent(); System.out.println("系统类加载器" + systemClassLoader);// System.out.println("平台类加载器" + classLoader1); // System.out.println("启动类加载器" + classLoader2); // } }
1.4. ClassLoader 中的两个方法
-
方法介绍
方法名 说明 public static ClassLoader getSystemClassLoader() 获取系统类加载器 public InputStream getResourceAsStream(String name) 加载某一个资源文件 -
示例代码
- 注意属性文件要放到src下
public class ClassLoaderDemo2 {
public static void main(String[] args) throws IOException {
//static ClassLoader getSystemClassLoader() 获取系统类加载器
//InputStream getResourceAsStream(String name) 加载某一个资源文件
//获取系统类加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
//利用加载器去加载一个指定的文件
//参数:文件的路径(放在src的根目录下,默认去那里加载)
//返回值:字节流。
InputStream is = systemClassLoader.getResourceAsStream("prop.properties");
Properties prop = new Properties();
prop.load(is);
System.out.println(prop);
is.close();
}
}
自定义类加载器
- 就是自己写一个类 继承ClassLoad ,重写加载类的一些方法.让我们需要的类通过我们自己的类加载器加载
- 1 从非标准的来源加载代码:如果你的字节码是放在数据库、甚至是在云端,就可以自定义类加载器,从指定的来源加载类。
- 2 不同版本的相同jar包 一个项目 可能用到相同的框架的不同版本 logback1.0 logback2.0
Comments NOTHING