Java 虚拟机程序执行:02 虚拟机的类加载机制

虚拟机的类加载机制

类加载的时机

JVM 会在程序第一次主动引用类的时候,加载该类,被动引用时并不会引发类加载的操作。也就是说,JVM 并不是在一开始就把一个程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次。那么什么是主动引用,什么是被动引用呢?

  • 主动引用

    • 遇到 new、getstatic、putstatic、invokestatic 字节码指令,例如:

      • 使用 new 实例化对象;
      • 读取或设置一个类的 static 字段(被 final 修饰的除外);
      • 调用类的静态方法。
    • 对类进行反射调用;
    • 初始化一个类时,其父类还没初始化(需先初始化父类);
      • 这点类与接口具有不同的表现,接口初始化时,不要求其父接口完成初始化,只有真正使用父接口时才初始化,如引用父接口中定义的常量。
    • 虚拟机启动,先初始化包含 main() 函数的主类;
    • JDK 1.7 动态语言支持:一个 java.lang.invoke.MethodHandle 的解析结果为 REF_getStatic、REF_putStatic、REF_invokeStatic。
  • 被动引用
    • 通过子类引用父类静态字段,不会导致子类初始化;
    • Array[] arr = new Array[10]; 不会触发 Array 类初始化;
    • static final VAR 在编译阶段会存入调用类的常量池,通过 ClassName.VAR 引用不会触发 ClassName 初始化。

也就是说,只有发生主动引用所列出的 5 种情况,一个类才会被加载到内存中,也就是说类的加载是 lazy-load 的,不到必要时刻是不会提前加载的,毕竟如果将程序运行中永远用不到的类加载进内存,会占用方法区中的内存,浪费系统资源。

类的显式加载和隐式加载

  • 显示加载:

    • 调用 ClassLoader#loadClass(className) 或 Class.forName(className)
    • 两种显示加载 .class 文件的区别:
      • Class.forName(className) 加载 class 的同时会初始化静态域,ClassLoader#loadClass(className) 不会初始化静态域;
      • Class.forName 借助当前调用者的 class 的 ClassLoader 完成 class 的加载。
  • 隐式加载:
    • new 类对象;
    • 使用类的静态域;
    • 创建子类对象;
    • 使用子类的静态域;
    • 其他的隐式加载,在 JVM 启动时:
      • BootStrapLoader 会加载一些 JVM 自身运行所需的 Class;
      • ExtClassLoader 会加载指定目录下一些特殊的 Class;
      • AppClassLoader 会加载 classpath 路径下的 Class,以及 main 函数所在的类的 Class 文件。

类加载的过程

类的生命周期

加载 --> 验证 --> 准备 --> 解析 --> 初始化 --> 使用 --> 卸载
       |<------- 连接 ------->|
|<------------- 类加载 ---------------->|

类的生命周期一共有 7 个阶段,其中前五个阶段较为重要,统称为类加载,第 2 ~ 4 阶段统称为连接,加载和连接中的三个过程开始的顺序是固定的,但是执行过程中是可以交叉执行的。接下来,我们将对类加载的 5 个阶段进行一一讲解。

加载

加载的 3 个阶段
  • 通过类的全限定名获取二进制字节流(将 .class 文件读进内存);
  • 将字节流的静态存储结构转化为运行时的数据结构;
  • 在内存中生成该类的 Class 对象;
    • HotSpot 虚拟机把这个对象放在方法区,非 Java 堆。
分类
  • 非数组类

    • 系统提供的引导类加载器
    • 用户自定义的类加载器
  • 数组类
    • 不通过类加载器,由 Java 虚拟机直接创建
    • 创建动作由 newarray 指令触发,new 实际上触发了 [L全类名 对象的初始化
    • 规则
      • 数组元素是引用类型

        • 加载:递归加载其组件
        • 可见性:与引用类型一致
      • 数组元素是非引用类型
        • 加载:与引导类加载器关联
        • 可见性:public

验证

  • 目的: 确保 .class 文件中的字节流信息符合虚拟机的要求。
  • 4 个验证过程:
    • 文件格式验证:是否符合 Class 文件格式规范,验证文件开头 4 个字节是不是 “魔数” 0xCAFEBABE
    • 元数据验证:保证字节码描述信息符号 Java 规范(语义分析)
    • 字节码验证:程序语义、逻辑是否正确(通过数据流、控制流分析)
    • 符号引用验证:对类自身以外的信息(常量池中的符号引用)进行匹配性校验
  • 这个操作虽然重要,但不是必要的,可以通过 -Xverify:none 关掉。

准备

  • 描述: 为 static 变量在方法区分配内存。
  • static 变量准备后的初始值:
    • public static int value = 123;

      • 准备后为 0,value 的赋值指令 putstatic 会被放在 <client>() 方法中,<client>()方法会在初始化时执行,也就是说,value 变量只有在初始化后才等于 123。
    • public static final int value = 123;
      • 准备后为 123,因为被 static final 赋值之后 value 就不能再修改了,所以在这里进行了赋值之后,之后不可能再出现赋值操作,所以可以直接在准备阶段就把 value 的值初始化好。

解析

  • 描述: 将常量池中的 “符号引用” 替换为 “直接引用”。

    • 在此之前,常量池中的引用是不一定存在的,解析过之后,可以保证常量池中的引用在内存中一定存在。
    • 什么是 “符号引用” 和 “直接引用” ?
      • 符号引用:以一组符号描述所引用的对象(如对象的全类名),引用的目标不一定存在于内存中。
      • 直接引用:直接指向被引用目标在内存中的位置的指针等,也就是说,引用的目标一定存在于内存中。

初始化

  • 描述: 执行类构造器 <client>() 方法的过程。
  • <client>() 方法
    • 包含的内容:

      • 所有 static 的赋值操作;
      • static 块中的语句;
    • <client>() 方法中的语句顺序:
      • 基本按照语句在源文件中出现的顺序排列;
      • 静态语句块只能访问定义在它前面的变量,定义在它后面的变量,可以赋值,但不能访问。
    • 与 <init>() 的不同:
      • 不需要显示调用父类的 <client>() 方法;
      • 虚拟机保证在子类的 <client>() 方法执行前,父类的 <client>() 方法一定执行完毕。
        • 也就是说,父类的 static 块和 static 字段的赋值操作是要先于子类的。
    • 接口与类的不同:
      • 执行子接口的 <client>() 方法前不需要先执行父接口的 <client>() 方法(除非用到了父接口中定义的 public static final 变量);
    • 执行过程中加锁:
      • 同一时刻只能有一个线程在执行 <client>() 方法,因为虚拟机要保证在同一个类加载器下,一个类只被加载一次。
    • 非必要性:
      • 一个类如果没有任何 static 的内容就不需要执行 <client>() 方法。

注:初始化时,才真正开始执行类中定义的 Java 代码。

类加载器

如何判断两个类 “相等”

  • “相等” 的要求

    • 同一个 .class 文件
    • 被同一个虚拟机加载
    • 被同一个类加载器加载
  • 判断 “相等” 的方法
    • instanceof 关键字
    • Class 对象中的方法:
      • equals()
      • isInstance()
      • isAssignableFrom()

类加载器的分类

  • 启动类加载器(Bootstrap)

    • <JAVA_HOME>/lib
    • -Xbootclasspath 参数指定的路径
  • 扩展类加载器(Extension)
    • <JAVA_HOME>/lib/ext
    • java.ext.dirs 系统变量指定的路径
  • 应用程序类加载器(Application)
    • -classpath 参数

双亲委派模型

  • 工作过程

    • 当前类加载器收到类加载的请求后,先不自己尝试加载类,而是先将请求委派给父类加载器

      • 因此,所有的类加载请求,都会先被传送到启动类加载器
    • 只有当父类加载器加载失败时,当前类加载器才会尝试自己去自己负责的区域加载
  • 实现
    • 检查该类是否已经被加载
    • 将类加载请求委派给父类
      • 如果父类加载器为 null,默认使用启动类加载器
      • parent.loadClass(name, false)
    • 当父类加载器加载失败时
      • catch ClassNotFoundException 但不做任何处理
      • 调用自己的 findClass() 去加载
        • 我们在实现自己的类加载器时只需要 extends ClassLoader,然后重写 findClass() 方法而不是 loadClass() 方法,这样就不用重写 loadClass() 中的双亲委派机制了
  • 优点
    • 自己写的类库同名类不会覆盖类库的类

原文地址:https://www.cnblogs.com/mengY/p/12254504.html

时间: 2024-10-07 10:11:34

Java 虚拟机程序执行:02 虚拟机的类加载机制的相关文章

Java虚拟机学习(1): 类加载机制

转自:微信公共号ImportNew 来源:java2000_wl 链接:blog.csdn.net/java2000_wl/article/details/8040633 JVM把class文件加载的内存,并对数据进行校验.转换解析和初始化,最终形成JVM可以直接使用的Java类型的过程就是加载机制. 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的生命周期包括了:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(

Java虚拟机笔记(一):类加载机制

一.概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 二.类加载的生命周期 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载(Loading).验证(Verification).准备(Preparation).解析(Resolution).初始化(Initialization).使用(Using)和卸载(Unloading)7个阶段. 其中:验证.准备.解析3

「深入Java虚拟机(4)」:类加载机制

类加载过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段. 其中类加载的过程包括了加载.验证.准备.解析.初始化五个阶段.在这五个阶段中,加载.验证.准备和初始化这四个阶段发生的顺序是确定的,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定).另外注意这里的几个阶段是按顺序开始,而不是按顺序进行或完成,因为这些阶段通常都是互相交叉地混合进行的,通常

深入理解JVM虚拟机6:深入理解JVM类加载机制

深入理解JVM类加载机制 简述:虚拟机把描述类的数据从class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 下面我们具体来看类加载的过程: 类的生命周期 类从被加载到内存中开始,到卸载出内存,经历了加载.连接.初始化.使用四个阶段,其中连接又包含了验证.准备.解析三个步骤.这些步骤总体上是按照图中顺序进行的,但是Java语言本身支持运行时绑定,所以解析阶段也可以是在初始化之后进行的.以上顺序都只是说开始的顺序,实际过

Java 虚拟机程序执行:01 Class文件的组成结构

Class文件的组成结构 Class 文件是一组以 8 位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在 Class 文件中,中间没有任何分隔符.Java 虚拟机规范规定 Class 文件采用一种类似 C 语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表,我们之后也主要对这两种类型的数据类型进行解析. 无符号数: 无符号数属于基本数据类型,以 u1.u2.u4.u8 分别代表 1 个字节.2 个字节.4 个字节和 8 个字节的无符号数,可以用它来描述数字.

1.虚拟机类加载机制

Java虚拟机的类加载机制 1.Java虚拟机的特点 1.1语言无关性 Java虚拟机并不进进支持java语言,可以支持JRuby,JPython,Scala等 1.2平台无关性 Java天生就是为了摆脱操作系统的束缚而产生的,提出了一个"编译一次,任意运行"的口号 总结: Java系的语言通过自己的编译器把源代码编译为字节码存放在class文件中,虚拟机通过加载字节码实现,虚拟机的语言无关性 字节码的功能远远比java语言强大,因为java不能提供的功能,其他的Java系语言可能可以

jvm之java类加载机制和类加载器(ClassLoader)的详解

当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载.连接.初始化3个步骤来对该类进行初始化.如果没有意外,JVM将会连续完成3个步骤,所以有时也把这个3个步骤统称为类加载或类初始化. 一.类加载过程 1.加载 加载指的是将类的class文件读入到内存,并为之创建一个java.lang.Class对象,也就是说,当程序中使用任何类时,系统都会为之建立一个java.lang.Class对象. 类的加载由类加载器完成,类加载器通常由JVM提供,这些类加载器也是前面所有程序运行的基础

java\c程序的内存分配

JAVA 文件编译执行与虚拟机(JVM)介绍 Java 虚拟机(JVM)是可运行Java代码的假想计算机.只要根据JVM规格描述将解释器移植到特定的计算机上,就能保证经过编译的任何Java代码能够在该系统上运行.本文首先简要介绍从Java文件的编译到最终执行的过程,随后对JVM规格描述作一说明. 一.Java源文件的编译.下载.解释和执行 Java应用程序的开发周期包括编译.下载.解释和执行几个部分.Java编译程序将Java源程序翻译为JVM可执行代码?字节码.这一编译过程同C/C++的编译有

Java基础加强——类加载机制

什么叫类加载 JVM把 .class 字节码文件加载到内存,并进行相关的校验.解析.初始化,最终转换为虚拟机可用的JAVA类型的过程,称为JVM类加载机制. (当然,JVM并不关心class文件的来源,什么?什么叫class文件?--每一个Java class文件都对一个Java类或者Java接口做出了全面描述) 类加载器的分类 启动(Bootstrap)类加载器:引导类装入器是用本地代码实现的类装入器,它负责将 <Java_Runtime_Home>/lib下面的核心类库或-Xbootcla