一.程序存储格式

  • 统一的程序存储格式:不同平台的虚拟机于所有平台都统一使用程序存储格式——字节码(ByteCode);
  • Java 虚拟机不关心 Class 文件的来源,而只和“Class文件"这种二进制文件格式关联,也就是说Java虚拟机只认识“Class"文件;
  • Java 编译器可以把 Java 程序代码编译成虚拟机所需要的Class 文件;

二.Class 文件结构

Class 文件是以 8 个字节为单位的二进制流,紧凑排列,中间没有空隙;如果想查看一个 Class 文件除了通过 winHex 编译器看到字节码,也可以通过 javap -verbose xxx.Class 输出字节码内容,这样看起来比较直观。
1、基本类型
无符号数:

  • 定义:基本的数据类型,u1、u8表示1个字节,8个字节。
  • 使用:可以用来描述数字、索引、引用,utf-8格式的字符串;

表:

  • 定义:多个无符号数和其他表组成的复合数据类型;通常以“_info” 结尾。
  • 使用:描述有层次关系的复合结构数据;

2、魔数与版本

  • 魔数:每个Class文件的头4个字节,唯一作用就是确定这个文件是否能被一个虚拟机接受的Class文件;
  • 次版本号:紧接着魔数后面的第5和第6个字节;
  • 主版本号:第7和第8个字节代表主版本号,比如说50对应的就是JDK1.6.
  • 可以使用十六进制编译器WinHex打开一个Class文件瞅瞅;

3、常量池
  版本号之后紧跟的就是常量池入口,可以理解为Class文件之中的资源仓库;

  • 常量池容量计数器:u2类型,代表本Class文件有N-1个常量(因为是从1开始技术的);
  1. 0项常量:不引用任何一个常量池项目
  • 常量池放置的内容每一项都是一个表,主要分两类
  1. 字面量:文本字符串、final常量值等;
  2. 符号引用
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符
  • length:
  1. 定义:UTF-8编码的字符串长度是多少个字节;
  2. 65535限制:Class文件中方法、字段等都需要引用CONSTANT_ Utf8_ info型常量的length为u2类型,最大为65535.如果某个变量或者方法名超过了64K,那么这个length容不下了,当然也就无法编译了。

4、访问标志(access_flags)

  • 常量池之后紧跟的就是访问标志。主要包括了这个Class是类or接口,是不是 public,是不是 abstract 类型,是不是 final 类型。

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

  • java.lang.Object类索引为0;
  • 类的索引其实就是描述了这个Class的extends和implements的关系;

6、字段表集合(field_info)

  • 用于描述定义的变量,依次包括了访问标志(access_ flags)、名称索引(name_ index)、描述索引(descriptor_ index)、属性表集合(attributes)。
  • 描述的信息如下:
    1. 作用域:public、private、protect
    2. 实例or类变量:static
    3. 可变性:final
    4. 并发可见性:volatile
    5. 是否可序列化:transient
    6. 字段数据类型:基本类型、对象、数组等
    7. 字段名称;
  • 字段表集合原则

    1. 1、不会列出超类or父类或者父接口继承而来的字段;
    2. 2、有可能列出原本Java代码中不存在的字段(内部类会自动添加指向外部类实例的字段,才能引用到外部类);
    3. 3、Java语言中字段是无法重载的;

7、方法表集合
和字段表集合差不多,方法表集合用来描述Class文件中的方法,但是访问标志和属性表集合和字段表集合有所区别;

  • 访问标志:

    • volatile、transient关键字不可以修饰方法,方法表中少了这两种标志;
    • synchronized、native、strictfp和abstract可以修复方法,故方法表增加了这些对应的标志;
  • Code属性:
    • 方法体中的代码放在了“Code”属性里面了;
  • 方法表集合原则
    • 方法没有重写(Override),父类的信息不会写到子类的方法表中;
    • 编译器有可能自动添加一些方法,典型的如类构造器的“< clinit >”、方法&实例构造器的“< init >“方法;
    • 重载(Overload)一个方法,需要添加一个特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合;

8、属性表集合(attribute_info)
上述那些表需要携带自己的某些属性,来描述自己的特殊环境信息,比如InnderClasses、LineNumberTable、Code 之类的;

  • Code (用语描述代码)

    • max_stack:操作栈深度最大值,JVM 运行时根据这个值来分配栈帧(Stack Frame)中的操作栈深度;
    • max_locals:代表了局部变量表所需要的存储空间。
      • Slot:虚拟机为局部变量分配内存的最小单位

        • byte、char、float、int、short、boolean、returnAddress 长度少于32位,占1个slot
        • double、long 64位,占2个slot
      • 当代码超出一个局部变量的作用域时,这个局部变量所占用的 slot 可以被其他的局部变量所使用
    • code_length:字节码长度
    • code:存储字节码指令
    • 65535限制:虚拟机规定了一个方法不允许超过 65535 条字节码,否则编译不通过;
    • 执行:执行过程中的数据交换、方法调用等操作都是基于栈(操作栈)的;
    • this关键字:在实例方法中通常可以有个 this 关键字来引用当前对象的变量,这是因为 Java 编译时在局部变量表中自动增加了这个(this)局部变量。
  • LineNumberTable:描述 Java 的源码行号和字节码行号;
  • LocalVariableTable:描述局部变量表中的变量与Java源码中定义的变量之间的关系;

三.字节码指令
1、字节码组成

  • 操作码(Opcode):i(助记符)代表int类型数据操作....等等;
  • 操作数 (Operands):永远都是一个数组类型的对象;

Java虚拟机采用面向操作数栈而不是寄存器的架构,字节码指令集是一种指令集架构。放弃了操作数对齐,省略了填充的符号和间隔。
2、加载和存储指令
将数据在帧栈中将局部变量表和操作数栈之间来回传输。

  • 将一个局部变量加载到操作栈;
  • 将一个数值从操作数栈存储到局部变量表;
  • 将一个常量加载到操作数栈;
  • 扩充局部变量表的访问索引的指令;

3、运算指令

  • 将两个操作数栈上的值进行某种特定运算,并把结果重新存入到操作栈顶;
  • Java没有直接支持byte、short、char、boolean类型,都转为int类型进行运算,使用int的指令代替;

4、类型转换指令

  • 宽化转换

    • int到long、float、double
    • long到float、double
    • float到double
  • 窄化转换
    • 必须显示的声明转换
    • 有溢出或者丢精的情况,但不会抛出异常

5、同步指令

  • Java虚拟机支持方法级同步和方法内部一段指令序列同步,这两种同步都是通过“管程”来支持;
  • 执行线程就要求先成功持有“管程”,然后才能执行方法,最后方法执行完成后,才释放“管程”。
  • Java虚拟机通过 monitorenter 和 monitorexit两个指令配对使用,另外编译器会自动增加一个异常处理器。当出现异常时,这个异常处理器能够捕获到所有的异常,并且释放“管程”,monitorexit 指令响应。这样的话,保证了 monitorenter 和monitorexit 总是成对出现的。

 三.代码举例

1、Java文件:

 1 package com.xxx.ccc;
 2 public final class InitConfig {
 3     public static final InitConfig BFCACCOUNT = new InitConfig(0, "aaa", "AAA");
 4     private int mIndex;
 5     private String mData;
 6     private String mDescribe;
 7     private InitConfig(int indexFlag, String data, String describe) {
 8         this.mIndex = indexFlag;
 9         this.mData = data;
10         this.mDescribe = describe;
11     }
12     public String getmData() {
13         return this.mData;
14     }

2、Class 文件:

 1 Last modified 2017-7-4; size 1050 bytes
 2 MD5 checksum 2beb0c10f91b793c3570edcf2d1eff78
 3 Compiled from "InitConfig.java"
 4 public final class com.xxx.xxx.InitConfig
 5 minor version: 0  //次版本号
 6 major version: 51 //主版本号
 7 flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER  //访问标志
 8 Constant pool: //常量池
 9  #1 = Methodref          #14.#41        // java/lang/Object."<init>":()V
10  #2 = Fieldref           #5.#42         // com/xxx/xxx/InitConfig.mIndex:I
11  #6 = Class              #46            // com/xxx/xxx/common/constant/ConstData
12  #7 = String             #47            // aaa
13  #23 = Utf8               <init>
14  #24 = Utf8               (ILjava/lang/String;Ljava/lang/String;)V
15  #25 = Utf8               Code
16  #26 = Utf8               LineNumberTable  //Java的源码行号和字节码行号
17  #27 = Utf8               LocalVariableTable //局部变量表中的变量与Java源码中定义的变量之间的关系
18  #28 = Utf8               this
19  #32 = Utf8               getmData
20  #33 = Utf8               ()Ljava/lang/String;
21  #37 = Utf8               <clinit>
22  #38 = Utf8               ()V
23  #40 = Utf8               InitConfig.java
24  #41 = NameAndType        #23:#38        // "<init>":()V
25  #45 = Utf8               com/xxx/xxx/InitConfig
26  #46 = Utf8               com/xxx/xxx/common/constant/ConstData
27  #53 = NameAndType        #17:#16        // SEAACCOUNT:Lcom/xxx/ccc/InitConfig;
28  #54 = Utf8               java/lang/Object
29 public static final com.xxx.xxx BFCACCOUNT;
30     descriptor: Lcom/xxx/xxx/InitConfig;
31     flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
32 public java.lang.String getmData();
33     descriptor: ()Ljava/lang/String;
34     flags: ACC_PUBLIC
35     Code:
36          stack=1, locals=1, args_size=1
37             0: aload_0
38             1: getfield      #3                  // Field mData:Ljava/lang/String;
39          4: areturn
40           LineNumberTable: //Java的源码行号和字节码行号
41             line 36: 0
42           LocalVariableTable: //局部变量表中的变量与Java源码中定义的变量之间的关系
43             Start  Length  Slot  Name   Signature
44                0       5     0   this   Lcom/xxx/xxx/InitConfig;  //方法里面默认增加了个this

四.小结

  为什么说一些”非Java"语言也是可以在 JVM 上跑,这是因为 JVM 只认识 Class 文件,所以如果某某语言最终编译出的文件是 Class 文件,那么对于 JVM 来说没有什么区别,但是得按照 Class 文件的结构来,不然也无法正常执行。Class 定义了许多特定的基本类型和表结构,通过魔数让 JVM 认识该文件,版本号保证可以在要求的 JDK 版本上运行,在常量池中定义好常量,访问标志位确定访问权限。索引集合方便与外界的 class 保持联系,字段表保存我们定义好的变量,方法表存储方法的信息,属性表存储了上述各种表的一些属性。其中记住 slot为局部变量分配内存的最小单位,当程序超出作用域的时候,slot 可以被其他替换使用。到这里,仅仅是代码最静态的存储的格式,程序要运行起来。还需要操作指令,也是由字节码存储,包括操作码和操作数。有加载存储、运算、类型转换、同步指令。

深入理解Java虚拟机04--类结构文件相关推荐

  1. java虚拟机编译文件,理解Java虚拟机(1)之一个.java文件编译成.class文件发生了什么...

    理解Java虚拟机(1)之一个.java文件编译成.class文件发生了什么 最近在看<深入理解Java虚拟机>弄明白了很多java的底层知识,决定分几部分总结下,从.java文件编译,到 ...

  2. 深入理解Java虚拟机知乎_深入理解Java虚拟机(类文件结构)

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

  3. 《深入理解Java虚拟机》笔记4——类文件结构

    代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步. 由于最近十年内虚拟机以及大量建立在虚拟机之上的程序语言如雨后春笋般出现并蓬勃发展,将我们编写的程序编译成二进 ...

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

    欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 之前在阅读 ASM 文档时,对于已编译类的结构.方法描述符.访问标志.ACC_PUBLIC.ACC_PRIVATE.各种字节码指令等等许多概 ...

  5. 深入理解Java虚拟机--中

    深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...

  6. 深入理解java虚拟机-1.自动内存管理

    文章目录 1.自动内存管理 1.1 Java内存区域与内存溢出异常 1.1.1 运行时数据区域 程序计数器 程序计数器为什么是私有的? java虚拟机栈 本地方法栈 虚拟机栈和本地方法栈为什么是私有的 ...

  7. mysql种编译码写在哪_深入理解Java虚拟机(程序编译与代码优化)

    对于性能和效率的追求一直是程序开发中永恒不变的宗旨,除了我们自己在编码过程中要充分考虑代码的性能和效率,虚拟机在编译阶段也会对代码进行优化.本文就从虚拟机层面来看看虚拟机对我们所编写的代码采用了哪些优 ...

  8. 深入理解Java虚拟机-如何利用 JDK 自带的命令行工具监控上百万的高并发的虚拟机性能...

    虚拟机系列文章 深入理解 Java 虚拟机(第一弹) - Java 内存区域透彻分析 深入理解 Java 虚拟机(第二弹) - 常用 vm 参数分析 深入理解 Java 虚拟机-如何利用 Visual ...

  9. 深入理解 Java 虚拟机(第二弹) - 常用 vm 参数分析

    来自:好好学java 话不多说,今天就分析一下一些常用的Java虚拟机的参数设置,以及如何更好的使用! 1 JVM参数简介 首先想说的是其实这些参数我们并不是陌生的,在平时的开发和使用中经常都会遇到, ...

最新文章

  1. 2018/5/1-----1987年图灵奖PPT
  2. 程序员书单_UML篇
  3. ​SpringCloud:统一异常处理
  4. Intel Realsense 使用D435时需安装的几款官方软件
  5. 问题一:使用AndroidDriver而非原来的AppiumDriver的原因
  6. CodeForces - 1485E Move and Swap(树形dp)
  7. linux命令看文件内容,Linux文件内容查看相关命令
  8. java文章上一篇下一篇_每个人都必须阅读的10篇Java文章
  9. Vue Axios的配置 (高仿饿了么)
  10. python os.path.splitext()的用法_Python常用模块之os.path
  11. Java中如何组装字符串
  12. celery-01-异步任务模块-解决发送邮件的延时问题
  13. PLSQL Developer 安装与配置
  14. python 添加图片_python3 tkinter添加图片和文本
  15. 为什么薄膜干涉的厚度要很小_薄膜干涉的膜为什么不能太厚?
  16. 时间序列分析(3)| ARMA模型的拟合
  17. 保定2021高考成绩查询,保定2021年中考网上查询
  18. Cocos2d-x一张小背景重复贴图充满整个屏幕
  19. Server-Side Timestamping in the Audit Trail System (XAF时间戳在审核中的应用代码)
  20. 快速计算网络地址和广播地址

热门文章

  1. heartbeat v1版CRM的高可用web集群的实现
  2. sql 密码随机生成
  3. Oracle 常见的33个等待事件
  4. The user does not exist or is not unique错误
  5. [转载]深入探索.NET框架内部了解CLR如何创建运行时对象
  6. MAC Opencv include选择
  7. RabbitMQ 相关概念和方法详解
  8. git上的分支命名规范
  9. bzoj3993 [SDOI2015]星际战争
  10. python3 进程