类加载(Class Loading)机制是将字节码格式的文件加载到内存,经过连接、初始化后,形成可以被jvm虚拟机可以执行的类型的过程。
Class Loading机制主要包括 加载-连接-初始化 连接又包含 验证-准备-解析
类的生命周期就比Class Loading多了两步就是 使用-卸载。整体: 加载-验证-准备-解析-初始化-使用-卸载
一 加载(Loading)
负责将字节码文件加载到jvm内存中,这个内存空间就是我们常说的方法区
1.通过类的全限定名获取定义此类的二进制字节流 获取方式有本地,网络等
2.将字节流所对的静态存储结构转化为方法区运行时的数据结构
3.在内存中生成累的java.lang.class对象,也就是方法区中对外暴露的数据访问入口
加载,是class loading的第一步,偶尔加载和连接(比如字节码格式的验证)的执行是交叉执行的,但是两个阶段的开始执行时间是按照固定的顺序的。还有,jvm加载数组的时候是加载的数组内的数据类型,比如String[],加载器仅仅会加载String,因为数组是相同类型,为了减少不必要的加载次数,多维数组就通过递归,从外到内加载类型。引用类型和基本类型的加载其实是一样的,以为比如int类型在javac阶段已经被自动装箱成integer了。
二 验证(Verification)
验证是连接阶段的第一步,目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求。
1.文件格式的验证:验证字节流是否符合Class文件格式的规范,比如,主次版本号是否在当前虚拟机的处理范围内,常量池中的常量是否有不支持的类型。
2.元数据验证:对字节码描述的信息进行语义分析(对比javac编译阶段的语义判断),保证其描述的信息符合java代码规范
3.字节码验证:通过数据流和控制流分析,确保程序语义的合法性和逻辑性。
4.符号引用验证:确保解析动作能正确执行。
验证阶段重要但是非必须,当所应用的类已经经过反复验证,我们可以采用-Xverifynone参数关闭大部分的类验证措施,就可以缩短虚拟机类加载的时间。
三 准备(Preparation)
准备阶段正式为类变量分配内存并设置初始化值的阶段,这些变量都会在方法区中,所以变量都是被static修饰的变量,而实例变量会在对象实例化时随时对象一起在堆内存分配,jvm虚拟机的内存管理,有兴趣可以 https://blog.csdn.net/Liveor_Die/article/details/77895631,那这里要提的一点是,比如 static int test = 333;准备阶段的初始化值是0而不是333,因为这时还没执行任何java方法,而赋值操作的指令putstatic是在程序被编译后,存放在类构造器方法中了,所以只能是类初始化后才能赋值333.那如果指定为 final static修饰,那就在准备阶段直接赋值333了。
四 解析(Analysis)
解析阶段是虚拟机将常量池内的符号引用替换为直接引用的过程。解析动作主要针对类或接口、字段、类方法、接口方法、方法类型、方法句柄和调用点限定符7类符号引用进行。
五 初始化(Initialization)
初始化阶段真正开始执行类中定义的java程序代码。准备阶段,变量已经进行了一次初始值的赋值,但是在初始化阶段,根绝开发者通过程序控制的代码逻辑去初始化类变量和其他资源。
初始化分为主动引用和被动引用
1.遇到new、getstatic、putstatic或者invokestatic这四条字节码指令时,如果没有进行过初始化,则先出发初始化。生成这四条指令的常见场景:使用new关键字实例化对象,读取或者设置一个类的静态字段(被final修饰,已经在编译期就赋值放入到常量池的静态字段除外),还有调用一个类的静态方法。
2.使用java.lang.reflect包的方法对类进行反射调用的时候,如果没有初始化,则初始化。
3.初始化一个类的时候,如果父类没初始化
4.虚拟机启动时,需要指定程序入口,也就是main方法,那么main方法所在的类需要主动初始化。
只有以上四种为主动引用,其他都是被动引用了。
总结:以上就是类加载的相关步骤以及各自注意点,说到类加载就一定离不开类加载器,所以接下来会写一波classloader。