深入理解Java虚拟机04--类结构文件

一.程序存储格式

  • 统一的程序存储格式:不同平台的虚拟机于所有平台都统一使用程序存储格式——字节码(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 可以被其他替换使用。到这里,仅仅是代码最静态的存储的格式,程序要运行起来。还需要操作指令,也是由字节码存储,包括操作码和操作数。有加载存储、运算、类型转换、同步指令。

原文地址:https://www.cnblogs.com/ganchuanpu/p/9429248.html

时间: 2024-08-30 09:15:41

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

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

深入理解Java虚拟机(类文件结构) 欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 之前在阅读 ASM 文档时,对于已编译类的结构.方法描述符.访问标志.ACC_PUBLIC.ACC_PRIVATE.各种字节码指令等等许多概念听起来都是云山雾罩.一知半解,原因就在于对类文件结构和类加载机制不够了解.直到后来细读了<深入理解 Java 虚拟机>中虚拟机执行子系统的相关内容,才建立了清晰的认知.如果你也和我一样,不了解类结构和类加载,但是工作中又涉及到字节码相关内容,相信后面两篇文章

《深入理解Java虚拟机》:类加载的过程

<深入理解Java虚拟机>:类加载的过程 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.其中类加载的过程包括了加载.验证.准备.解析.初始化五个阶段. 下面详细讲述类加载过程中每个阶段所做的工作. 加载 加载时类加载过程的第一个阶段,在加载阶段,虚拟机需要完成以下三件事情: 1.通过一个类的全限定名来获取其定义的二进制字节流. 2.将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构. 3.在Java堆中生成一

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

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

深入理解Java虚拟机到底是什么

摘自:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机 我们都知道Java程序必须在虚拟机上运行.那么虚拟机到底是什么呢?先看网上搜索到的比较靠谱的解释: 虚拟机是一种抽象化的计算机,通过在实际的计算机上仿真模拟各种计算机功能来实现的.Java虚拟机有自己完善的硬体架构,如处理器.堆栈.寄存器等,还具有相应的指令系统.JVM屏蔽了与具体操作系统平台相关的信息,使得Java程序只需生成在Java虚拟机上运行的

《深入理解java虚拟机》:类的初始化

深入理解java虚拟机>:类的初始化 类从被加载到虚拟机内存中开始,到卸载出内存为止,它的整个生命周期包括:加载.验证.准备.解析.初始化.使用和卸载七个阶段.其中验证.准备.解析3个部分统称为连接.类加载的过程包括了加载.验证.准备.解析.初始化五个阶段. 加载.验证.准备.初始化和卸载这5个阶段的顺序时确定的,类的加载过程必须按照这种顺序按部就班的开始,而解析阶段则不一定,它在某些情况下可以在初始化阶段之后开始,这是为了支持Java语言的运行时绑定(也成为动态绑定或晚期绑定).另外注意这里的

深入理解java虚拟机系列(一):java内存区域与内存溢出异常

文章主要是阅读<深入理解java虚拟机:JVM高级特性与最佳实践>第二章:Java内存区域与内存溢出异常 的一些笔记以及概括. 好了开始.如果有什么错误或者遗漏,欢迎指出. 一.概述 先上一张图 这张图主要列出了Java虚拟机管理的内存的几个区域. 常有人把Java内存区分为堆内存(Heap)和栈内存(Stack),这种分法比较粗糙,Java内存区域的划分实际上远比这复杂,从上图就可以看出了.堆栈分法中所指的"栈"实际上只是虚拟机栈,或者说是虚拟机栈中的局部变量表部分.接下

《深入理解Java虚拟机》读书笔记——第1章 走近Java

(注:原文请见<深入理解Java虚拟机>周志明 著,知识点部分参考百度百科) 总述 第1章分为两个部分:Java概述及自己动手编译JDK的教程. 1 Java概述 介绍了Java的整体特性.Java的技术体系组成.Java及JVM的发展史. 1.1 Java的总体特性 1)Java不仅仅是一门编程语言,更是由一系列计算机软件和规范组成的技术体系. 2)Java具有众多优点: a.Java虚拟机在千差万别的物理机上建立了统一的运行平台,实现了跨平台性.(主要) b.提供了相对安全的内存管理和访问

(1) 深入理解Java虚拟机到底是什么?

好文转载:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机 作为一个Java程序员,我们每天都在写Java代码,我们写的代码都是在一个叫做Java虚拟机的东西上执行的.但是如果要问什么是虚拟机,恐怕很多人就会模棱两可了.在本文中,我会写下我对虚拟机的理解.因为能力所限,可能有些地方描述的不够欠当.如果你有不同的理解,欢迎交流. 我们都知道Java程序必须在虚拟机上运行.那么虚拟机到底是什么呢?先看网上搜索到

《深入理解Java虚拟机》读书笔记---第二章 Java内存区域与内存溢出异常

Java与C++之间有一堵由内存动态分配和垃圾收集技术所围成的高墙,墙外面的人想进去,墙里面的人却想出来.这一章就是给大家介绍Java虚拟机内存的各个区域,讲解这些区域的作用,服务对象以及其中可能产生的问题. 1.运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域. 1.1程序计数器 程序计数器(Program Counter Register)是一块较小的内存空间,它的作用可以看作是当前线程所执行的字节码的行号指示器.在虚拟机的概念模型中里,字