zoukankan      html  css  js  c++  java
  • JVM类加载机制(一)

    一、概念

      程序编译后,生成class文件,经过加载、验证、准备、解析、初始化,最终使程序可以被JVM识别。

    二、类的生命周期

      类从被加载到虚拟机内存开始,到卸载出虚拟机内存结束,一共经历加载、验证、准备、解析、初始化、使用、卸载七个阶段。

    其中验证、准备、解析统称为连接。

      借用网上的图:

      其中解析过程在某些情况下可以在初始化之后执行,这是为了支持JAVA的动态绑定。

    三、类的初始化时机

      1. 使用new实例化对象

      2. 反射

      3. 初始化一个类时,发现父类没有被初始化,先初始化父类

      4. 指定要执行的主类包含main方法

      5. 使用JDK1.7的动态语言支持时,MethodHandle实例包含REF_getputinvoke static句柄,且对应类未初始化

      以上场景行为称为对类的主动引用,而被动引用不会被初始化,包含以下几个场景:

      1. System.out.print(SubClass.value); // value的引用在父类SuperClass中

      2. SuperClass[] sca =new SuperClass[10]; // 数组类型不会触发类的初始化

        数组类型是jvm自动生成、继承Object的子类。

      3.System.out.print(Constants.HELLOWORLD); // 常量类在编译时将进入常量池,本质上不会调用Constants类的初始化。

    四、类加载过程

      1. 加载

    • 根据(包名+类名)获取该类的二进制流
    • 将二进制流的静态存储结构转化为方法区的运行时存储结构
    • 生成 java.lang.Class对象,并作为该类的唯一入口

      2. 验证

    • 文件格式验证:是否以0xCAFEBABE开头、class文件版本号等
    • 元数据验证:语义分析,保证描述的信息符合java规范,这一步并未涉及方法体内
    • 字节码验证:方法体内部代码是否合法、符合逻辑
    • 符号引用验证:是否能定位到类,验证字段、类、接口方法的访问权限

      3. 准备

      static修饰的变量将被设置为零值。

      

       public static int value = 123; // 在准备阶段将被赋0

       public static final int value = 123; // 如果是常量,将直接初始化,而不是0,因为final的意思是:不可变的。

      4. 解析

      符号引用->直接引用

      5. 初始化

      初始化阶段才开始真正的执行JAVA代码,该阶段是虚拟机执行类构造器<clinit>()方法的过程。

      <clinit>()方法有以下特点:

    • 按语义顺序收集类变量的赋值动作和静态语句块static{ },其中静态语句块只能访问在它之前定义的变量,在它之后的只能赋值。

      

    public class Test {
        static {
            i = 0;                // 给变量赋值可以正常编译通过
            System.out.print(i);  // 这句编译器会提示“非法向前引用”
        }
        static int i = 1;
    }
    • 保证子类的<clinit>()方法在执行之前,父类的<clinit>()已经执行完成。
    static class Parent {
        public static int A = 1;
        static {
            A = 2;
        }
    }
     
    static class Sub extends Parent {
        public static int B = A;
    }
     
    public static void main(String[] args) {
         System.out.println(Sub.B);  // 输出结果是父类中的静态变量 A 的值,也就是 2。
    }
    • <clinit>()对于接口或类不是必须的,如果一个类没有对类变量的赋值动作,且无静态方法块static{},编译器可以

    不为该类生成<clinit>()方法。

    • 接口有变量赋值动作时,执行<clinit>()不会执行父类的<clinit>(),实现该接口的类初始化时不会执行接口的<clinit>方法
    • 多线程下,jvm保证类的<clinit>被正确的加锁和同步,多个线程同时初始化类构造器,将只有一个类初始化,其他将

    阻塞,所以<clinit>有耗时操作时将会造成线程阻塞。

    每一步脚印都要扎得深一点!
  • 相关阅读:
    微信小程序开发---各代码文件简介
    LeetCode71. 简化路径
    LeetCode70. 爬楼梯
    LeetCode69. x 的平方根
    LeetCode68. 文本左右对齐
    LeetCode剑指 Offer 09. 用两个栈实现队列
    LeetCode67. 二进制求和
    LeetCode66. 加一
    LeetCode65. 有效数字
    LeetCode64. 最小路径和
  • 原文地址:https://www.cnblogs.com/bloodthirsty/p/12502799.html
Copyright © 2011-2022 走看看