• JVM虚拟机


    概述

    Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎都是程序运行的必要数据。当遇到需要占用8位字节以上空间的数据项时,会按照高位在前的方式分割成若干个8位字节进行存储。

    Class文件格式中只有两种数据类型:无符号数

    • 无符号数属于最基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
    • 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,以“_info”结尾。表用来描述有层次关系的符合结构的数据。

    整个Class文件本质上就是一张表。

    无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的容量计数器加若干个连续的数据项的形式,称这一系列连续的某一类型的数据位某一类型的集合。

    class文件的组织结构

    1. 魔数
    2. 本文件的版本信息
    3. 常量池
    4. 访问标志
    5. 类索引
    6. 父类索引
    7. 接口索引集合
    8. 字段表集合
    9. 方法表集合
    10. 属性表集合

    1. 魔数

    每个Class文件的头4个字节称为魔数(Magic Number),唯一作用就是确定这个文件是否为一个能被虚拟机接受的Class文件。

    魔数的表示是用16进制的数:0xCAFEBABE 来表示。


    2. 本文件的版本信息

    在魔数后面的4个字节存储的就是Class文件的版本号:

    • 第5、第6个字节是次版本号(Minor Version)
    • 第7、第8个字节是主版本号(Major Version)

    Java版本号的计算

    Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1。

    高版本的JDK能向下兼容以前版本的Class文件,但不能兼容以后版本的Class文件,即使文件格式并未发生变化,虚拟机也拒绝执行超过其版本号的Class文件。


    3. 常量池

    3.1. 常量池的概念

    常量池可以理解为Class文件之中的资源仓库,他是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。

    常量池中主要存放两大类常量:

    • 字面量:接近于Java语言层面的常量概念,如文本字符串、声明为final的常量
    • 符号引用:就是我们定义的各种名字,包括下面三类:
      • 类和接口的全限定名
      • 字段的名称和描述符
      • 方法的名称和描述符

    3.2. 常量池的特点

    1. 常量池长度不固定

      常量池的大小是不固定的,因此常量池开头放置一个u2类型的无符号数,用来存储当前常量池的容量。JVM根据这个值就知道常量池的头尾来。

      这个无符号数从1开始,不是通常的从0开始

    2. 常量池中的常量由二维表来表示

      常量池开头有个常量池容器计数器,接下来就全是一个个常量了,只不过常量都是由一张张二维表构成,除了记录常量的值以外,还记录当前常量的相关信息。

    3. Class文件之中的资源仓库

    4. Class文件结构中与其他项目关联最多的数据类型

    5. 占用Class文件空间最大的数据项目之一

    3.3. 常量池中常量的类型

    刚才介绍了,常量池中的常量大体上分为:字面值常量 和 符号引用。在此基础上,根据常量的数据类型不同,又可以被细分为14种常量类型。这14种常量类型都有各自的二维表示结构。每种常量类型的头1个字节都是tag,用于表示当前常量属于14种类型中的哪一个。

    以CONSTANT_Class_info常量为例,它的二维表示结构如下:
    CONSTANT_Class_info表

    类型 名称 数量
    u1 tag 1
    u2 name_index 1

    tag表示当前常量的类型(当前常量为CONSTANT_Class_info,因此tag的值应为7,表示一个类或接口的全限定名);

    name_index表示这个类或接口全限定名的位置。它的值表示指向常量池的第几个常量。它会指向一个CONSTANT_Utf8_info类型的常量,它的二维表结构如下:
    CONSTANT_Utf8_info表:

    类型 名称 数量
    u1 tag 1
    u2 length 1
    u1 bytes length

    CONSTANT_Utf8_info表示字符串常量;

    tag表示当前常量的类型,这里应该是1;

    length表示这个字符串的长度;

    bytes为这个字符串的内容(采用缩略的UTF8编码)


    4. 访问标志

    访问标志(access_flags)占用两个字节,这个标志用于识别一些类或者接口层次的访问信息:

    • 这个Class是类还是接口
    • 是否定位为public类型
    • 是否定义为abstract类型
    • 是否被声名为final
    • .......

    访问标志中一共有16个标志位可用,当前只用了8个,其他要求一律为0。


    5. 类索引、父类索引和接口索引集合

    类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,接口索引集合(interfaces)是一组u2类型的数据的集合。

    Class文件由这三项数据来确定这个类的继承关系:

    • 类索引确定这个类的全限定名

    • 父类索引用于确定这个类的父类的全限定名

      java.lang.Object外,所有Java类的父类索引都不为0。

    • 接口索引集合描述这个类实现了哪些接口

      被实现的接口按接口顺序从左到右排列在接口索引集合中。

    类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的所引致指向一个类型为CONSTANT_Class_info的类描述符常量,该常量的bytes字段记录了本类、父类的全限定名。

    由于一个类的接口可能有好多个,因此需要用一个集合来表示接口索引,它在类索引和父类索引之后。这个集合头两个字节表示接口索引集合的长度,接下来就是接口的名字索引。


    6. 字段表集合

    字段表用于描述接口或者类中生命的变量。字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。

    类型 名称 数量
    u2 access_flags 1
    u2 name_index 1
    u2 descriptor_index 1
    u2 attributes_count 1
    attribute_info attributes attributes_count
    • access_flags

      字段的访问标志。

    • name_index

      本字段名称的索引,指向一个CONSTANT_Class_info类型的常量,存储了本字段的名字等信息。

    • descriptor_index

      字段和方法的描述符,用于描述本字段在Java中的数据类型等信息。

    • attributes_count

      属性表集合的长度。

    • attributes

      属性表集合。

    全限定名

    全限定名就是把类全名中的.替换成了/而已,为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个;来表示全限定名结束。

    简单名称

    简单名称就是指没有类型和参数修饰的方法或者字段名称。

    描述符

    描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。

    在描述符中:

    • 基本数据类型以及代表无返回值的void类型都用一个大写字符来表示。
    • 对象类型则用字符L加对象的全限定名来表示。
    标识字符 含义 标识字符 含义
    B 基本类型byte J 基本类型long
    C 基本类型char S 基本类型short
    D 基本类型double Z 基本类型boolean
    F 基本类型float V 特殊类型void
    I 基本类型int L 对象类型,如Ljava/lang/Object
    1. 对于数组类型,每一唯独将使用一个前置的“[”字符来描述,如一个定义为java.lang.String[][]类型的二维数组,将被记录为:[[Ljava/lang/String]],一个整形数组int[]将被记录为:[I

    2. 对于描述方法,按照“先参数列表,后返回值的顺序描述”,参数列表按照参数的严格顺序放在一组小括号“()”内。例如:void inc()的描述符为:()V。方法java.lang.String.toString()的描述符为:()Ljava/lang/String

      方法int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述符为:([CII[CIII)I


    7. 方法表集合

    在class文件中,所有的方法以二维表的形式存储,每张表来表示一个函数,一个类中的所有方法构成方法表的集合。
    方法表的结构和字段表的结构一致,只不过访问标志和属性表集合的可选项有所不同。

    类型 名称 数量
    u2 access_flags 1
    u2 name_index 1
    u2 descriptor_index 1
    u2 attributes_count 1
    attribute_info attributes attributes_count

    方法中的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面,属性表作为Class文件格式中最具拓展性的一种数据项目,在第8节中讲。


    8. 属性表集合

    在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。

    属性表集合对于各个属性表的顺序不再做严格要求,只要不与已有属性名重复即可。任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。

    为了正确解析Class文件,虚拟机与定义了一些虚拟机实现应当能识别的属性,对于这些属性,它的名称需要从常量池中引用一个CONSTANT_Utf9_info类型的常量来表示,而属性值的结构则是完全自定义的。

    一个符合规则的属性表应该有如下结构:

    类型 名称 数量
    u2 attribute_name_index 1
    u4 attribute_length 1
    u1 info attribute_length
  • 相关阅读:
    MySql面试题、知识汇总、牛客网SQL专题练习
    产生过拟合的原因
    《人类简史》这本烧脑书风靡全球的秘密是什么?
    厌食?暴食?试试这个 VR 新疗法
    协程、异步IO
    进程池
    进程(同步)锁
    特朗普变脸:同媒体“友好会谈”,怨媒体“死不悔改”
    多进程Queue
    redis 在 php 中的应用(事务 [ Transaction ] 篇)
  • 原文地址:https://www.cnblogs.com/yisany/p/10609195.html
Copyright © 2020-2023  润新知