类的生命周期image.png

类初始化时机

触发类初始化的情况

  1. new创建对象,访问类的静态字段(final修饰的常量字段除外),调用类的静态方法
  2. 反射API调用类时
  3. 子类初始化触发父类初始化

    对于接口而言,接口有default方法的,会初始化接口

  4. 虚拟机启动指定的主类

  5. 动态调用涉及的类

    不触发类初始化的特殊情况说明

  6. 通过子类引用父类的静态字段,不会触发子类的初始化

  7. 通过数组定义来引用类,不会触发类的初始化
  8. 类的常量不会触发类的初始化

    常量在编译阶段存入类的常量池中,本质上并没有直接引用到定义常量的类

类加载过程

image.png

1.加载

步骤:

  1. 通过类的全限定名获取类的二进制字节流
  2. 将二进制字节流转化为方法区的运行时数据结构
  3. 内存中生成对应的java.lang.Class对象,存放到方法区中

    要点:

    非数组类由类加载器加载,数组类由虚拟机直接创建
    数组类中的元素类型是引用类型,在加载元素类型时,数组类会被标识为与元素类型对应的类加载器关联
    数组类中的元素类型是基本类型,数组类会被标识为与启动类加载器关联

    2.验证

    确保class文件的字节流信息符合虚拟机的要求,保证虚拟机运行时的安全

    文件格式验证

    基于二进制字节流进行验证,验证版本号,文件的魔数,常量池中常量的类型等

    元数据验证

    对字节码描述的信息进行语义分析,保证不存在不符合java语言规范的元数据信息,校验是否有父类,是否允许继承,方法实现,字段是否合理

    字节码验证

    通过数据流和控制流分析,确定程序语义的合法性且符合逻辑。主要针对类的方法体校验分析。

    符号引用验证

    连接阶段解析过程中发生的验证,即发生在虚拟机将符号引用转换为直接引用的时候,确保解析动作能正确执行

    3.准备

    为类变量分配内存并为类变量设置零值,都在方法区分配内存,如果类变量为常量,初始化为指定值

    4.解析

    虚拟机将常量池中的符号引用解析成直接引用的过程。

    符号引用: 用一组符号描述所引用的目标,类似方法签名,通过符号引用唯一确定需要引用的类,引用的方法,引用方法的参数类型和返回类型 直接引用: 直接指向目标的指针,类似内存地址

5.初始化

执行类构造器()方法,()方法包含了类变量的赋值操作,静态语句,收集的顺序和代码源文件保持一致

类加载器

类加载器用于实现类的加载动作
类是否“相等”取决于类的全限定名和加载类的类加载器是否一样

两种类加载器:

  1. 启动类加载器(Bootstrap ClassLoader),使用C++实现,虚拟机自身一部分
  2. 所有其他的类加载器,java语言实现,继承自抽象类ClassLoader

包括扩展类加载器(Extension ClassLoader)【ExtClassLoader】,
应用程序类加载器(Application ClassLoader)【AppClassLoader】,
其他用户自定义类加载器

双亲委派模型:
一个类加载器收到了类的加载请求,自己先不加载,请求委派给父类加载器去完成,每个层次的类加载器都是如此,最终都传送到顶层启动类加载器中,当父类的加载器无法完成加载请求,子类加载器才尝试自己去加载

image.png

  1. 保证核心类库不被篡改
  2. 防止类的重复加载

具体实现:
实现双亲委派的代码都集中在java.lang.ClassLoader的loadClass()方法之中

  1. 先通过方法findLoadedClass检查类是否加载过
  2. 没有加载则调用父加载器的loadClass方法,若父加载器为空则使用启动类加载器作为父加载器
  3. 父加载器加载失败,再调用自己的findClass方法进行加载