1.平台无关性和语言无关性

  Oracle公司以及其他虚拟机发行商发布过许多可以运行在各种不同硬件平台和操作系统上的Java虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码,从而实现了程序的“一次编写,到处运行”各种不同平台的Java虚拟机,以及所有平台都统一支持的程序存储格式——字节码(Byte Code)是构成平台无关性的基石。
  时至今日,商业企业和开源机构已经在Java语言之外发展出一大批运行在Java虚拟机之上的语言,如Kotlin、Clojure、Groovy、JRuby、JPython、Scala等。实现语言无关性的基础仍然是虚拟机和字节码存储格式。

2.Class类文件结构简介

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

  根据《Java虚拟机规范》的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:“无符号数”和“”。

  无符号数:属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
  :是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上也可以视作是一张表,这张表由下图所示的数据项按严格顺序排列构成。具体内容在下面一一展开。

 

3.Class类文件结构

3.1文件准备

先准备简单的代码,生成class文件,使用16进制编辑器查看(我使用的winhex查看)

package com.ruoyi.weixin.user;

public class Hello {

    private static final int a = 10;

    private static int b;

    private int c;

    public int inc(){
return c - 1;
}
}

3.2magic魔数

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

3.2版本号

  紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号(MinorVersion),第7和第8个字节是主版本号(Major Version)
  查看偏移地址0x00000004-0x00000007,得到次版本号是0(=0x0000),主版本号是52(=0x0034),也就是版本号是52.0,对应的是jdk8。

  Java的版本号是从45开始的,JDK 1.1之后的每个JDK大版本发布主版本号向上加1,高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,因为《Java虚拟机规范》在Class文件校验部分明确要求了即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件

3.3常量池

3.3.1常量池容器计数器

  紧接着主、次版本号之后的是常量池入口,由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count),指定常量池中有多少个常量,占2个字节。

  查看偏移地址0x00000008-0x00000009,得到该常量池中一共有25(=26-1)个常量。减1的原因是常量是从1开始计数的。在Class文件格式规范制定之时,设计者将第0项常量空出来是有特殊考虑的,这样做的目的在于,如果后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,可以把索引值设置为0来表示。Class文件结构中只有常量池的容量计数是从1开始。

  

3.3.2常量池常量

  常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)。
  字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。
  符号引用则属于编译原理方面的概念,主要包括下面几类常量:
    被模块导出或者开放的包(Package)
    类和接口的全限定名(Fully Qualified Name)
    字段的名称和描述符(Descriptor)
    方法的名称和描述符
    方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
    动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)

3.3.3常量表

  常量池中每一项常量都是一个表,常量表中分别有17种不同类型的常量,这17类表都有一个共同的特点,表结构起始的第一位是个u1类型的标志位(tag),代表着当前常量属于哪种常量类型。17种常量类型所代表的具体含义如

 

3.3.4常量解析

下面就前面两个常量做一个解析

第一个常量0A 00 04 00 16(从0A判断常量类型,就可以知道这个常量的字节数)

OA是10,也就是说第一个常量的标志是10,查看结果总表,发现它是CONSTANT_Methodref_info,它包含三部分,共占5个字节

tag:OA,是10

index:00 04,是4,指向常量池中的第4个常量

index:00 16是第二个index,是22,指向常量池中第22个常量

第二个常量09 00 03 00 17

09是9,也就是标志是9,查看结果总表,发现它是CONSTANT_Fieldref_info,它包含三部分,共占5个字节

tag:09,是9

index:00 03,是3,指向常量池中的第4个常量

index:00 17是第二个index,是23,指向常量池中第23个常量

3.3.5使用javap来解析class文件

输入命令,输出字节码内容

javap -verbose Hello.class

  可以看到共有25个常量,和常量计数器的数量一致,第一个是methodref,两个index指向第4和22个常量,第二个是fieldref,两个index指向第3和23个常量,和上面解析的是一样的

 3.4访问标志

  在常量池结束之后,紧接着的2个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等等,具体见下图

  这里标志位是00 21,也就是0x0001和0x0020的和,表示被public修饰,且允许invokespecial字节码指令的新语义

3.5类索引、父类索引、接口索引集合

  类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定该类型的继承关系。
  类索引用于确定这个类的全限定名,
  父类索引用于确定这个类的父类的全限定名。由于Java语言不允许多重继承,所以父类索引只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了java.lang.Object外,所有Java类的父类索引都不为0。
  接口索引集合就用来描述这个类实现了哪些接口

  如上图所示:

  类索引是00 03,指向常量池中第三个常量

  

  父类索引是00 04,指向第四个常量

  

  接口索引集合 00 00 ,没有实现接口

3.6字段表集合

  后面紧跟着的就是字段学习。首先就是字段数量描述(字段表集合的大小),然后是字段表集合。

3.6.1简介

  字段表(field_info)用于描述接口或者类中声明的变量。Java语言中的“字段”(Field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。字段可以包括的修饰符有字段的作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否被序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。上述这些信息中,各个修饰符都是布尔值,要么有某个修饰符,要么没有,很适合使用标志位来表示。而字段叫做什么名字、字段被定义为什么数据类型,这些都是无法固定的,只能引用常量池中的常量来描述。表6-8中列出了字段表的最终格式
 
3.6.2字段表结构
  一个字段对于一个字段表,包含5个内容
 

3.6.3字段表中的access_flags

 3.6.4name_index

  表示字段的简单名称,指向常量池
 

 3.6.5descriptor_index

  字段和方法的描述符,指向常量池

 3.6.6attribute_count

  属性表计数器,字段表可以在属性表中附加描述零至多项的额外信息。这些附加信息存储在attributes(属性表集合)中,这个的值就是attribute_info的数量
 
3.6.7attributes属性表集合

属性表(attribute_info),这个更具体的下面再讲
 
3.6.8示例解析

字段数量:00 03 表示有三个字段,之后正式进入字段表集合

access_flags:00 1A表示 0x0002和0x0008和0x0010的和 表示private static final

name_index:00 05 指向第五个常量,字段名为a

descriptor_index:00 06指向第6个常量,值为 I,对于的 基本类型为int

再后面的属性表集合先不看,后面再讲

3.7方法表集合

Class文件存储格式中对方法的描述与对字段的描述采用了几乎完全一致的方式

  方法的定义可以通过访问标志、名称索引、描述符索引来表达清楚,但方法里面的代码去哪里了?方法里的Java代码,经过Javac编译器编译成字节码指令之后,存放在方法属性表集合中一个名为“Code”的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目,将在下一节中详细讲解

 3.7.1access_flags标志位

3.7.2 解析

access_flag:00 01 表示public

name_index:00 12 指向第18个常量,描述方法名

describle_index:00 13 指向第19个常量,描述方法学习,无参数,返回值int

属性表个数:00 01  1个属性表

属性表:后面讲

JAVA虚拟机11-Class文件结构的更多相关文章

  1. 深入理解Java虚拟机(类文件结构)

    深入理解Java虚拟机(类文件结构) 欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 之前在阅读 ASM 文档时,对于已编译类的结构.方法描述符.访问标志.ACC_PUBLIC.ACC_P ...

  2. 《深入理解Java虚拟机》类文件结构

    上节学习回顾 在上一节当中,主要以自己的工作环境简单地介绍了一下自身的一些调优或者说是故障处理经验.所谓百变不离其宗,这个宗就是我们解决问题的思路了. 本节学习重点 在前面几章,我们宏观地了解了虚拟机 ...

  3. 深入理解Java虚拟机(类文件结构+类加载机制+字节码执行引擎)

    目录 1.类文件结构 1.1 Class类文件结构 1.2 魔数与Class文件的版本 1.3 常量池 1.4 访问标志 1.5 类索引.父索引与接口索引集合 1.6 字段表集合 1.7 方法集合 1 ...

  4. Java虚拟机 - Class类文件结构

    [深入Java虚拟机]之二:Class类文件结构 平台无关性 Java是与平台无关的语言,这得益于Java源代码编译后生成的存储字节码的文件,即Class文件,以及Java虚拟机的实现.不仅使用Jav ...

  5. Java虚拟机,类文件结构深度解析

    Java类文件结构 Java虚拟机不和包括Java在内的任何语言绑定,只与 "Class文件" 这种特定的二进制文件所关联, Class文件中包含了Java虚拟机指令集合符号表以及 ...

  6. Java虚拟机11:运行期优化

    前言 http://www.cnblogs.com/xrq730/p/4839245.html,HotSpot采用的是解释器+编译器并存的架构,之前的这篇文章里面已经讲过了,本文只是把即时编译器这块再 ...

  7. Java虚拟机——Class类文件结构

    Class文件格式采用一种类似C语言结构体的结构来存储数据,这种数据结构只有两种数据类型:无符号数和表.      无符号数属于基本的数据类型,数据项的不同长度分别用u1, u2, u4, u8表示, ...

  8. 深入理解Java虚拟机(七)——类文件结构

    Java的无关性 由于计算机领域中有很多操作系统和硬件平台同时在竞争,所以,很多编程语言的程序设计会与其运行的平台和操作系统产生耦合,这样就大大增加了程序员的工作,为了适应不同的平台,需要修改很多代码 ...

  9. 【Java虚拟机11】线程上下文类加载器

    前言 目前学习到的类加载的知识,都是基于[双亲委托机制]的.那么JDK难道就没有提供一种打破双亲委托机制的类加载机制吗? 答案是否定的. JDK为我们提供了一种打破双亲委托模型的机制:线程上下文类加载 ...

  10. Java虚拟机11:内存分配原则

    前言 JVM的自动内存管理要自动化的解决两个问题:对象分配内存以及回收分配给对象的内存.对象的内存分配一般是指在堆上分配,少数情况下也可能会直接分配在老年代上,对象主要分配在新生代的Eden 区上,如 ...

随机推荐

  1. Flask(一)

    pip install flask 依赖wsgi flask框架是基于werkzegu的wsgi实现,flask没有自己的wsgi 用户一旦请求,就会调用app.__call__方法 flask 路由 ...

  2. mindxdl---common---db_handler.go

    // Copyright (c) 2021. Huawei Technologies Co., Ltd. All rights reserved.// Package common this file ...

  3. oracle问题记录

    ORA-01034: ORACLE not available [oracle@ilanni ~]$ sqlplus /nolog SQL*Plus: Release 10.2.0.1.0 – Pro ...

  4. linux系统移植

    1 linux环境搭建 1.1 添加交叉开发工具链 新建如下工程目录: gcc-4.6.4.tar.xz #拷贝 tar -Jxvf gcc-4.6.4.tar.xz #解压 cd ./gcc-4.6 ...

  5. 模拟Promise的功能

    模拟Promise的功能,  按照下面的步骤,一步一步 1. 新建是个构造函数 2. 传入一个可执行函数 函数的入参第一个为 fullFill函数 第二个为 reject函数: 函数立即执行, 参数函 ...

  6. 使用python玩转二维码!速学速用!⛵

    作者:韩信子@ShowMeAI Python3◉技能提升系列:https://www.showmeai.tech/tutorials/56 本文地址:https://showmeai.tech/art ...

  7. jquery 操作样式

    <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8&quo ...

  8. 【Java技术】String类的使用

    属于引用类型,在java.lang包下,类似的还有Integer.Character.Boolean.Math 常用方法: char charAt(int index)返回 char指定索引处的值. ...

  9. BIO和NIO的区别和原理

    BIO BIO(Blocking IO) 又称同步阻塞IO,一个客户端由一个线程来进行处理 当客户端建立连接后,服务端会开辟线程用来与客户端进行连接.以下两种情况会造成IO阻塞: 服务端会一直阻塞,直 ...

  10. TypeScript 之 控制流分析(Control Flow Analysis)

    控制流分析(Control Flow Analysis) 描述: CFA 几乎总是采用联合,基于代码逻辑去减少联合里面的类型数量. 大多数时候,CFA 在自然的JavaScript布尔逻辑中工作,但是 ...