类加载机制描述

Java虚拟机把描述类的数据从Class文件加载到内存中,并对数据进行校验、转换解析和初始化,最终形成可被虚拟机直接使用的Java类型。在Java语言中,这些过程都是在程序运行期间完成的,不需要预编译

类加载的时机

整个生命周期为加载验证准备解析初始化使用卸载七个阶段。解析阶段有时候可能发生在初始化过程之后。严格规定了一下几种情况如果没有初始化必须立刻对类(也需要按顺序)进行初始化:

  1. 遇到new、getstatic、putstatic或invokestatic这四条字节指令时。
  2. 使用java.lang.reflect包对类型进行反射调用时。
  3. 父类还没有进行初始化的时候。
  4. 虚拟机启动,用户需要指定一个主类时候。
  5. java8中 接口中含有default修饰的默认方法时,其实现类初始化时需先初始化接口。

加载

加载是类加载的第一个阶段。在加载阶段,Java虚拟机需要完成一下三件事情。

  1. 通过一个类的全限定名来获取定义此类的二进制流。
  2. 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构。
  3. 在内存中生成一个代表该类的java.lang.class对象,作为方法区这个类的各种数据的访问入口。
    对于第一点如果获取二进制流,Java虚拟机并未对其具体规范,可以通过本地class文件、zip压缩包、动态代理技术生成,由加密文件中生成。
    此处两个知识点,动态代理模板设计模式、需要了解一下。因为存放在内存中的对象是代表该类的模板,生成实例时可以根据不同需要生成不同的实例。

验证

加载完毕后,我们需要对二进制流进行验证,这一步目的是:确保class文件的字节流中包含的信息符合java虚拟机规范的全部要求,保证不会危害到java虚拟机的安全。大致会完成以下四个阶段的验证。

  1. 文件格式验证,主要包括验证是否以魔数0xCAFEBABE开头,常量池中是否有不支持的类型等等。
  2. 元数据验证,主要包括这个类是否有父类、父类是否继承了不允许被继承的类(final修饰的类)、类中字段、方法是否与父类产生矛盾(比如覆盖final字段、不符合规则的重写)等等。
  3. 字节码验证,为了确定程序语义是合法的、符合逻辑的。主要包括保证任何跳转指令都不会跳到方法体以外的字节码指令上,保证方法体的类型转换总是有效的等等。
  4. 符号引用验证,检查该类是否缺少或者被禁止访问它依赖的某些外部类、方法、字段等资源。

准备

此阶段为类中定义的变量(即静态变量、被statci修饰的变量)分配内存并设置类变量初始化的阶段。
注意一点这里的类变量不是实例变量。

public static int value = 123

经过准备阶段后初始化的值是0,123要到后面的初始化阶段去了。

解析

该阶段是将常量池内的符号引用替换为直接引用的过程。符号引用经常以CONSTANT_Class_info、CONSTANT_Class_Fieldref_info等类型常量出现,用于定位常量目标。直接引用则直接指向目标,这块比较繁琐
难以讲解。

初始化

准备阶段已经给类变量初始化0值等。该阶段则是根据程序员编码的主观计划初始化变量和其他资源。也是类构造器<clinit()>方法的过程,是java编译器的自动生成物。其主要作用如下:

  1. 自动收集类中所有类变量的赋值动作和静态代码块中的语句,并对其进行合并。合并顺序则是源代码中语句出现的顺序。下面语句最终值为0。

    pritvate static int i = 10
    static{
     i = 0;
    }
  2. Java虚拟机保证其父类的<clinit()>方法执行在子类的<clinit()>方法前。则父类的静态代码块语句优先于子类的赋值操作

类加载器

在Java中定义的类加载器只有两种:引导类加载器、自定义类加载器。引导类加载器为bootstrapclassloader,如extensionclassloader、systemclassloader都属于自定义类加载器(派生自抽象类classloader的)。我们在进行类加载的时候通常会使用双亲委派模型进行类加载。使用该模型有以下好处:

  1. 避免类被重复加载
  2. 保护程序的安全性,比如String类,如果不用双亲委派,我们可以自定义类加载器加载,并插入恶意代码。

其工作机制为:一个类加载器收到了类加载请求,它首先不会自己去尝试加载这个类,而是把该请求委派给父类加载器去完成,每一个都是如此。因此所有的类加载请求最终都应该传送到顶层的启动类加载器中,如果父类反馈自己无法完成该加载请求,那么就会让子类加载器尝试加载。例如我们加载String类,传到bootstrapclassloader,它发现自己能加载,那么它就会加载String类。

启动类加载器

通常加载以java、javax、sun开头的,rt.jar,tools.jar都是启动类加载器加载的。如果需要委派给启动类加载器加载,则使用null即可,我们尝试获取也是null。

面试题
如何判断两个类是否相同?可以通过是否是同一个类加载器加载的来判断(此处判断两个类相同,指的是同样的方法、字段、类名等等,也与实例对象有区别)