一、加载
io读取类全限定名(包名+类名),找到.class二进制文件,映射成jvm能识别到结构,然后加载至内存中,生成Class对象。
二、链接
1、验证
- 第一阶段,文件格式的验证,比如验证字节码是否符合jvm加载规范,比如是否以魔数OXCAFEBABE开始。
- 第二阶段,验证元数据。比如非抽象类,是否实现了父类接口中要求实现的方法。
- 第三阶段,字节码验证。验证语义是否合法。
- 第四阶段,符号引用验证。比如通过全限定名是否能找到对应的类。符号引用的类、方法等是否可以被当前类访问到(权限控制)。
2、准备
为静态变量分配内存、初始化值,比如int类型初始值是0。被final修饰的static字段不会设置,因为final在编译的时候就分配了。
3、解析(很重要的一步,并非顺序执行)
将常量池(根据字节码解析出来的常量池,而非我们常说到方法区中的常量池)中的符号引用转为直接引用。加入符号引用指向了一个未加载的类或其属性,会触发加载该类(但未必触发该类的链接以及初始化)。简单来说就是将指向常量池的引用指针,转换为指向内存的引用指针。 注意:解析器往往可能是在初始化之后才执行。
Q:为什么不一定在类加载时就执行?
虚拟机实现可以根据需要来判断到底是在类被加载器加载时就对常量池中的符号引用进行解析,还是等到一个符号引用将要被使用前才去解析它。
另外,只有当对类的主动使用的时候才会导致类的初始化,类的主动使用包括以下八种:
- 创建类的实例,也就是new的方式
- 访问某个类或接口的静态变量,或者对该静态变量赋值
- 调用类的静态方法
- 通过反射调用类
- 初始化某个类的子类,则其父类也会被初始化
- 如果一个接口声名了非抽象、非静态的方法,对直接或者非直接实现该接口的子类进行初始化时,该接口也会被初始化
- 使用动态代理时,会初始化被代理类
- 自jdk8后,接口中加入了默认方法(通过default关键字修饰),接口的实现类发生了初始化,则该接口作为父类要先进行初始化
三、初始化
执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。初始化就是执行类的构造器方法init()的过程。init()是javac编译器自动收集类中所有类变量的赋值动作和静态代码块中的语句合并来的,并不需要程序手动实现。另外如果该类具有父类,jvm会保证父类的init()方法先执行,然后再执行子类的init()(也就是构造器中的隐式super()。)
Q:何时触发类加载?
new Object() Class.forName().newInstance() 使用Constructor类的newInstance方法 包含main函数的类(待验证)
参考文献:
网友评论