类加载的过程

文章目录

  • 1. 类加载的过程


1. 类加载的过程

类加载的过程_第1张图片
Class 文件需要加载到虚拟机之后才能进行运行和使用,那么虚拟机是如何加载这些 class 文件的呢??

系统加载 Class 文件的内容主要用三步:加载——>连接——>初始化,连接部分又可以分为三步:验证——>准备——>解析

1. 加载
类加载过程的第一步,主要完成下面这三件事情:
①. 通过全类名获取定义此类的二进制字节流
②.将字节流所代表的静态存储结构转换为方法区运行时数据结构
③.在内存中生成一个代表该类的 Class 对象,作为方法区这些数据的访问入口

2. 连接
验证:
类加载的过程_第2张图片
准备:
准备阶段是正式为类变量设置分配内存,并设置初始值的阶段这些内存都在方法区中分配:
对于该阶段有以下几点需要注意:

  1. 这时候进行内存分配的只包括类变量(Class Variable,即静态变量,被static 关键字修饰的变量,只与类有关,因此被称为类变量),实例对象会在对象实例化时随着对象一块分配到Java 堆中。
  2. 从概念上讲,类变量所使用的内存都应该在方法区中进行分配。不过有一点注意的是,在 JDK 7 之前,Hotspot 使用 永久代来实现方法区时,实现是完全符合这种逻辑概念的。而在JDK 7 及之后,把原本放在永久代中的字符串常量池和静态变量等移到堆中。这个时候类变量会一并随着Class 对象一并放在 Java 堆中。
  3. 这里所设置的初始值,通常情况下是数据类型默认的 “零值”,如 (0,0L,null,false),比如我们定义了 public static int value = 11,那么value 变量在准备阶段赋的值是0,而不是11,(初始化阶段才会赋值),特殊情况:比如给 value 变量加上了 final 关键字public static final int value=11 ,那么准备阶段 value 的值就被赋值为 11。

解析:
解析阶段是虚拟机将常量值中的符号引用替换为直接引用的过程,解析动作主要针对类和接口,字段,类方法,接口方法,方法类型,方法句柄,方法的限定符。

符号引用就是一组符号来描述目标,可以是任何字面量。直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。在程序实际运行时,只有符号引用是不够的,举个例子:在程序执行方法时,系统需要明确知道这个方法所在的位置。Java 虚拟机为每个类都准备了一张方法表来存放类中所有的方法。当需要调用一个类的方法的时候,只要知道这个方法在方法表中的偏移量就可以直接调用该方法了。通过解析操作符号引用就可以直接转变为目标方法在类中方法表的位置,从而使得方法可以被调用。

3. 初始化阶段
初始化
初始化阶段是执行初始化方法 ()方法的过程,是类加载的最后一步,这一步 JVM 才开始真正执行类中定义的 Java 程序代码(字节码)。

说明:< clinit> () 方法是编译之后自动生成的。

对于< clinit> () 方法的调用,虚拟机会自己确保其在多线程环境中的安全性。因为 < clinit> () 方法是带锁线程安全,所以在多线程环境下进行类初始化的话可能会引起多个进程阻塞,并且这种阻塞很难被发现。

对于初始化阶段,虚拟机严格规范了有且只有 5 种情况下,必须对类进行初始化(只有主动去使用类才会初始化类):

1.当遇到 new 、 getstatic、putstatic 或 invokestatic 这 4 条直接码指令时,比如 new 一个类,读取一个静态字段(未被 final 修饰)、或调用一个类的静态方法时。
当 jvm 执行 new 指令时会初始化类。即当程序创建一个类的实例对象。
当 jvm 执行 getstatic 指令时会初始化类。即程序访问类的静态变量(不是静态常量,常量会被加载到运行时常量池)。
当 jvm 执行 putstatic 指令时会初始化类。即程序给类的静态变量赋值。
当 jvm 执行 invokestatic 指令时会初始化类。即程序调用类的静态方法。
2.使用 java.lang.reflect 包的方法对类进行反射调用时如 Class.forname("…"), newInstance() 等等。如果类没初始化,需要触发其初始化。
3.初始化一个类,如果其父类还未初始化,则先触发该父类的初始化。
4.当虚拟机启动时,用户需要定义一个要执行的主类 (包含 main 方法的那个类),虚拟机会先初始化这个类。
5.MethodHandle 和 VarHandle 可以看作是轻量级的反射调用机制,而要想使用这 2 个调用, 就必须先使用 findStaticVarHandle 来初始化要调用的类。
6. 当一个接口中定义了 JDK8 新加入的默认方法(被 default 关键字修饰的接口方法)时,如果有这个接口的实现类发生了初始化,那该接口要在其之前被初始化。

你可能感兴趣的:(java)