JVM中类加载的过程

简介: JVM中类加载的过程


 前面看了类加载的时机,本文来记录下类加载的过程,也就是加载的每个阶段都做了哪些事情

类的生命周期

image.png

加载

 "加载"是类加载过程中的一个阶段,在这个阶段虚拟机做了3件事

   通过一个类的全限定名获取定义此类的二进制流

   通过这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

   在内存中生成一个代表这个类的 java.lang.Class对象,作为方法区这个类的各种数据访问的入口。

 image.png

     注意,加载阶段与连接阶段的部分内容是交叉进行,加载阶段尚未完成,连接阶段可能已经开始了,但总体的顺序还是先加载再连接。

验证

 验证阶段是连接阶段的第一步,这个阶段的目的是确保Class文件的字节流中包含的信息符合当前虚拟机的要求。不会危害虚拟机自身。验证的内容包含如下4个阶段

   文件格式验证

     验证字节流的格式是否符合class文件的规范及是否能被当前虚拟机处理。

   a.是否已魔数0xCAFEBABE开头

   b.主次版本号是否在当前虚拟机处理范围之内

   c.常量池的常量中是否有不被支持的常量类型tag标志

   d.指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量

   e.CONSTANT_Utf8_Info型的常量是否有不符合UTF-8编码的数据

   f.Class文件中各个部分及文件本身是否有被删除的或附加的其他信息

   …

   元数据验证

     语言分析,保证描述信息符合java语言规范要做

   a.这个类是否有父类

   b.这个类的父类是否继承了不允许继承的类(final修饰)

   c.非抽象类,是否实现了父类及接口中的所有的抽象方法

   d.类中字段,方法是否和父类产生矛盾

   …

   字节码验证

     本阶段是最复杂的阶段,通过数据流和控制流分析确定程序语义是否合法和符合逻辑。

   符号引用验证

准备

 本阶段也称为零值阶段,也就是将类中的类变量分配内存及赋初值,此处的初值是赋予对应类型的零值,如下

public static int value=123;

那么变量value的值在这个阶段赋予的是0而不是123,这里int为0,long为0L,boolean为false… …真正的初始化赋值是在初始化阶段进行的。同时要注意如果类变量被final修饰那么准备结果的结果就会不同

public final static int value=123;

编译时Javac将会为value生成ConstantValue属性,在准备阶段虚拟机就会根据ConstantValue的设置将value赋值为123.

解析

 解析阶段就是将常量池内的符号引用替换为直接引用的过程。解析阶段包含以下内容。

   类或接口的解析

   字段解析

   类方法解析

   接口方法解析

初始化

 在准备阶段已经对类变量赋值过一次了,当时是赋予的零值,而到了初始阶段则会根据我们主观计划去初始化类变量和其他资源,其本质初始化阶段是执行类构造器<clinit>方法的过程,在这个过程中有几个要注意的地方

   静态语句块只能访问到定义在静态语句块之前的变量。可以给定义在之后的变量赋值但不可以访问

public class Test2 {
  static{
    i = 10;  // 能赋值
    System.out.println(i); // 但不能访问,提示 非法向前引用
  }
  static int i = 0;
}

   <clinit>不需要显示的调用父类的类构造器。虚拟机保证子类的<clinit>方法执行之前父类的<clinit>方法已经执行

   由于父类先执行<clinit>方法,所以父类的静态语句块会优先于子类的静态语句块执行

public class Test2 {
  static class Parent{
    static int A = 1;
    static{
      A = 2;
    }
  }
  static class Sub extends Parent{
    static int B = A;
  }
  public static void main(String[] args) {
    System.out.println(Sub.B);
  }
}

输出结果是2而不是1.

   <clinit>方法对于类或接口来说并不是必需的。如果一个类中没有静态语句块也没有对变量的赋值操作,那么编译器可以不为这个类生成<clinit>方法。

   接口中不能使用静态语句块,任然有变量赋值操作,所以接口和类一样也会生成<clinit>()方法,但接口和类不同,接口中的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有当父接口中定义的变量使用时父接口才会初始化。接口的实现类在初始化的时候一样不会执行<clinit>方法

   同一个类只会被加载一次,/()方法也只会执行一次,如果多线程环境中只会有一个线程执行<clinit>方法,其他线程需要等待其执行完成。如果执行比较耗时那么会产生阻塞。

public class Test2 {
  static {
    if (true) {
      System.out.println(Thread.currentThread().getName()+"开始初始化...");
      while (true) {
        // 死循环 阻塞
      }
    }
  }
}
/**
 * 测试
 * 
 * @author 波波烤鸭
 * @email dengpbs@163.com
 *
 */
public class Test {
  public static void main(String[] args) {
    new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始...");
        Test2 t = new Test2();
        System.out.println("线程结束...");
      }
    }).start();
    new Thread(new Runnable() {
      @Override
      public void run() {
        System.out.println(Thread.currentThread().getName() + "线程开始...");
        Test2 t = new Test2();
        System.out.println("线程结束...");
      }
    }).start();
  }
}

输出结果

Thread-0线程开始...
Thread-1线程开始...
Thread-0开始初始化...

一个线程在初始化,但死循环了,另一个线程只能等待。

参考《深入理解java虚拟机》


相关文章
|
4月前
|
前端开发 安全 Java
聊聊Java虚拟机(一)—— 类加载子系统
虚拟机就是一款用来执行虚拟计算机指令的计算机软件。它相当于一台虚拟计算机。大体上,虚拟机分为系统虚拟机和程序虚拟机。系统虚拟机就相当于一台物理电脑,里面可以安装操作系统;程序虚拟机是为了执行单个计算机程序而设计出来的虚拟机。其中 Java 虚拟机就是**执行 Java 字节码指令的虚拟机**。
46 2
|
5天前
|
前端开发 安全 Java
深入浅出JVM(八)之类加载器
深入浅出JVM(八)之类加载器
|
6天前
|
存储 缓存 安全
JVM 类的加载篇
JVM 类的加载篇
17 0
|
20天前
|
Java
[JVM] Java类的加载过程
[JVM] Java类的加载过程
[JVM] Java类的加载过程
|
28天前
|
存储 监控 安全
JVM工作原理与实战(五):类的生命周期-加载阶段
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了类的生命周期、类的加载阶段等内容。
28 5
|
1月前
|
存储 前端开发 安全
JVM内部世界(内存划分,类加载,垃圾回收)(上)
JVM内部世界(内存划分,类加载,垃圾回收)
55 0
|
2月前
|
Arthas 存储 Java
不重启 JVM,如何替换掉已经加载的类
不重启 JVM,如何替换掉已经加载的类
35 0
|
3月前
|
安全 Java 程序员
深入理解jvm - 类加载过程
深入理解jvm - 类加载过程
51 0
|
4月前
|
缓存 安全 前端开发
JVM(类的加载与ClassLoader、双亲委派机制)
JVM(类的加载与ClassLoader、双亲委派机制)
|
5月前
|
存储 缓存 前端开发
JVM(二):Class加载机制
JVM(二):Class加载机制