1.类加载机制
a. 类从加载到虚拟机到,卸载出内存为止,整个生命周期包括:
加载 -》连接 -》初始化 -》使用 -》 卸载
连接:验证 -》 准备 -》解析
b.什么时候开始类的加载第一阶段 – 加载?
jvm 没有明确的规定强制约束
c. 初始化阶段,五种情况
- 遇到new , 读取,设置静态字段(被final 修饰、已经在编译器把结果放到常量池的静态地段除外)
- 通过 反射手段 实例化一个类,发现该类没有被初始化时
- 调用一个类,发现该类的父类没有初始化时
- jvm启动时,需要 加载一个主类,虚拟机先执行该主类
以上几种情况是对一个类的主动引用,如果是被动引用则不会触发初始化
d.被动引用的情况
1.通过子类引用父类的静态字段,不会导致子类的初始化
public class Main {
public static void main(String[] args) {
System.out.println(Sub.x);
/*
* parent init !
10
*
* 不会初始化子类 sbu ,因为该静态字段是在父类中定义的
*/
}
}
class Parent {
static {
System.out.println("parent init !");
}
public static int x = 10;
}
class Sub extends Parent{
static {
System.out.println("sub init !");
}
}
2.通过数组的定义来引用类,不会触发该类的初始化
public static void main(String[] args) {
// System.out.println(Sub.x);
Parent[] parents = new Parent[10];
/*
* 不会输出任何信息
*/
}
3.常量在编译器期存入调用类的常量池中,本质上没有被直接引用定义该常量的类,因此不会触发定义类的初始化
public class Main {
public static void main(String[] args) {
System.out.println(Sub.y); // 访问 Sub.y 不会触发父类的 和 子类的初始化
}
}
class Parent {
static {
System.out.println("parent init !");
}
public static int x = 10;
public static final int y = 1;
}
2.类加载过程
a.加载
加载和类加载时两个概念,加载时类加载的第一个阶段
在加载阶段,,需要完成三件事情
1.通过一个全限定类名区获取定义此类的二进制流 (可以来自网络,计算机生成,文件生成,zip包)
2.将这个字节流所代表的静态存储结果转换 为方法区的动态数据结构
3.在内存中生成一个代表该类的java.lang.Class(字节码)对象,作为方法区这个类的各种数据的入口
b.验证
目的:确保 Class 文件的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机
1.文件格式验证
字节流是否符合 Class 文件的规范,并且能被当前虚拟机处理
- 验证魔数
- 主版本、此版本号
- 常量池中的数据是否 有不被支持的类型
…
2.元数据验证
保证符合java语言规范的要求
- 这个类是否有父类(所有的类都有Object 这个父类)
- 这个类是否继承了不允许被继承的类
- 如果这个类是抽象类,是否实现了父类,或接口中所有 被要求实现的方法
- …
3.字节码验证
对类的方法体进行校验分析,保证校验类的方法在运行时不会做出危害虚拟机的安全事件
c.准备阶段
正式为类变量分配内存并设置类变量的初始值阶段。值得注意的是,这里的变量使用的内存是在方法区中分配的
进行分配内存的只有类变量(被 static修饰),不包括实例变量,实例变量会在对实例化的时候随对象分配到 java堆中
这里的实例化并不是赋值为指定的值,而是变量类型的零值
public static int x = 123;
// 准备阶段过后,x = 0
// 初始化阶段后 x = 123
d.解析
解析是虚拟机将常量符号引用转换成直接引用的过程
e.初始化
在准备阶段,变量已经被赋上“零”值,而在这个阶段,将程序员指定的值进行赋值
初始化过程是执行类构造器的 方法的过程
- () 方法时由编译器自动收集类的所有类变量的赋值的赋值操作,和静态语句块中的的语句合并产生
- 编译器收集的顺序是语句在文件出现的顺序决定的
- 静态语句块只能访问到定义在静态语句块之前的变量
定义在静态语句块之后的变量,前面的静态语句块能给其赋值,但是不能访问
静态字段 和 静态代码块顺序执行
- 在执行子类初始化之前先执行父类的初始化
class Parent {
static {
x = 10; // 可以给定义在 static{}后的变量赋值
System.out.println(x); // 不可以访问
}
public static int x = 10;
}
3.类加载器
3.1类与类加载器
程序员可以自定义加载器
- 比较两个雷是否“相等”,只有是由同一个类加载器加载的前提下才有意义
- 否则,即使这两个类来自同一个 Class 文件,被同一个虚拟机加载,但是加载他们的类加载器不同,这两个类就不相等
这里的“相等”,包括代表类的 Class 对象的 equals()方法和 isInstance()方法
如果来自不同的类加载器, equalas() 和 isInstance()都不同
3.2双亲委派机制
类加载器:
启动类加载器
加载\lib 目录下的
扩展类加载器
加载\lib\ext 目录下的
应用程序类记载器
如果应用程序没有自定义类加载器,就是程序中默认的加载器
双亲委派机制工作:如果一个类加载器收到了类加载的请求,不会自己去尝试加载这个类,而是把这请求委派给其父类加载器去完成。每一层的加载器都是 如此,因此所有的加载请求都会被传到顶层的启动类加载器中,只有当其父类反馈自己无法完成加载这个请求时,子类加载器才会去尝试自己去完成加载
因此Object 类的程序在各种加载器环境中都是同一个类