ClassLoad 类加载

类的生命周期

  • 加载
  • 验证
  • 准备
  • 解析
  • 初始化
  • 使用
  • 卸载

验证、准备、解析为连接

解析与初始化交换顺序为动态绑定

类加载过程

包含了加载、验证、准备、解析、初始化阶段

1、加载

  • 通过一个类的全限定名来获取此类的二进制字节流;
  • 将这个字节流所代表的静态存储结构转换为方法区的运行时存储结构
  • 在堆内存中生成一个代表这个类的class对象,作为方法区这个类的各种数据的访问入口

加载.class文件的方式有:

  • 1、 从本地系统中直接加载
  • 2.、通过网络下载.class文件
  • 3.、从zip,jar等归档文件中加载.class文件
  • 4.、从专有数据库中提取.class文件
  • 5.、将Java源文件动态编译为.class文件

2、验证

确保class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机本身

  • 文件格式验证:验证字节流是否符合 Class 文件格式的规范,并且能被当前版本的虚拟机处理。
  • 元数据验证:对字节码描述的信息进行语义分析,以保证其描述的信息符合 Java 语言规范的要求。
  • 字节码验证:通过数据流和控制流分析,确保程序语义是合法、符合逻辑的。
  • 符号引用验证:发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验。

3、准备

准备阶段为类变量分配内存并设置初始值,使用的是方法区的内存。

  • 1、为类变量分配内存(方法区内存)
  • 2、为类变量设置初始值(数据类型的初始值)
ps:类变量是被 static 修饰的变量

如果字段声明为常量(final static)则在准备阶段赋值为java代码中定义的值

4、解析

将常量池的符号引用替换为直接引用的过程。

  • 符号引用与虚拟机实现的布局无关,引用的目标并不一定要已经加载到内存中。各种虚拟机实现的内存布局可以各不相同,但是它们能接受的符号引用必须是一致的,因为符号引用的字面量形式明确定义在Java虚拟机规范的Class文件格式中。
  • 直接引用可以是指向目标的指针,相对偏移量或是一个能间接定位到目标的句柄。如果有了直接引用,那引用的目标必定已经在内存中存在。

5、初始化

初始化阶段才真正开始执行类中的定义的 Java 程序代码。初始化阶段即虚拟机执行类构造器 () 方法的过程。

  • 初始化变量是从上到下依次初始化

  • 初始化实例变量(实例变量会在对象实例化时随着对象一块分配在Java堆中),静态变量

  • 方法是由编译器自动收集类中的类变量的赋值操作和静态语句块中的语句合并而成的。

  • 虚拟机会保证方法执行之前,父类的方法已经执行完毕。

  • 如果一个类中没有对静态变量赋值也没有静态语句块,那么编译器可以不为这个类生成()方法。

ps:初始化时机

  • (1) 创建类的实例,也就是new的方式
  • (2) 访问某个类或接口的静态变量,或者对该静态变量赋值
  • (3) 调用类的静态方法
  • (4) 反射(如Class.forName(“com.shengsiyuan.Test”))
  • (5) 初始化某个类的子类,则其父类也会被初始化
  • (6) Java虚拟机启动时被标明为启动类的类(包含main()方法的那个类),直接使用java.exe命令来运行某个主类

ps:以下几种情况不会执行类初始化:

  • 通过子类引用父类的静态字段,只会触发父类的初始化,子类不会初始化
  • 定义对象数组,不会触发该类的初始化
  • 常量在编译期间会存入常量池中,本质上并没有直接引用定义常量的类,不会触发定义常量的类
  • 通过类名获取Class对象,不会触发类的初始化
  • 通过Class.forName加载指定类时,如果指定参数initialize为false,也不会进行初始化,这个参数是告诉虚拟机,是否有对类进行初始化
  • 通过ClassLoader默认的loadClass方法,也不会触发初始化

6、结束生命周期

    1. 执行了System.exit()方法
    1. 程序正常执行结束
    1. 程序在执行过程中遇到了异常或错误而异常终止
    1. 由于操作系统出现错误而导致Java虚拟机进程终止


类加载器

在 Java 虚拟机外部实现,以便让应用程序自己决定如何去获取所需要的类。

1、类与类加载器

两个类相等:类本身相等,并且使用同一个类加载器进行加载。这是因为每一个类加载器都拥有一个独立的类名称空间。

2、类加载器分类

  • 启动类加载器
  • 扩展类加载器
  • 应用程序类加载器

3、双亲委派模型

一个类加载器首先将类加载请求传送到父类加载器,只有当父类加载器无法完成类加载请求时才尝试加载。

好处:保证使用不同类加载器最终得到的是同一个对象

protected synchronized Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
    // First, check if the class has already been loaded
    Class c = findLoadedClass(name);
    if (c == null) {
        try {
            if (parent != null) {
                c = parent.loadClass(name, false);
            } else {
                c = findBootstrapClass0(name);
            }
        } catch (ClassNotFoundException e) {
            // If still not found, then invoke findClass in order
            // to find the class.
            c = findClass(name);
        }
    }
    if (resolve) {
        resolveClass(c);
    }
    return c;
}
  • 首先通过Class c = findLoadedClass(name);判断一个类是否已经被加载过。
  • 如果没有被加载过执行if (c == null)中的程序,遵循双亲委派的模型,首先会通过递归从父加载器开始找,直到父类加载器是Bootstrap ClassLoader为止。
  • 最后根据resolve的值,判断这个class是否需要解析。
原文地址:https://www.cnblogs.com/mingyi123/p/9383901.html