深入理解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开始技术的);
- 0项常量:不引用任何一个常量池项目
- 常量池放置的内容每一项都是一个表,主要分两类
- 字面量:文本字符串、final常量值等;
- 符号引用
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- length:
- 定义:UTF-8编码的字符串长度是多少个字节;
- 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)。
- 描述的信息如下:
- 作用域:public、private、protect
- 实例or类变量:static
- 可变性:final
- 并发可见性:volatile
- 是否可序列化:transient
- 字段数据类型:基本类型、对象、数组等
- 字段名称;
- 字段表集合原则
- 1、不会列出超类or父类或者父接口继承而来的字段;
- 2、有可能列出原本Java代码中不存在的字段(内部类会自动添加指向外部类实例的字段,才能引用到外部类);
- 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 可以被其他的局部变量所使用
- 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文件:
package com.xxx.ccc;
public final class InitConfig {
public static final InitConfig BFCACCOUNT = new InitConfig(, "aaa", "AAA");
private int mIndex;
private String mData;
private String mDescribe;
private InitConfig(int indexFlag, String data, String describe) {
this.mIndex = indexFlag;
this.mData = data;
this.mDescribe = describe;
}
public String getmData() {
return this.mData;
}
2、Class 文件:
Last modified --; size bytes
MD5 checksum 2beb0c10f91b793c3570edcf2d1eff78
Compiled from "InitConfig.java"
public final class com.xxx.xxx.InitConfig
minor version: //次版本号
major version: //主版本号
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER //访问标志
Constant pool: //常量池
# = Methodref #.# // java/lang/Object."<init>":()V
# = Fieldref #.# // com/xxx/xxx/InitConfig.mIndex:I
# = Class # // com/xxx/xxx/common/constant/ConstData
# = String # // aaa
# = Utf8 <init>
# = Utf8 (ILjava/lang/String;Ljava/lang/String;)V
# = Utf8 Code
# = Utf8 LineNumberTable //Java的源码行号和字节码行号
# = Utf8 LocalVariableTable //局部变量表中的变量与Java源码中定义的变量之间的关系
# = Utf8 this
# = Utf8 getmData
# = Utf8 ()Ljava/lang/String;
# = Utf8 <clinit>
# = Utf8 ()V
# = Utf8 InitConfig.java
# = NameAndType #:# // "<init>":()V
# = Utf8 com/xxx/xxx/InitConfig
# = Utf8 com/xxx/xxx/common/constant/ConstData
# = NameAndType #:# // SEAACCOUNT:Lcom/xxx/ccc/InitConfig;
# = Utf8 java/lang/Object
public static final com.xxx.xxx BFCACCOUNT;
descriptor: Lcom/xxx/xxx/InitConfig;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
public java.lang.String getmData();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=, locals=, args_size=
: aload_0
: getfield # // Field mData:Ljava/lang/String;
: areturn
LineNumberTable: //Java的源码行号和字节码行号
line :
LocalVariableTable: //局部变量表中的变量与Java源码中定义的变量之间的关系
Start Length Slot Name Signature
this Lcom/xxx/xxx/InitConfig; //方法里面默认增加了个this
四.小结
为什么说一些”非Java"语言也是可以在 JVM 上跑,这是因为 JVM 只认识 Class 文件,所以如果某某语言最终编译出的文件是 Class 文件,那么对于 JVM 来说没有什么区别,但是得按照 Class 文件的结构来,不然也无法正常执行。Class 定义了许多特定的基本类型和表结构,通过魔数让 JVM 认识该文件,版本号保证可以在要求的 JDK 版本上运行,在常量池中定义好常量,访问标志位确定访问权限。索引集合方便与外界的 class 保持联系,字段表保存我们定义好的变量,方法表存储方法的信息,属性表存储了上述各种表的一些属性。其中记住 slot为局部变量分配内存的最小单位,当程序超出作用域的时候,slot 可以被其他替换使用。到这里,仅仅是代码最静态的存储的格式,程序要运行起来。还需要操作指令,也是由字节码存储,包括操作码和操作数。有加载存储、运算、类型转换、同步指令。
深入理解Java虚拟机04--类结构文件的更多相关文章
- 深入理解Java虚拟机--中
深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...
- 深入理解Java虚拟机(类文件结构)
深入理解Java虚拟机(类文件结构) 欢迎关注微信公众号:BaronTalk,获取更多精彩好文! 之前在阅读 ASM 文档时,对于已编译类的结构.方法描述符.访问标志.ACC_PUBLIC.ACC_P ...
- 深入理解Java虚拟机之自己编译JDK
题外话 最近在阅读<深入理解Java虚拟机>,其中有一小节实战是自己编译JDK,实际操作下来后遇到问题不少,为此特地记录,也希望可以给大家带来一些参考! 前置准备 平台及工具:Window ...
- 《深入理解Java虚拟机》虚拟机性能监控与故障处理工具
上节学习回顾 从课本章节划分,<垃圾收集器>和<内存分配策略>这两篇随笔同属一章节,主要是从理论+实验的手段来讲解JVM的内存处理机制.好让我们对JVM运行机制有一个良好的概念 ...
- 《深入理解 java虚拟机》学习笔记
java内存区域详解 以下内容参考自<深入理解 java虚拟机 JVM高级特性与最佳实践>,其中图片大多取自网络与本书,以供学习和参考.
- (1) 深入理解Java虚拟机到底是什么?
好文转载:http://blog.csdn.net/zhangjg_blog/article/details/20380971 什么是Java虚拟机 作为一个Java程序员,我们每天都在写Java ...
- 深入理解java虚拟机(5)---字节码执行引擎
字节码是什么东西? 以下是百度的解释: 字节码(Byte-code)是一种包含执行程序.由一序列 op 代码/数据对组成的二进制文件.字节码是一种中间码,它比机器码更抽象. 它经常被看作是包含一个执行 ...
- 深入理解java虚拟机(4)---类加载机制
类加载的过程包括: 加载class到内存,数据校验,转换和解析,初始化,使用using和卸载unloading过程. 除了解析阶段,其他过程的顺序是固定的.解析可以放在初始化之后,目的就是为了支持动态 ...
- 深入理解Java虚拟机博客参考目录
参考博客:注明请注明出处 深入理解Java虚拟机读书笔记之:第5章 Java虚拟机(Java虚拟机内部结构图,很重要) 深入理解Java虚拟机读书笔记之:第6章 Java class文件: <深 ...
随机推荐
- 树莓派GPIO口的使用
树莓派的优势在于Liunx操作系统加GPIO口,其中IO口时物联网组成中不可缺少的,高低电平的控制是很有必要的存在,再加有python的支持,玩转GPIO相对就容易多了 管脚编号 BCM: 编号侧重 ...
- python学习笔记01-变量
变量的作用: 1.保存信息 方便日后被调用 操作 2. 更改 代表描述性的意思 让人明白是什么意思 行业命名规则: 1.student_number 2.studentNumber 驼峰体 不要以大 ...
- 【shiro】(2)---基于RUL的权限管理
基于RUL的权限管理 我想在写shiro权限管理认证前,先来一个基于URL实现的权限管理控制. 一.基于URI的权限业务逻辑 实现思路: 将系统操作的每个url配置在权限表中,将权限对应 ...
- 图片base64上传时可能遇到的问题
base64上传图片时服务器接到的值可能会丢失字符串 解决方法如下:(分为单个上传和多个上传) <?php $BASE_DIR = "../"; //文件上传 $img = ...
- 全网最详细的最新稳定OSSEC搭建部署(ossec-server(CentOS6.X)和ossec-agent(CentOS6.X))(图文详解)
不多说,直接上干货! 前言 写在前面的话,网上能够找到一些关于ossec方面的资料,虽然很少,但是总比没有强,不过在实际的使用过程中还是会碰到许多稀奇古怪的问题.整理整理我的使用过程,就当做一篇笔记吧 ...
- Long类型时间如何转换成视频时长?
数据库中存放的视频时长是一个Long类型的毫秒/秒时间,现在需要把这个时间转换成标准的视频时长格式,在我看来这应该是一个很常用的转化有一个很常用的转换方法工具才对,可是我百度找了许久,没有一个简单直观 ...
- 如何使用借助python完成 ARCGIS工具箱的调用
上个月使用python调用arcgis工具箱完成了火点txt文件转shp文件的小功能, 感觉很不错, 写下这篇博客希望对大家有所帮助. 1.环境介绍: 系统: win8.1(64位) arcgis:d ...
- Appcan开发笔记:导出Excel文件
Appcan IDE为4.0+; appcan提供了导出文件的方法 appcan.file.write 文件会自动创建,要解决的事情是Excel用字符串输出,可以考虑 csv(逗号间隔).HTML.X ...
- 基于SpringMVC+Spring+MyBatis实现秒杀系统【客户端交互】
前言 该篇主要实现客户端和服务的交互.在第一篇概况里我已经贴出了业务场景的交互图片. 客户端交互主要放在seckill.js里来实现.页面展现基于jsp+jstl来实现. 准备工作 1.配置web.x ...
- MONGODB(二)——索引操作
一.1.插入10w条数据> for(var i = 0;i<100000;i++){... var rand = parseInt(i*Math.random());... db.pers ...