<<深入Java虚拟机>>-虚拟机类加载机制-学习笔记

类加载的时机

  • 遇到new、getstatic、putstatic或invokestatic这4个字节码指令时,如果类没有进行过初始化,则需要先触发其初始化。生成这4条指令最常见的Java场景是:使用new关键字实例化对象的时候、读取或设置一个类的静态字段(被final修饰、已在编译期把结果放入到常量池的静态变量除外)的时候,以及调用一个类的静态方法的时候。
  • 使用java.lang.reflect包的方法对类进行反射调用时候,如果类没有进行初始化,则需先触发其初始化。
  • 当初始化一个类的时候,发现其父类还没有进行过初始化,则需要先触发父类的初始化。
  • 当虚拟机启动时,用户需要制定一个要执行的主类(包含main()方法的那个类),虚拟机会先初始化这个类。

类加载过程

  类从被加载到虚拟机内存开始 ,到卸载出内存为止,它的整个生命周期分为:加载(Loading)、验证(Verification)、准备(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)和卸载(Unloading)七个阶段。其中验证、准备和解析三个部分统称为连接(Linking)。

加载

  •  在加载阶段,虚拟机需要完成以下三件事情:

    • 通过一个类的全限定名来获取定义此类的二进制字节流。
    • 将这个二进制字节流所代表的静态存储结构转化为方法区的运行时数据结构。
    • 在Java堆中生成一个能代表这个类的java.lang.Class对象,作为方法区这些数据的访问入口。
  • 验证

     验证是连接阶段的第一步,这一阶段的目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。

     不同虚拟机对类验证的实现不同,但大致上都会完成下面四个阶段的检验过程:

    • 文件格式验证

       这个阶段验证字节流是否符合Class文件格式的规范,并且能够被当前版本的虚拟机处理。例如:是否以魔数0xCAFEBABE(作用是表示是一个可以被虚拟机使用的Class文件)开头;主、次版本号是否在当前虚拟机的处理范围之类;指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量;Class文件中各个部分及文件本身是否有被删除或附加的其他信息。

       该验证阶段的主要目的是保证输入的字节流能够正确的被解析并存储于方法区之中,格式上符合描述一个Java类型信息的要求。

       这阶段的验证是基于字节流进行的,经过了这个阶段的验证之后,字节流才会进入内存的方法区中进行存储,所以后面的三个验证阶段全是基于方法区的存储结构进行的。

    • 元数据验证

       第二阶段是对字节码描述的信息进行语义分析的,以保证描述的信息符合Java语言规范的要求。

       如:这个类是否有父类(除了java.lang.Object外,其余的类都有父类);这个类的父类是否继承了不允许被继承的类(被final修饰的类);如果这个类不是抽象类,是否实现了其父类或接口中要求实现的所有方法。

    • 字节码验证

       第三个阶段是整个验证过程中最复杂的一个阶段,主要工作是进行数据流和控制流进行分析。这个阶段将对类的方法体进行校验分析。

       例如:保证方法体中的类型转换是有效的,比如可以将一个子类对象赋值给父类数据类型,这是安全的,但是把父类对象赋值给子类数据类型,则是危险和不合法的。

    • 符号引用验证

       最后一个阶段的校验发生在虚拟机将符号引用转化为直接引用的时候。

       例如:符号引用中通过字符串描述的全限定名是否能找到对应的类;符号引用的类、字段和方法的访问性(private、protected、default、public)是否可被当前类访问。

  • 准备

     准备阶段是正式为类变量分配内存并设置类变量初始值的阶段,这些内存都将在方法区中进行分配。这个时候进行内存分配的仅包括类变量(被static修饰的变量),而不包括实例变量,实例变量将会在对象实例化时随着对象一起分配在Java堆中。其次这里说的初始值“通常情况”下是数据类型的零值,假设一个类变量的定义为:

    public static int value = 123;

     那么变量value在准备阶段之后的初始值为0而不是123,因为这个时候尚未执行任何Java方法,而把value赋值为123的putstatic指令是程序被编译后,存放于类构造器<clinit>()方法之中,所以把value赋值为123的动作将在初始化阶段才会被执行。

     至于“特殊情况”,是指:

    public static final int value = 123;

     如果类字段的字段属性表中存在ConstantValue属性,则会在准备初始阶段初始化为指定的值,所以在标注为final之后,value的值在准备阶段初始化为123而不是0。

  • 解析

     解析阶段是虚拟机将常量池中的符号引用替换为直接引用的过程。

    • 符号引用(Symbolic References):符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能够无歧义地定位到目标即可。符号引用于虚拟机实现的内存布局无关,引用的目标并不一定加载到了内存中。
    • 直接引用(Direct References):直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关,同一符号引用在不同虚拟机实例上翻译出来的直接引用一般不同。如果有了直接引用,那么引用的目标一定存在了内存中。

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

  • 初始化

     类初始化阶段是类加载过程的最后一步,在这个阶段才真正执行类中定义的Java程序代码。

     在准备阶段,常量已经赋过一次系统要求的初始值,而在初始化阶段,则是根据程序员通过程序制定的主观去初始化类的变量。或者从另外一个角度来说:初始化阶段是执行类构造器<clinit>()方法的过程。

    • <clinit>()方法是由编译器自动收集类中所有类变量的赋值动作和静态语句块(static{}块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的,静态语句块中只能访问到定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但是不能访问。
    • <clinit>()方法与类的构造函数不同,它不需要显示地调用父类构造器,虚拟机会保证在子类的<clinit>()方法执行之前,父类的<clinit>()方法已经执行完毕。
    • 接口中如果有变量的赋值操作,那么接口与类一样也会生成<clinit>()方法。但接口与类不同的是,执行接口的<clinit>()方法不需要先执行父接口的<clinit>()方法,只有当父接口中的变量被使用时父接口才会被初始化。另外,接口的实现类在初始化时也一样不会执行接口的<clinit>()方法。
    • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁和同步,如果多个线程同时去初始化一个类,那么只有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待。
时间: 2024-12-07 02:04:44

<<深入Java虚拟机>>-虚拟机类加载机制-学习笔记的相关文章

深入Java虚拟机JVM类加载初始化学习笔记

1. Classloader的作用,概括来说就是将编译后的class装载.加载到机器内存中,为了以后的程序的执行提供前提条件. 2. 一段程序引发的思考: 风中叶老师在他的视频中给了我们一段程序,号称是世界上所有的Java程序员都会犯的错误. 一般不假思索的结论就是,a=1,b=1.给出的原因是:a.b都是静态变量,在构造函数调用的时候已经对a和b都加1了.答案就都是1.但是运行完后答案却是a=1,b=0. 为什么呢,这3句无非就是静态变量的声明.初始化,值的变化和声明的顺序还有关系吗?Java

【深入理解Java虚拟机】类加载机制

本文内容来源于<深入理解Java虚拟机>一书,非常推荐大家去看一下这本书. 本系列其他文章: [深入理解Java虚拟机]Java内存区域模型.对象创建过程.常见OOM [深入理解Java虚拟机]垃圾回收机制 1.类加载机制概述 虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可以被虚拟机直接使用的Java类型,这就是虚拟机的类加载机制. 在java中,类型的加载.连接和初始化过程都是在程序运行期间完成的,这种策略虽然会带来一些性能开销,但是却为jav

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

虚拟机的类加载机制 虚拟机的类加载机制 类加载的时机 类的显式加载和隐式加载 类加载的过程 类的生命周期 加载 加载的 3 个阶段 分类 验证 准备 解析 初始化 类加载器 如何判断两个类 “相等” 类加载器的分类 双亲委派模型 类加载的时机 JVM 会在程序第一次主动引用类的时候,加载该类,被动引用时并不会引发类加载的操作.也就是说,JVM 并不是在一开始就把一个程序就所有的类都加载到内存中,而是到不得不用的时候才把它加载进来,而且只加载一次.那么什么是主动引用,什么是被动引用呢? 主动引用

虚拟机的类加载机制

虚拟机的类加载机制 概述 虚拟机的类加载机制:虚拟机把描述类的数据从Class文件加载到内存,并对数据进行校验.转换解析和初始化,最终形成可被虚拟机使用的Java类型. 在Java语言里,类型的加载.连接和初始化过程都是在程序的运行期间完成的. 类加载的时机 类的生命周期:加载.连接(验证.准备.解析).初始化.使用.卸载. 类加载过程中加载.验证.准备.初始化和卸载阶段的顺序是可以确定的.但是解析阶段可以在初始化阶段之后再进行(为了支持Java语言的运行时绑定) 对于类加载的加载阶段,虚拟机并

JAVA的反射机制学习笔记(二)

上次写JAVA的反射机制学习笔记(一)的时候,还是7月22号,这些天就瞎忙活了,自己的步伐完全被打乱了~不能继续被动下去,得重新找到自己的节奏. 4.获取类的Constructor 通过反射机制得到某个类的构造器,然后调用该构造器创建该类的一个实例 Class<T>类提供了几个方法获取类的构造器. public Constructor<T> getConstructor(Class<?>... parameterTypes) 返回一个 Constructor 对象,它反

图解JAVA中的类加载机制(详细版)

注:本文为作者整理和原创,如有转载,请注明出处. 上一篇博文,把JAVA中的Class文件格式用图形的方式画了一下,逻辑感觉清晰多了,同时,也为以后查阅的方便. Class文件只是一种静态格式的二进制流,它只有被虚拟机加载进内存解析之后才会生成真正的运行时的结构,因此,搞清楚类加载机制不但有助于我们加深理解Class文件中各个字段的含义,同时也有利于我们更深入的了解JAVA代码背后的暗流涌动.比如new关键字背后,虚拟机都做了什么?JAVA中的哪些操作会真正导致类被加载?哪些操作又会导致类被初始

Java基础:类加载机制

之前的<java基础:内存模型>当中,我们大体了解了在java当中,不同类型的信息,都存放于java当中哪个部位当中,那么有了对于堆.栈.方法区.的基本理解以后,今天我们来好好剖析一下,java当中的类加载机制(其实就是在美团的二面的时候,被面试官问的懵逼了,特地来总结一下,免得下次再那么丢人 T-T). 我们都知道,在java语言当中,猴子们写的程序,都会首先被编译器编译成为.class文件(又称字节码文件),而这个.class文件(字节码文件)中描述了类的各种信息,字节码文件格式主要分为两

Java、JVM类加载机制

虚拟机的类加载机制是:JVM把描述类的数据从.class文件加载到内存,并对数据进行校验.解析.初始化,最终形成可以被JVM直接使用的Java类型. 加载.连接(验证.准备.解析).初始化.使用.卸载. 其中解析可以放到初始化之后. 加载: 一.根据类的全名(com.example.test.class)获取定义此类的二进制字节流 二.分析并将二进制字节流转化为方法区(存放类的信息.final.static变量) 三.产生java.lang.class对象 验证: 文件格式.类是不是抽象类,是不

《Java程序性能优化》学习笔记 Ⅳ JVM调优

第五章 JVM调优5.1 Java虚拟机内存模型1.JVM虚拟机将其内存数据分为程序计数器.虚拟机栈,本地方法栈,Java堆,和方法去等部分.5.2 JVM内存分配参数5.3 垃圾收集基础5.4 常用调优案例和方法5.5 使用JVM参数5.6 实战JVM调优 <Java程序性能优化>学习笔记 Ⅳ JVM调优