JVM类加载机制

## 一、从JVM架构说起

image-20200507160216558

JVM分为三个子系统:

(1)类加载器子系统 (2)运行时数据区 (3)执行引擎

二、虚拟机启动、类加载过程分析

package com.darchrow;

/**
 * @author mdl
 * @date 2020/5/7 16:12
 */
public class HelloWorld {
    public static void main(String[] args) {
        System.out.println("Hello World!");
    }
}

编译: 源文件-->class文件,这个时候只是将源文件翻译为JVM可识别的文件,落到磁盘上。

javac HelloWorld.java

运行:类加载器在此期间负责将class加载到内存,最终形成可以被JVM直接使用的java类型。

java HelloWord

系统将调用{JAVA_HOME}/bin/java.exe完成以下步骤:

1. 为JVM申请内存空间
2. 创建引导加载器BootstrapClassLoader实例,加载系统类到内存方法区域
3. 创建启动类实例Launcher,并取得类加载器ClassLoader
4. 使用获取到的ClassLoader加载我们定义的HelloWorld类
5. 加载完成JVM执行main方法
6. 结束,java程序结束,JVM销毁
  1. 引导类加载器 Bootstrap ClassLoader,它加载的是 {JRE_HOME}/lib下的类

    image-20200507174150684

  2. 创建启动类实例Launcher,并取得类加载器ClassLoader,这个时候获取到的有

    ExtClassLoader、AppClassLoader

    image-20200507174213370

三、类的加载

​ 类加载器并不需要程序“首次主动使用”时加载它,JVM规范允许加载器在预料某个类将要被使用时就预先加载它,如果在预先加载时出现错误,类加载器必须在程序首次主动时报告错误,如果这个类一直没有被程序主动使用,那么加载器不会报告此错误。

​ 加载后数据的存储大概是这个样子的

image-20200508105612803

​ 类加载的过程

image-20200508105844967

1) 加载

	1. .class文件到内存

 		2. 类的数据结构到方法区
          		3. 堆区生成一个java.lang.Class对象,作为方法区数据结构的入口

2)连接

2.1) 校验:确保被加载的类的正确性

文件格式验证:验证字节流是否符合Class文件格式的规范;例如:是否以0xCAFEBABE开头、主次版本号是否在当前虚拟机的处理范围之内、常量池中的常量是否有不被支持的类型。

元数据验证:对字节码描述的信息进行语义分析(注意:对比javac编译阶段的语义分析),以保证其描述的信息符合Java语言规范的要求;例如:这个类是否有父类,除了java.lang.Object之外。

字节码验证:通过数据流和控制流分析,确定程序语义是合法的、符合逻辑的。

符号引用验证:确保解析动作能正确执行。

2.2) 准备:为类变量在方法区分配内存,并将其初始化为零值

​ 类变量是被static修饰的变量

public static int value = 123;

​ 准备阶段过后初始值为0

​ 也有特例

public static final int value = 123;

final修饰的static变量为常量,准备阶段后就是123了

​ 实例变量不会在此阶段分配内存,它将会在对象实例化时随着对象一起分配到堆中。(实例化不是类加载的一个过程,类加载发生在所有实例化操作之前,并且类加载只能进行一次,实例化可以进行多次)

2.3)解析:把类中的符号引用转换为直接引用

符号引用就是一组符号来描述目标,可以是任何字面量。

直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

举例:

package com.darchrow.demo;

/**
 * @author mdl
 * @date 2020/5/8 11:44
 */
public class Animal {
}

package com.darchrow.demo;

/**
 * @author mdl
 * @date 2020/5/8 11:44
 */
public class Person {

    private Animal pet;

}

Person类中引用了Animal,编译阶段只能用com.darchrow.demo.Animal代表Animal的真实地址,解析阶段,JVM将解析该符号引用,来确定com.darchrow.demo.Animal的真实地址(如果该类还没加载过,则先加载)。

3)初始化:虚拟机执行类构造器 <clinit>() 方法的过程

  1. 声明类变量为初始值

  2. 使用静态代码块为类变量指定初始值

<clinit>()方法有如下特点:

  • 按顺序收集。特别注意的是,static{}代码块只能访问定义在它之前的变量,定义在它之后的变量,只能赋值,不能访问。

    package com.darchrow.demo;
    
    /**
     * @author mdl
     * @date 2020/5/8 14:24
     */
    public class TestStatic {
    
        static {
            int i = 0;
            // System.out.println(i); 这句编译器会提示“非法向前引用”
        }
    
        static int i = 1;
    
        public static void main(String[] args) {
            System.out.println(TestStatic.i);
        }
    }
    
    

    image-20200508143339845

  • 虚拟机会保证子类() 方法运行之前,父类的 () 方法已经执行结束。

    package com.darchrow.demo;
    
    /**
     * @author mdl
     * @date 2020/5/8 14:36
     */
    public class Parent {
    
        static{
            System.out.println("父类静态代码块");
        }
    
        public Parent() {
            // 实例化发生在类加载之后
            System.out.println("父类构造函数");
        }
    }
    
    
    package com.darchrow.demo;
    
    /**
     * @author mdl
     * @date 2020/5/8 14:36
     */
    public class Children extends Parent {
    
        static {
            System.out.println("子类静态代码块");
        }
    
        public Children() {
            // 实例化发生在类加载之后
            System.out.println("子类构造函数");
        }
    
        public static void main(String[] args) {
            new Children();
        }
    
    }
    
    

    image-20200508145338927

  • () 方法对于类非必须,如果类中不包含静态变量、静态代码块,编译器可以不生成此方法

  • 接口中不可以使用静态语句块,但可以声明静态变量。区别于类,接口的() 方法不需要先去执行父类的() 方法,只有当父类中定义的变量使用时,父接口才初始化。另外,接口的实现类在初始化时同样不会调用接口的() 方法。

  • () 方法多线程下已被正确的加锁和同步,如果多线程初始化一个类,将只有一个线程执行,其他线程将阻塞等待知道执行线程执行完毕。注意避免在一个类的() 方法中有耗时操作。

    初始化时机:其实就是主动引用
    1. new对象、访问类变量(final static除外)、类方法
    2. 使用反射:java.lang.reflect,如果类未被初始化,则先初始化
    3. 初始化类时发现父类未被初始化,先触发父类的初始化
    4. 虚拟机启动,用于指定主类并执行main方法,其实我们发现main方法就是类方法
    5. jdk1.7 动态语言支持

    例外的情况即被动引用

    • 子类引用父类的静态字段,不会触发子类的初始化

      package com.darchrow.demo.passive;
      
      /**
       * @author mdl
       * @date 2020/5/8 15:16
       */
      public class SuperClass {
      
          public static int value =100;
      
          static {
              System.out.println("父类静态代码块");
          }
      
      }
      
      package com.darchrow.demo.passive;
      
      /**
       * @author mdl
       * @date 2020/5/8 15:16
       */
      public class SubClass extends SuperClass {
      
          static {
              System.out.println("子类静态代码块被调用");
          }
      
      }
      
      package com.darchrow.demo.passive;
      
      /**
       * @author mdl
       * @date 2020/5/8 15:19
       */
      public class Test {
      
          public static void main(String[] args) {
              System.out.println(SuperClass.value);
          }
      
      }
      

      image-20200508152113469

    • 通过数组来定义引用类,不会触发此类的初始化,数组继承Object,包含数组的属性和方法。

      还是上面的例子,改下Test类

      package com.darchrow.demo.passive;
      
      /**
       * @author mdl
       * @date 2020/5/8 15:19
       */
      public class Test {
      
          public static void main(String[] args) {
      //        System.out.println(SuperClass.value);
              SuperClass[] sca = new SuperClass[10];
          }
      
      }
      

      控制台无任何打印

    • 常量在编译阶段会进入常量池(方法区),本质上并没有直接引用到定义常量的类,因此不会触发常量的初始化

      package com.darchrow.demo;
      
      /**
       * @author mdl
       * @date 2020/5/8 15:25
       */
      public class ConstClass {
      
          static {
              System.out.println("我被调用了!");
          }
      
          public static final String HELLO_WORLD="hello world";
      
      }
      
      package com.darchrow.demo.passive;
      
      import com.darchrow.demo.ConstClass;
      
      /**
       * 被动引用
       *
       * @author mdl
       * @date 2020/5/8 15:19
       */
      public class Test {
      
          public static void main(String[] args) {
      //        System.out.println(SuperClass.value);
      //        SuperClass[] sca = new SuperClass[10];
              System.out.println(ConstClass.HELLO_WORLD);
          }
      }
      

      image-20200508152842618

posted @ 2020-05-08 16:33  饮酒至天明  阅读(130)  评论(0编辑  收藏  举报