精彩推荐

一百期Java面试题汇总SpringBoot内容聚合IntelliJ IDEA内容聚合Mybatis内容聚合

Class类文件的结构

任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(类和接口也可以用反射的方式通过类加载器直接生成)

Class文件时一组以8位字节为基础单位的二进制流,各个数据都严格按照顺序紧凑排列在Class文件中,没有任何分隔符。

Class文件格式采用一种类似C语言结构体的伪结构存储数据,这种结构中只包含无符号数两种类型。

无符号数

  • 无符号数属于基本数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节、8个字节的无符号数

  • 无符号数可以用来描述数字、引用、数量值或者按照utf编码的字符串值。

  • 表是由多个无符号整数或者其他表构成的符合数据类型,都由"_info"结尾。

  • 表用于描述有层次关系的复合结构的数据,整个Class文件实际上就是一张表。

ClassFile {    u4             magic;    u2             minor_version;    u2             major_version;    u2             constant_pool_count;    cp_info        constant_pool[constant_pool_count-1];    u2             access_flags;    u2             this_class;    u2             super_class;    u2             interfaces_count;    u2             interfaces[interfaces_count];    u2             fields_count;    field_info     fields[fields_count];    u2             methods_count;    method_info    methods[methods_count];    u2             attributes_count;    attribute_info attributes[attributes_count];}

编辑器用16进制打开类文件

0  1  2  3  4  5  6  7  8  9  A  B  C  D  E  Fca fe ba be 00 00 00 34 00 3f 0a 00 0a 00 2b 0800 2c 09 00 0d 00 2d 06 40 59 00 00 00 00 00 0009 00 0d 00 2e 09 00 2f 00 30 08 00 31 0a 00 32....

magic

类文件第一个数据为u4,我们查看16进制文件前4个字符是cafebabe,它用来确定这个文件是否为一个能被虚拟机接受的Class文件。

minor_version、major_version

u4后的两个u2,即00 00 00 34用来代表jdk的主次版本。

常量池

常量池是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据,也是Class文件中第一个出现的表类型数据项目。

存放类型

存放类型包含:

  • 字面量:文本字符串、声明为final的常量值等。

  • 符号引用:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。不会保存它们最终的信息,因为这是无意义的,它们不经过运行期转换的话得不到真正的入口地址,也就无法被虚拟机使用

类加载的时机

类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括以下 7 个阶段:加载、验证、准备、解析、初始化、使用、卸载

加载、验证、准备、初始化和卸载这 5 个阶段的顺序是确定的,类的加载过程必须按照这种顺序按部就班地开始(注意是“开始”,而不是“进行”或“完成”),而解析阶段则不一定:它在某些情况下可以在初始化后再开始,这是为了支持 Java 语言的运行时绑定(多态)。

对类进行初始化的情况

虚拟机规范严格规定了有且只有5钟情况必须立即对类进行初始化:

  • 使用 new、getstatic、putstatic、或invokestatic这四条字节码命令时,后三个命令分别代表对类的静态变量进行操作,调用类的静态方法。生成这四条指令最常见的场景为:new一个对象的时候、读取或者赋值给类的静态变量的时候(被final修饰的除外,因为已经在编译期把结果放入了常量池)、以及调用一个静态方法的时候。

  • 反射调用类的时候,如果类未被初始化需要进行初始化

  • 当实例化某类时,其父类没被初始化,需要初始化父类

  • 当虚拟机启动时,用户指定的执行的主类(包含main方法的类),虚拟机会先初始化这个主类

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

这 5 种场景称为对一个类进行主动引用(有且只有这五种才可以触发类的初始化),除此之外,其它所有引用类的方式都不会触发初始化,称为被动引用

被动引用反例

/** * 被动引用 Demo1: * 通过子类引用父类的静态字段,不会导致子类初始化。 */class SuperClass {    static {        System.out.println("SuperClass init!");    }    public static int value = 123;}class SubClass extends SuperClass {    static {        System.out.println("SubClass init!");    }}public class NotInitialization {    public static void main(String[] args) {        System.out.println(SubClass.value);        // SuperClass init!    }}

对于静态字段,只有直接定义这个字段的类才会被初始化,因此通过其子类来引用父类中定义的静态字段,只会触发父类的初始化而不会触发子类的初始化。

/** * 被动引用 Demo2: * 通过数组定义来引用类,不会触发此类的初始化。 */public class NotInitialization {    public static void main(String[] args) {        SuperClass[] superClasses = new SuperClass[10];    }}

new数组对象时并不会触发SuperClass类的初始化,而是在这段代码里触发一个名为Lorg.fenixsoft.classloading.SuperClass的类初始化,他直接继承自Object类,由虚拟机来产生和触发。

/** * 被动引用 Demo3: * 常量在编译阶段会存入调用类的常量池中,本质上并没有直接引用到定义常量的类,因此不会触发定义常量的类的初始化。 */class ConstClass {    static {        System.out.println("ConstClass init!");    }    public static final String HELLO_ZHIYIN = "Hello ZhiYin";}

public class NotInitialization {    public static void main(String[] args) {        System.out.println(ConstClass.HELLO_ZHIYIN);    }}

JVM在编译期进行了传播优化,将ConstClass类中的常量放入了NotInitialization的常量池中,事实上这个常量已经和ConstClass类没有了联系,不会触发初始化。

类加载的过程

类加载过程包括 5 个阶段:加载、验证、准备、解析和初始化。

加载

“加载”是“类加载”过程的第一步,在加载阶段,虚拟机需要完成以下三件事情:

  • 通过一个类的全限定名(com.zhiyin.TestClass)来获取定义此类的二进制字节流

  • 将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构

  • 在内存中(HostSpot在方法区)生成一个代表该类的java.lang.Class对象,作为方法区这个类的各种数据的访问入口

获取二进制字节流

对于 Class 文件,虚拟机没有指明要从哪里获取、怎样获取。除了直接从编译好的 .class 文件中读取,还有以下几种方式:

  • 从 zip 包中读取,如 jar、war等

  • 从网络中获取,如 Applet

  • 通过动态代理技术生成代理类的二进制字节流

  • 由 JSP 文件生成对应的 Class 类

  • 从数据库中读取,如 有些中间件服务器可以选择把程序安装到数据库中来完成程序代码在集群间的分发。

“数组类”与“非数组类”加载情况的不同

  • 非数组类由加载器来进行加载

  • 数组类由于没有字节流,由jvm直接创建,如果数组中的对象是引用类,递归采用加载器进行加载

注意事项

  • 虚拟机规范未规定 Class 对象的存储位置,对于 HotSpot 虚拟机而言,Class 对象比较特殊,它虽然是对象,但存放在方法区中。

  • 加载阶段与连接阶段的部分内容交叉进行,加载阶段尚未完成,连接阶段可能已经开始了。但这两个阶段的开始时间仍然保持着固定的先后顺序。

验证

验证意义

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

验证过程

  1. 文件格式验证:第一阶段是验证字节流是否符合Class文件的规范

  • 是否以0xCAFEBABE开头

  • 主次版本号是否能被当前版本虚拟机处理

  • 常量池中常量是否有不被支持的类型

  • 指向常量的各种索引值中是否有指向不存在的常量或不符合类型的常量

  • Class文件中各个部分是否有被删除或额外添加的内容等….

  1. 元数据验证:第二阶段是对字节码描述的信息进行语义分析,保证符合Java语言要求

  • 这个类是否有父类(除了Object之外都应该有父类)

  • 这个类是否继承了不允许被继承的类(final类)

  • 如果不是抽象类是否实现了父类或接口中要求被实现的方法

  • 类中的字段和方法是否与父类发生矛盾(同名同参函数等)….

  1. 字节码验证:本阶段是验证过程中最复杂的一个阶段,是对方法体进行语义分析,保证方法在运行时不会出现危害虚拟机的事件。

  2. 符号引用验证:最后一个阶段的验证时发生在虚拟机将符号引用转化为直接引用的时候。这个动作在连接的第三阶段——解析阶段中发生,校验以下内容:

  • 符号引用中通过字符串描述的全限定名是否能找到对应的类

  • 在指定类中是否存在合法的字段、方法描述符

  • 检查符号引用中的类、字段、方法是否可被当前类访问(private、protected、public、default)

    符号引用验证如果没有通过,会抛出一个java.lang.IncompatibleClassChangeError异常的子类,如常见的java.lang.NoSuchFieldError、java.lang.NoSuchMethodError

注意事项

对于虚拟机的类加载机制而言,验证是一个很重要的、但不是必须的(因为对程序运行期无影响)一个阶段,如果运行的全部代码(包括自己编写的以及第三方包中的代码)都已经被反复使用和验证过,那么在实施阶段就可以使用-Xverify:none来关闭大部分类的验证过程,以缩短虚拟机类加载的时间

准备

准备阶段是正式为类变量(被 static修饰的变量)分配内存并设置类变量初始值(通常为零值,引用类型为null)的阶段,这些变量所使用的内存将在方法去区中进行分配。如下语句中:

public static int value = 666;

value变量在准备阶段之后初始值变为0而不是666,变为666的过程是在初始化阶段进行。

上面说到通常情况下是零值,特殊情况为该变量同时被final修饰,是常量。

public static final int value = 666;

编译时value就会生成ConstantValue属性(定义为常量),在准备阶段虚拟机就会依据ConstantValue的设置将value赋值为666.

解析

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

class二进制字节流中的引用关系都是符号引用没有真正的意义,解析之后将会变成直接指向目标的指针。

初始化

类初始化阶段是类加载过程的最后一步,是执行类构造器方法的过程。

类构造器方法是由编译器自动收集类中的所有类变量的赋值动作和静态语句块(static {} 块)中的语句合并产生的,编译器收集的顺序是由语句在源文件中出现的顺序所决定的。

静态语句块中只能访问定义在静态语句块之前的变量,定义在它之后的变量,在前面的静态语句块中可以赋值,但不能访问。如下方代码所示:

public class Test {    static {        i = 0;  // 给变量赋值可以正常编译通过        System.out.println(i);  // 这句编译器会提示“非法向前引用”    }    static int i = 1;}

类构造器方法不需要显式调用父类构造器,虚拟机会保证在子类的类构造器方法执行之前,父类的类构造器方法方法已经执行完毕。

由于父类的类构造器方法方法先执行,意味着父类中定义的静态语句块要优先于子类的变量赋值操作。如下方代码所示:

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); // 输出 2}

类构造器方法不是必需的,如果一个类没有静态语句块,也没有对类变量的赋值操作,那么编译器可以不为这个类生成它。

接口中不能使用静态代码块,但接口也需要通过类构造器方法为接口中定义的静态成员变量显式初始化。但接口与类不同,接口的类构造方法不需要先执行父类的类构造方法方法,只有当父接口中定义的变量使用时,父接口才会初始化。

虚拟机会保证一个类的类构造方法在多线程环境中被正确加锁、同步。如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的类构造方法。

参考《深入理解Java虚拟机》、Jvm官方规范

END

我知道你 “在看

torch的model加载完怎么看_看完这篇后,别再说你不懂JVM类加载机制了~相关推荐

  1. 看完这篇后,别再说你不懂JVM类加载机制了~

    精彩推荐 一百期Java面试题汇总 SpringBoot内容聚合 IntelliJ IDEA内容聚合 Mybatis内容聚合 Class类文件的结构 任何一个Class文件都对应着唯一一个类或接口的定 ...

  2. java加载并运行虚拟机_《深入理解Java虚拟机》- Java虚拟机是如何加载Java类的?...

    Java虚拟机是如何加载Java类的?  这个问题也就是面试常问到的Java类加载机制.在年初面试百战之后,菜鸟喜鹊也是能把这流程倒背如流啊!但是,也只是字面上的背诵,根本就是像上学时背书考试一样. ...

  3. 新手必看:访问url到加载全过程详解(看完不会我吃shi)

    新手必看:访问url到加载全过程详解(看完不会我吃shi) 1.放在前面:新手必须知道的那些概念 1.1 什么是IP.域名.主机名.url.服务器 1.2 http & https 1.3 O ...

  4. JVM 类加载机制与加载过程

    JVM的类加载机制:在代码编译后,就会生成JVM(Java虚拟机)能够识别的二进制字节流文件(*.class).而JVM把Class文件中的类描述数据从文件加载到内存,并对数据进行校验.转换解析.初始 ...

  5. Bootstrap tab页签刷新加载不显示,只有点击其他标签后第一个才显示

    Bootstrap tab页签刷新加载不显示,只有点击其他标签后第一个才显示 解决方法: 在tab-content里面的tab-pane中添加active <!-- 导航区 --> < ...

  6. torch cpu版加载权重

    state_dict = torch.load(config["pretrain_snapshot"],map_location=lambda storage, loc: stor ...

  7. 深度学习——09模型的保存:torch.save()、加载:torch.load()

    两种方式 保存模型主要分为两类: 1.保存整个模型 2.保存模型参数 1.第一种 结构模型+模型参数 保存整个网络模型,加载整个网络模型(可能比较耗时) # 保存方式1 torch.save(vgg1 ...

  8. cascader 动态加载 回显_ElementUI cascader级联动态加载回显和搜索看这个就够了

    这一篇是上一次讨论cascader级联动态加载回显问题的延续,文末有链接. 以下是思考和开发的过程,不感兴趣可以直接看使用文档. https://github.com/zhuss/lazy-casca ...

  9. elementui 加载中_ElementUI cascader级联动态加载回显和搜索看这个就够了

    这一篇是上一次讨论cascader级联动态加载回显问题的延续,文末有链接. 以下是思考和开发的过程,不感兴趣可以直接看使用文档. https://github.com/zhuss/lazy-casca ...

最新文章

  1. php返回json的结果
  2. Vue项目实战01: vue里父传子 传事件(easy)
  3. 2019年开源安全现状调查报告发布
  4. 【PPT分享】特斯拉远景规划及中国供应链的机遇.pdf(附119页ppt下载链接)
  5. Scipy教程 - 统计函数库scipy.stats
  6. python脚本(比较两个Excel表格的不同并标记)
  7. google chrome 同步书签 查看gmail邮箱 谷歌浏览器同步助手
  8. pytorch中tensor转numpy
  9. Barcode Producer for Mac(创建条形码软件)
  10. 【飞桨】GAN:U-GAT-IT【2020 ICLR】论文研读
  11. Linux设备模型(2)——Kobject
  12. JVM 直接内存的使用与回收
  13. 深度学习(Deep Learning)
  14. 操作系统笔记(3)——同步与互斥
  15. 莱布尼兹乘积微分公式证明纠错
  16. VS 2022永久密钥
  17. 第五章--第八章 因果图 正交试验表 状态转移 流程分析
  18. Vue移动端UI框架
  19. 转载的socks4 socks5 rfc1928一大堆
  20. vba word 查找_知乎高赞:Word中有哪些批量神技,让你相见恨晚

热门文章

  1. python的一些基础小结总结
  2. c语言学习-对一个百分制的成绩给出相应的等级(如90分以上A,80分以上B等
  3. 重构是什么、为什么要重构
  4. 用于数据输入的基本WPF窗口功能
  5. 学习3D图形引擎中使用的基本数学
  6. Python 之父 Guido van Rossum 宣布
  7. SqlBulkCopy批量数据导入(EF实现)
  8. matlab流量结构分析,科学网-分享求解“结构分解分析(SDA)”各项均值的MATLAB程序-计军平的博文...
  9. 云南省计算机专业技术,云南省2018年下半年全国计算机技术与软件专业技术资格(水平)考试顺利举行...
  10. python plot函数label_python – Matplotlib Contour Clabel位置