java类加载及类初始化

时间:2023-02-16 22:35:57

1.前言

  java是跨平台语言,主要是因为它的java虚拟机的存在,java有事编译语言,所以需要将编写的java文件编译成jvm可运用的class字节码文件。在java中一切皆对象。对于Java虚拟机而言,一个Java类也是一个对象。一个类在JVM中被实例化成一个对象,需要经历三个过程:加载、链接和初始化。

2.加载

  通过读取字节码二进制.class文件将类加载到内存,从而达到类的从硬盘上到内存上的一个迁移,所有的class必须加载到内存才能工作。一个Java类在被加载到内存后会在Java堆中创建一个类(java.lang.Class)对象,同时JVM为每个类对象都维护一个常量池(类似于符号表)并将这个字节流代表的静态存储结构转换为方法区的运行时数据结构。

  • Bootstrap ClassLoader:这个加载器不是一个Java类,而是由底层的c++实现,负责在虚拟机启动时加载Jdk核心类库以及加载后两个类加载器。
  • Extension ClassLoader:是一个普通的Java类,继承自ClassLoader类,负责加载{JAVA_HOME}/jre/lib/ext/目录下的所有jar包。
  • App ClassLoader:是Extension ClassLoader的子对象,负责加载应用程序classpath目录下的所有jar和class文件。
  • 自定义类加载器

3.链接

  (1)验证

  验证是类加载的第二个阶段,这个阶段也是持续时间最长(从阶段连续性来说),这个阶段从加载开始进行,一直进行到解析阶段结束。验证是为了保证class文件中的内容是符合虚拟机规范的二进制字节流,防止通过执行一些不安全的二进制字节流而导致虚拟机奔溃。 从整体来看,类加载过程的验证阶段可以分为四个部分:文件格式验证、元数据验证、字节码验证和符号引用验证。

  (2)准备

  准备阶段是证实为类变量分配内存并且设置初始化值的阶段,这些变量所使用的内存都在方法区分配。这个阶段进行初始化的数据只有静态字段,并且是赋值初始化值(final修饰的字段除外),不是代码中定义的值。   

  public static int value = 123;在准备阶段,value在方法区分配内存,并且设置初始值0,如果value被final修饰,形如:public static final int value = 123;则该变量在准备阶段将会被赋值123,并且不会引起类的初始化过程,示例及说明见第二部分(类加载的时机)的示例

 

  (3)解析

  解析阶段是虚拟机将符号引用转化为直接引用的过程,符号引用在之前已经介绍过了,在class文件中以形如"CONSTANT_Class_info"、"CONSTANT_Fieldref_info"、"CONSTANT_Methodref_info"格式存在。符号引用是指在class文件内部的引用指向,也就是说,在class文件内部,会对各个方法,变量进行编号,当存在方法之间的调用或变量之间的引用时,以此编号为引用,找到调用的方法和变量。直接应用就是在对方法和变量解析之后将方法和变量的地址值返回到调用方,以便在调用的时候快速直接的定位方法和变量。由于在class文件没有加入到jvm时,我们是不知道方法的具体分配位置的,所以以符号引用表示,到class加载之后,在这个运行环境中,我们是可以定位方法和变量的地址的。所以在此时做解析。

4.初始化

  初始化阶段是类加载过程的最后一步,这个阶段才开始真正的执行用户定义的Java程序。在准备阶段,变量已经赋过一次系统要求的初始值,而在初始化阶段,则需要为类变量(非final修饰的类变量)和其他变量赋值,其实就是执行类的<clinit>()方法。在Java语言体系中,<clinit>()是由编译器生成的,编译器在编译阶段会自动收集类中的所有类变量的赋值动作和静态语句块(static{})中的语句合并而成的,编译器收集的顺序是由语句的顺序决定的,静态语句块只能访问到定义在静态语句块之前的变量,定义在静态语句块之后的变量,可以赋值,但是不能访问。

  <clinit>()方法与类的构造方法不同,它不需要用户显示的调用,虚拟机会保证父类的<clinit>()方法先于子类的<clinit>()执行,java.lang.Object的<clinit>()方法是最先执行的。接口中不能使用用静态语句块,所以接口的<clinit>()只包含类变量,所以接口的<clinit>()方法执行时,不要求限制性父接口的<clinit>()方法。<clinit>()方法对于类和接口来说不是必须的,如果类或接口中没有定义类变量,也没有静态语句块,那么编译器将不为这个类或者接口生成<clinit>()方法,如果类或者接口中生成了<clinit>()方法,那么这个方法在执行过程中,虚拟机会保证在多线程环境下的线程安全问题。

  虚拟机规范给了严格规定,有且只有以下几种情况必须立即对类进行初始化:
    1、遇到new、putstatic、getstatic及invokestatic这4条字节码指令时,如果类没有初始化,则立即进行初始化,这4个命令分别代表实例化一个类、设置&读取一个静态字段(没有被final修饰)、调用类的静态方法;
    2、使用java.lang.reflect包的方法对类进行反射调用的时候,如果类没有初始化;
    3、当初始化一个类的时候,发现其父类没有初始化;
    4、当虚拟机启动时,需用将执行启动的主类(有main()方法的那个类)进行初始化;
   5、当使用动态语言时,如果一个java.lang.invoke.MethodHandle实例最终的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic句柄时,并且这个句柄对应的类没有初始化。

感谢:https://blog.csdn.net/u010942465/article/details/81709246