加载 => 验证、准备、解析(链接) => 初始化
类初始化时机
1. 遇到 new , getstatic , putstatic , invokestatic 这四条字节码指令时,如果类没有进行过初始化,需要进行初始化
- new : 使用 new 关键字实例化一个对象的时候
- getstatic putstatic : 读取或者设置一个类的静态变量,调用某个静态字段,只是定义了这个字段的类才会初始化(被 final 修饰的在编译期被写入常量池的除外)
- invokestatic : 调用一个类的静态方法
2. 使用 java.lang.reflect 包的方法对类进行反射调用时,如果类还没有进行初始化
3. 初始化一个类时,发现他的父类还没有进行初始化,则先出发其父类的初始化
4. 虚拟机启动时,用户需要指定一个要执行的主类,也就是包含 mian 方法的那个类,虚拟机会先初始化这个类
5. 使用 jdk 1.7 时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果是 REF_getStatic , REF_putStatic , REF_invokeStatic 的方法句柄,并且这个方法对应得类没有进行过初始化,则需要先出发其初始化。
1 加载
通过一个类的全限定名来获取定义此类的二进制字节流,将这个字节流所代表的静态存储结构转化为方法区的运行时数据,在内存中生成一个代表这个类的Java.lang.class 对象,作为方法区中这个类的访问入口。
2 链接
2.1 验证
为了验证 .class 文件中的字节流符合 Java 虚拟机的规范,包括文件的格式,元数据(父类,是否抽象类,)等
2.2 准备
为类变量分配内存,设置类变量初始值
2.3 解析
将静态常量池中的符号引用转化为直接引用。包括类或接口的解析,字段的解析,类方法的解析,接口方法的解析。
符号引用 与虚拟机实现的内存布局无关,只是用一组唯一的符号来描述所引用的目标。 class 文件中的常量池中包括 字面量( 文本字符串,声明为final的常量值)与 符号引用(类和接口的全限定名,方法的名称和描述符,字段的名称和描述符),在class 文件中不会保存各个方法字段的最终布局信息,因此,这些符号引用不经过转化是无法得到真正的内存入口地址。
直接引用 与虚拟机实现的内存布局有关,可以是直接指向目标的指针,偏移量,句柄。
3 初始化
该阶段才会真正的开始执行类中定义的Java 代码。执行类构造器clinit()方法,该方法由编译器自动收集类中所有类变量赋值动作和静态语句块的语句合并产生。并且子类的clinit() 调用之前会先执行父类的clinit(),接口除外(接口的实现类执行clinit 方法时不会执行接口的clinit 方法)。clinit 方法可以不存在,比如没有静态语句块和类变量。
标签:初始化,Java,clinit,虚拟机,接口,机制,解析,方法,加载 From: https://blog.51cto.com/u_11290086/5804047