为什么要进行类加载?
要将Class文件加载到JVM中,然后根据描述在不同的内存空间给它分配内存
连接
作用
将二进制字节流存储在方法区中,然后在堆内存中实例化一个Class类对象,这个对象作为访问方法区中的类型数据的外部接口
这是可控性最强的阶段,这个阶段可以自定义很多自己的东西
添加虚拟机启动参数打印加载阶段的信息
-XX:+TraceClassLoading
public class NotInitialization{ public static void main(String []args){ System.out.print(SubClass.value); } } class SuperClass { static { System.out.println("SuperClass init!"); } public final static int value = 123; } class SubClass extends SuperClass { static { System.out.println("SubClass init!"); } }
结果打印:
SuperClass init!
public class NotInitialization{ public static void main(String []args){ SuperClass []superClasses = new SuperClass[10]; } }
结果:没有显示init
public class NotInitialization { public static void main(String[] args) { System.out.print(SuperClass.value); } } class SuperClass { static { System.out.println("SuperClass init!"); } public final static int value = 123; }
结果:没有显示init
原因:编译阶段会做常量传播优化,将vale的值存在NotInitialization类对应的常量池中,执行main方法相当于仅调用自身常量池的引用,且不持有SuperClass的引用
确保加载进来的Class文件字节流符合规范,不会危害JVM安全
可通过 -Xverify:none 参数来关闭大部分类验证,缩短短类加载时间
特性
与加载阶段交叉运行,最耗费时间的
② 元数据验证
对字节码的描述进行语义校验
③ 字节码验证
通过数据流和控制流分析确保语义合法且符合逻辑,确保其不会危害虚拟机
特性
① 最耗费时间
② 无法确保字节码验证后的代码没问题,程序无法准确判断
③ 在javac编译器里加了StackMapTable优化字节码验证时间
④ 符号引用验证
保证解析行为能够正常执行
特性
① 检查是否能根据符号引用中的全限定名找到对应的类
② 符号引用中的类、字段、方法的是否可被当前类访问(权限规则)
给静态变量分配内存并进行初始化
特性
① java7以及以前是在方法区中分配,之后是配置在java堆中
② 静态常量的话会在这里进行初始化并且赋值,静态变量的话仅初始化(赋0值)
将常量池中的符号引用转为直接引用
符号引用:java类在编译时并不知道引用对象的内存地址,就用符号表示
直接引用:直接指向目标的指针
真正执行类中编写的java代码
特性
① 初始化前由类加载器主导,从初始化开始由程序主导
② 初始化阶段就是执行类构造器的构成
每个类加载器在收到类加载请求时,会优先委派给父类加载器,只有当父加载器在自己的搜索范围没有找到所需的类,子加载器才会尝试自己去加载
作用
确保基础类是相同的类加载器加载,保证java程序的稳定运行
避免不同的类加载器去加载常用的类如Object,会导致应用程序混乱
为什么要破坏
弄懂OSGi的实现
答:同时满足这三个条件 ① 这个类的所有实例都被回收 ② 加载该类的ClassLoader已被回收 ③ 该类对应的java.lang.Class对象没有被任何地方引用,无法通过反射创建对象
类卸载就是在方法区中清空该类的信息,java8及之后永生带消除,里面存放的出数据也就是类的信息被移动到java堆
答:验证、准备、解析
答:因为不同加载器加载同一个类出来的对象是不相等的,假如用多个加载器加载Object,会导致代码比较混乱。所以需要确保基础用的类都是相同的类加载器加载。双亲委派模型就是指每个类加载器在收到加载类的请求时,会优先委托父加载器加载,除非父加载器无法加载,才会自己尝试加载。热部署的情况下需要避开
答:具体加载的步骤无法看到,只能通过加-XX:+TraceClassLoading查看是否被加载