类加载机制(代码片段)

Fuu Fuu     2022-10-29     238

关键词:

类是在运行期间动态加载的。

类的生命周期

技术分享图片

 

包括以下 7 个阶段:

  • 加载(Loading)
  • 验证(Verification)
  • 准备(Preparation)
  • 解析(Resolution)
  • 初始化(Initialization)
  • 使用(Using)
  • 卸载(Unloading)

其中解析过程在某些情况下可以在初始化阶段之后再开始,这是为了支持 Java 的动态绑定。

类初始化时机

虚拟机规范中并没有强制约束何时进行加载,但是规范严格规定了有且只有下列五种情况必须对类进行初始化(加载、验证、准备都会随着发生):

  1. 遇到 new、getstatic、putstatic、invokestatic 这四条字节码指令时,如果类没有进行过初始化,则必须先触发其初始化。最常见的生成这 4 条指令的场景是:使用 new 关键字实例化对象的时候;读取或设置一个类的静态字段(被 final 修饰、已在编译器把结果放入常量池的静态字段除外)的时候;以及调用一个类的静态方法的时候。

  2. 使用 java.lang.reflect 包的方法对类进行反射调用的时候,如果类没有进行初始化,则需要先触发其初始化。

  3. 当初始化一个类的时候,如果发现其父类还没有进行过初始化,则需要先触发其父类的初始化。

  4. 当虚拟机启动时,用户需要指定一个要执行的主类(包含 main() 方法的那个类),虚拟机会先初始化这个主类;

  5. 当使用 JDK.7 的动态语言支持时,如果一个 java.lang.invoke.MethodHandle 实例最后的解析结果为 REF_getStatic, REF_putStatic, REF_invokeStatic 的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化;

以上 5 种场景中的行为称为对一个类进行主动引用。除此之外,所有引用类的方式都不会触发初始化,称为被动引用。被动引用的常见例子包括:

  • 通过子类引用父类的静态字段,不会导致子类初始化。
System.out.println(SubClass.value); // value 字段在 SuperClass 中定义
  • 通过数组定义来引用类,不会触发此类的初始化。该过程会对数组类进行初始化,数组类是一个由虚拟机自动生成的、直接继承自 Object 的子类,其中包含了数组的属性和方法。
SuperClass[] sca = new SuperClass[10];
  • 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。
System.out.println(ConstClass.HELLOWORLD);

类加载过程

包含了加载、验证、准备、解析和初始化这 5 个阶段。

1. 加载

加载是类加载的一个阶段,注意不要混淆。

加载过程完成以下三件事:

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

其中二进制字节流可以从以下方式中获取:

  • 从 ZIP 包读取,这很常见,最终成为日后 JAR、EAR、WAR 格式的基础。
  • 从网络中获取,这种场景最典型的应用是 Applet。
  • 运行时计算生成,这种场景使用得最多得就是动态代理技术,在 java.lang.reflect.Proxy 中,就是用了 ProxyGenerator.generateProxyClass 的代理类的二进制字节流。
  • 由其他文件生成,典型场景是 JSP 应用,即由 JSP 文件生成对应的 Class 类。
  • 从数据库读取,这种场景相对少见,例如有些中间件服务器(如 SAP Netweaver)可以选择把程序安装到数据库中来完成程序代码在集群间的分发。 ...

2. 验证

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

主要有以下 4 个阶段:

(一)文件格式验证

验证字节流是否符合 Class 文件格式的规范(是否以魔数0xCAFRBABE开头,主次版本号是否在当前虚拟机处理范围内),并且能被当前版本的虚拟机处理。

(二)元数据验证

对字节码描述的信息进行语义分析(这个类是否有父类,这个类的父类是否继承了不允许被继承的类如,final修饰的类),以保证其描述的信息符合 Java 语言规范的要求。

(三)字节码验证

通过数据流和控制流分析(方法体中的类型转换是否有效,保证跳转指令不会跳转到方法体以外的字节码指令上),确保程序语义是合法、符合逻辑的。

(四)符号引用验证

发生在虚拟机将符号引用转换为直接引用的时候,对类自身以外(常量池中的各种符号引用)的信息进行匹配性校验(符号引用中的类,字段和方法的访问性(private...)是否可被当前类访问)。

一阶段是基于字节流精选的,经过此阶段之后,字节流才会进入内存方法区中储存,而二三四阶段验证是基于方法区的储存结构进行的。

3. 准备

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

实例变量不会在这阶段分配内存,它将会在对象实例化时随着对象一起分配在 Java 堆中。

初始值一般为 0 值,例如下面的类变量 value 被初始化为 0 而不是 123。

public static int value = 123;

而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法中,所以把value赋值123的动作在初始化阶段才会被执行。

如果类变量是常量,那么会按照表达式来进行初始化,而不是赋值为 0。

public static final int value = 123;

编译时javac会为value生成ConstatValue属性,在准备阶段JVM就会根据ConstantValue的设置将value赋值为123。

4. 解析

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

符号引用(Symbolic Reference):符号引用以一组符号来描述所引用的目标,符号引用可以是任何形式的字面量,符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经在内存中。

直接引用(Direct Reference) :直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。

直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同的虚拟机实例上翻译出来的直接引用一般都不相同,如果有了直接引用,那引用的目标必定已经在内存中存在。

解析动作主要针对类或接口、字段、类方法、接口方法4类符号引用进行。

5. 初始化

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

在准备阶段,类变量已经赋过一次系统要求的初始值,而在初始化阶段,根据程序员通过程序制定的主观计划去初始化类变量和其它资源。

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

  • 是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static 块)中的语句合并产生的,编译器收集的顺序由语句在源文件中出现的顺序决定。特别注意的是,静态语句块只能访问到定义在它之前的类变量,定义在它之后的类变量只能赋值,不能访问。例如以下代码:
public class Test 
    static 
        i = 0;                // 给变量赋值可以正常编译通过
        System.out.print(i);  // 这句编译器会提示“非法向前引用”
    
    static int i = 1;

 

  • 与类的构造函数(或者说实例构造器 <init>())不同,不需要显式的调用父类的构造器。虚拟机会自动保证在子类的 <clinit>() 方法运行之前,父类的 <clinit>() 方法已经执行结束。因此虚拟机中第一个执行 <clinit>() 方法的类肯定为 java.lang.Object。

  • 由于父类的 <clinit>() 方法先执行,也就意味着父类中定义的静态语句块要优于子类的变量赋值操作。例如以下代码:

static class Parent 
    public static int A = 1;
    static 
        A = 2;
    


static class Sub extends Parent 
    public static int B = A;


public static void main(String[] args) 
     System.out.println(Sub.B);  // 输出结果是父类中的静态变量 A 的值 ,也就是 2。

 

  • <clinit>() 方法对于类或接口不是必须的,如果一个类中不包含静态语句块,也没有对类变量的赋值操作,编译器可以不为该类生成 <clinit>() 方法。

  • 接口中不可以使用静态语句块,但仍然有类变量初始化的赋值操作,因此接口与类一样都会生成 <clinit>() 方法。但接口与类不同的是,执行接口的 <clinit>() 方法不需要先执行父接口的 <clinit>() 方法。只有当父接口中定义的变量使用时,父接口才会初始化。另外,接口的实现类在初始化时也一样不会执行接口的 <clinit>() 方法。

  • 虚拟机会保证一个类的 <clinit>() 方法在多线程环境下被正确的加锁和同步,如果多个线程同时初始化一个类,只会有一个线程执行这个类的 <clinit>() 方法,其它线程都会阻塞等待,直到活动线程执行 <clinit>() 方法完毕。如果在一个类的 <clinit>() 方法中有耗时的操作,就可能造成多个进程阻塞,在实际过程中此种阻塞很隐蔽。

 

jvm的类加载机制(代码片段)

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制。 类加载的规则:全盘负责,当一个类加载器负责加载某个Class... 查看详情

java类加载机制(代码片段)

类加载器的种类 启动类加载器(BootstrapClassLoader)System.out.println(System.getProperty("sun.boot.class.path"));执行结果:D:javajdkjrelibesources.jar;D:javajdkjrelibt.jar;D:javajdkjrelibsunrsasign.jar;D:javajdkjrel 查看详情

深入理解java虚拟机——类加载机制(代码片段)

文章目录类加载机制类的生命周期类的加载过程1、加载2、验证3、准备4、解析5、初始化类的初始化时机类加载器类与类加载器类加载器分类双亲委派模型工作过程源码分析双亲委派机制的好处类加载机制类的生命周期一个类型... 查看详情

双亲委派机制(代码片段)

双亲委派机制双亲委派的原理:如果一个类加载器收到了类加载的请求,它首先不会自己去尝试加载这个类,而是把这个请求委托给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶... 查看详情

类加载机制(代码片段)

类是在运行期间动态加载的。类的生命周期 包括以下7个阶段:加载(Loading)验证(Verification)准备(Preparation)解析(Resolution)初始化(Initialization)使用(Using)卸载(Unloading)其中解析过程在某些情况下可以在初始化阶... 查看详情

类加载机制--浅谈(代码片段)

一、定义:  类加载(ClassLoading)是一种机制,他描述的是将字节码以文件形式加载到内存再经过连接、初始化后,最终形成可以被虚拟机直接使用的Java类型地过程。  ClassLoading包含了加载(Loading)、连接(Linking... 查看详情

类加载机制(代码片段)

概述与很多服务器应用一样,Tomcat也安装了各种类加载器(那就是实现了 java.lang.ClassLoader 的类)。借助类加载器,容器的不同部分以及运行在容器上的Web应用就可以访问不同的仓库(保存着可使用的类和资源)。这个机... 查看详情

jvm类加载机制一(代码片段)

类加载的过程什么是类加载?Java编译器会将我们编写好的代码编译成class字节码文件,JVM会把这些class字节码文件加载到内存中,并对加载的数据进行校验、准备、解析并初始化,这个过程就是类加载机制。类加载分为三个阶段... 查看详情

入门篇jvm类加载机制(代码片段)

类加载机制1.什么是类加载2.类加载的过程2.1加载2.2验证2.3准备2.4解析2.5初始化【重中之重之重中重】第一段代码:第二段代码:第三段代码:最后一段代码:总结1.什么是类加载首先你要知道一个类的从被加载到... 查看详情

类加载机制(代码片段)

虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验、转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型。Java类型的加载、连接和初始化过程都是在程序运行期间完成的。这种策略虽然会令类加载时稍... 查看详情

从源码透彻理解jvm类加载机制(代码片段)

本文我们深入分析一下类加载机制的原理,然后从源码角度看一下加载的过程。目录1类加载器分类初探2三种类加载器介绍3从源码角度分析加载过程3.1创建扩展类加载器3.2构造应用类加载器1类加载器分类初探JVM严格来讲支持... 查看详情

jvm类加载机制(代码片段)

文章目录一、类加载时机二、类加载过程2.1加载2.2验证2.3准备2.4解析2.5初始化三、类加载器3.1类与类加载器3.2双亲委派模型四、Tomcat类加载器架构原创不易,未经允许,请勿转载。博客主页:https://xiaojujiang.blog.csdn.net... 查看详情

jvm类加载机制(代码片段)

文章目录一、类加载时机二、类加载过程2.1加载2.2验证2.3准备2.4解析2.5初始化三、类加载器3.1类与类加载器3.2双亲委派模型四、Tomcat类加载器架构原创不易,未经允许,请勿转载。博客主页:https://xiaojujiang.blog.csdn.net... 查看详情

tomcat的类加载机制(代码片段)

    在前面 Java虚拟机:对象创建过程与类加载机制、双亲委派模型 文章中,我们介绍了JVM的类加载机制以及双亲委派模型,双亲委派模型的类加载过程主要分为以下几个步骤:(1)初始化ClassLoader时... 查看详情

java虚拟机原理android类加载机制(双亲委派机制|bootclassloader|pathclassloader|dexclassloader)(代码片段)

文章目录一、Android类加载机制二、双亲委派机制一、Android类加载机制Android中的类加载使用了双亲委派机制,如下图所示:在Android中提供了333个类加载器,BootClassLoader,PathClassLoader,DexClassLoader;双亲委派机制,是委派层级上的上下层级... 查看详情

jvm类加载机制详解(代码片段)

如下图所示,JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。加载加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang.Class对象,作为方法... 查看详情

jvm之类加载机制(代码片段)

JVM之类加载机制JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。 类加载五部分加载加载是类加载过程中的一个阶段,这个阶段会在内存中生成一个代表这个类的java.lang... 查看详情

tomcat类加载机制(代码片段)

1jvm类加载器1.1ClassLoader介绍Java的类加载,就是把字节码格式“.class”文件加载到JVM的方法区,并在JVM的堆区建立一个java.lang.Class对象的实例,用来封装Java类相关的数据和方法。JVM并不是在启动时就把所有的“.class”... 查看详情