`
chenzhou123520
  • 浏览: 4249763 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

JVM学习笔记(四):Class文件结构

阅读更多

本文根据《深入理解Java虚拟机》第6章内容整理

 

 

一、Java的语言无关性

 

    到今天为止,或许大部分的程序员都还认为Java虚拟机执行Java程序是一件理所当然和天经地义的事情。但在Java发展之初,设计者们就考虑过了在Java虚拟机上运行其它语言的可能性。时至今日商业机构和开源机构以及在Java语言之外发展出一大批在Java虚拟机上运行的语言,如Clojure,Groovy,JRuby,Jython,Scala,等等。

    实现语言无关性的基础仍然是虚拟机和字节码存储格式,使用Java编译器可以把Java代码编译为存储字节码的Class文件,使用JRuby等其它语言的编译器一样可以把程序编译成Class文件,虚拟机不需要关心Class来源于什么语言,只要它符合Class文件应用的结构就可以在Java虚拟机中运行。如下图所示:

 

图1:Java虚拟机提供的语言无关性

 

二、Class类文件的结构

 

    Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中。当遇到需要占用8个字节以上空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。

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

    无符号数属于基本的数据类型,以u1,u2,u4,u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数。

    表是由多个无符号数或其他表作为数据项组成的符合数据类型。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表,由下图所示的数据项构成:

Java Class文件格式

图2:Class文件格式

下面首先给出一段简单的Java程序,以这段代码编译成的Class文件为基础进行Class文件结构的介绍,代码如下:

 

public class TestClass{
	private int num;
	
	public int inc(){
		for(int i=0; i<10; i++){
			num = i;
		}
		return num;
	}
	
	public static void main(String[] args){
		new TestClass().inc();
	}
	
}

 把代码编译成Class文件后,使用16进制编辑器WinHex打开这个Class文件,可以看到Class的内部结构如下:

Class文件的结构

图3:Class文件的结构

 

2.1 魔数与Class文件的版本

    从上面的图2可以看出,Class文件格式表中,第一项为一个u4类型的名为magic的数据项。这是因为每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用就是用来确定这个文件是否为一个能被虚拟机接受的Class文件。很多文件存储标准中都使用魔数来进行身份识别。例如图片格式。使用魔数而不是扩展名来进行识别主要是基于安全考虑,因为文件扩展名可以很随便地被人为改动。

    Class文件的魔数的值为:0xCAFFBABE,这个名称的由来据说还有个很有趣的典故,在这里就不详细表述了,有兴趣的可以百度或者google!

    class文件魔数值如下图所示:

Class文件魔数

图4:Class文件的魔数

   紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6字节是次版本号(Minor Version),第7个和第8个字节是主版本号(Major Version)。Java的版本号是从45开始的,Jdk1.1之后的每个JDK版本发布主版本号向上加1,高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件。

    根据上面的图4所示,代表版本号的第5个字节和第6个字节值为0x0000,而主版本号的值为0x0032,即十进制的50,该版本号说明这个是可以被JDK1.6或以上版本的虚拟机执行的Class文件。

    下图列出了从jdk1.1到jdk1.7之间,主流的JDK版本编译器输出的默认的和可支持的Class文件版本号:

Java Class文件的版本号

图5:Class文件版本号

 

2.2 常量池

    紧接着主次版本号之后的是常量池入口,常量池是Class文件结构中与其他项目关联最多的数据类型。常量池之中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:

   1.类和接口的全限定名(Fully Qualified Name)

   2.字段的名称和描述符(Descriptor)

   3.方法的名称和描述符。

 

    使用Javap命令输出常量表

 

D:\JVM>javap -verbose TestClass
Compiled from "TestClass.java"
public class TestClass extends java.lang.Object
  SourceFile: "TestClass.java"
  minor version: 0
  major version: 50
  Constant pool:
const #1 = Method       #6.#20; //  java/lang/Object."<init>":()V
const #2 = Field        #3.#21; //  TestClass.num:I
const #3 = class        #22;    //  TestClass
const #4 = Method       #3.#20; //  TestClass."<init>":()V
const #5 = Method       #3.#23; //  TestClass.inc:()I
const #6 = class        #24;    //  java/lang/Object
const #7 = Asciz        num;
const #8 = Asciz        I;
const #9 = Asciz        <init>;
const #10 = Asciz       ()V;
const #11 = Asciz       Code;
const #12 = Asciz       LineNumberTable;
const #13 = Asciz       inc;
const #14 = Asciz       ()I;
const #15 = Asciz       StackMapTable;
const #16 = Asciz       main;
const #17 = Asciz       ([Ljava/lang/String;)V;
const #18 = Asciz       SourceFile;
const #19 = Asciz       TestClass.java;
const #20 = NameAndType #9:#10;//  "<init>":()V
const #21 = NameAndType #7:#8;//  num:I
const #22 = Asciz       TestClass;
const #23 = NameAndType #13:#14;//  inc:()I
const #24 = Asciz       java/lang/Object;

{
public TestClass();
  Code:
   Stack=1, Locals=1, Args_size=1
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
  LineNumberTable:
   line 1: 0


public int inc();
  Code:
   Stack=2, Locals=2, Args_size=1
   0:   iconst_0
   1:   istore_1
   2:   iload_1
   3:   bipush  10
   5:   if_icmpge       19
   8:   aload_0
   9:   iload_1
   10:  putfield        #2; //Field num:I
   13:  iinc    1, 1
   16:  goto    2
   19:  aload_0
   20:  getfield        #2; //Field num:I
   23:  ireturn
  LineNumberTable:
   line 5: 0
   line 6: 8
   line 5: 13
   line 8: 19

  StackMapTable: number_of_entries = 2
   frame_type = 252 /* append */
     offset_delta = 2
     locals = [ int ]
   frame_type = 250 /* chop */
     offset_delta = 16


public static void main(java.lang.String[]);
  Code:
   Stack=2, Locals=1, Args_size=1
   0:   new     #3; //class TestClass
   3:   dup
   4:   invokespecial   #4; //Method "<init>":()V
   7:   invokevirtual   #5; //Method inc:()I
   10:  pop
   11:  return
  LineNumberTable:
   line 12: 0
   line 13: 11


}

 

2.3 访问标志

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

 

2.4 类索引、父类索引与接口索引集合

   类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定这个类的继承关系。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。Java不允许多重继承,所以父类索引只有一个,除了java.lang.Object外,所有Java类的父类索引都不为0。接口索引集合就用来描述这个类实现了哪些接口,所有被实现的接口按类定义中的implements(如果类是一个接口则是extends)后的接口顺序从左到右排列在接口的索引集合中。

 

2.5 字段表集合

   字段表(field_info)用于描述接口或类中声明的变量。字段(field)包括了类级变量和实例级变量,但不包括方法内部声明的变量。一个字段的信息包括:作用域(public、private、protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否序列化(transient修饰符)、字段数据类型(基本数据类型、对象、数组)、字段名称。这些信息中,各个修饰符都是布尔值,要么有,要么没有。

   全限定名称:如果TestClass类是定义在com.chenzhou.jvm包中,那么这个类的全限定名为com/chenzhou/jvm/TestClass。

   简单名称:简单名称指没有类型和参数修饰的方法或字段名称,在上面的例子中,TestClass类中的inc()方法和num字段的简单名称分别为“inc”和“num”。

   描述符:描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte,char,double,float,int,long,short,boolean)及代表无返回值的void类型都用一个大写字符来表示,而对象则用字符L加对象的全限定名来表示,如下图所示:

描述符标识字符含义

图6:描述符标识字符含义

    对于数组类型,每一维度将使用一个前置的“[”字符来描述,如定义一个“java.lang.String[][]”类型的二维数组,将会被记录为:"[[Ljava/lang/String;",一个整型数组"int[]"将被记录为"[I"。

    用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照严格的顺序放在一组小括号"()"内,如方法void inc()的描述符为"()V",方法java.lang.String.toString()的描述符为"()Ljava/lang/String;",方法int indexOf(char[] source,int offset,int count,char[] target,int tOffset,int tCount,int fromIndex)的描述符为"([CII[CIII)I"。

 

2.6 方法表集合

    Class文件存储格式中对方法的描述与对字段的描述几乎完全一致。方法表的结构如同字段表一样,一次包括了访问标志、名称索引、描述符索引、属性表集合几项。

 

2.7 属性表集合

    Java虚拟机规范第二版中预定义了9项虚拟机应当能识别的属性,包括:Code、ConstantValue、Deprecated、Exceptions、InnerClasses、LineNumberTable、LocalVariableTable、SourceFile、Synthetic。

 

 

 

0
0
分享到:
评论

相关推荐

    java7rt.jar源码-Java_JVM:这是我的JavaJVM学习笔记

    Java_JVM This is my Java JVM learn notes JVM: java虚拟机 一:上篇——内存与垃圾回收器 二:中篇——字节码与类的加载 三:下篇——性能监控与调优篇 一: 上篇——内存与垃圾回收器 架构: jvm依赖的架构: 栈架构...

    cyc学习笔记.pdf

    2021秋招学习笔记总结,包含Java基础、Java并发、数据库、redis等常用后端技术等内容。数据类型 注:包装类型中一般设有缓冲池,比如Integer、String。 1、Integer缓存池范围-128~127都是同一个地址,在缓存池范围...

    JVM学习笔记(一)——类的加载机制

    ​ 类加载器并不需要等到某个类被“首次主动使用”时再加载它,JVM规范允许类加载器在预料某个类将要被使用时就预先加载它,如果在预先加载的过程中遇到了.class文件缺失或存在错误,类加载器必须在程序首次主动...

    java jdk8 学习笔记

    Java编译语言将Java代码编译成.class文件(只有一种形式),而C/C++语言将代码编译成01码,不同的操作系统的01码指令不同,这造成了不能跨平台,而Java将这个任务交给JVM,不同操作系统上的JVM将.class文件编译成不同...

    java7rt.jar源码-JVM:JVM学习笔记

    java7 rt.jar源码虚拟机 ...class CallableDemo { public static void main ( String [] args ) throws ExecutionException , InterruptedException { FutureTask task = new FutureTask ( new MyThread2 ()); n

    Java JDK 7学习笔记(国内第一本Java 7,前期版本累计销量5万册)

     《Java JDK 7学习笔记》详细介绍了JVM、JRE、Java SE API、JDK与IDE之间的对应关系。必须要时从Java SE API的源代码分析,了解各种语法在Java SE API中如何应用。  《Java JDK 7学习笔记》将IDE操作纳为教学内容...

    【隐匿的学习笔记】JVM(1) 类加载子系统

    其实就是JVM运行的第一步 讲class文件加载的过程 这个过程分为三步 加载 链接 初始化 也就是简图中的第一步 加载load 加载都做了什么? 1.通过类的全限定名获取定义此类的二进制字节流 ----&gt; 其实就是把磁盘上 ...

    net学习笔记及其他代码应用

    25.请详述在dotnet中类(class)与结构(struct)的异同? 答:Class可以被实例化,属于引用类型,是分配在内存的堆上的,Struct属于值类型,是分配在内存的栈上的. [Page] 26.根据委托(delegate)的知识,请完成以下用户...

    java总结(1).zip_commandkv3_java_searwg

    学习java基础的一些笔记,总结.如: java开发环境:JVM:java虚拟机:加载.class并运行.class JRE:java运行环境:包含JVM,还包含运行java程序所必须的环境(JRE=JVM+java系统类库)

    Java学习笔记

    一、JAVA SE基础 1、开发语言基本包括:数据类型、循环控制语句、数组、方法 2、面向对象:最终是面向接口编程的,封装...JAVA提供多线程、提供动态下载程序代码的机制、安全性,它的操作以字节码(class)文件形式保存

    积分java源码-java-11:Java11OCP学习笔记

    (JVM)是一个可执行文件。 当 JVM 运行时,它会加载给定的类并查找要运行的该类的主要方法。 JVM 的可执行文件名为 java。 每个 Java 类都属于一个包。 源文件中最多可以有一个 package 语句,并且它必须是文件中的第...

    关于Scala那些事儿

    java、Scala都是基于JVM的编程语言(文件编译成class文件保存),类相互之间可以调用,Scala并可以调用现有的Java类库;Spark1.6中使用的是Sacla2.10。 面向过程:需要你自己去一步一步的执行 面向函数:也是需要...

    jive.chm

    混淆 1 Sun公司的混淆器JADE 2 RetroGuard的使用方法 &lt;br&gt; JVM 1 JVM之class文件结构 2 JVM 內的資料處理 &lt;br&gt; MVC Framework 1 应用Struts的网站建设 &lt;br&gt; ...

    Jive资料集

    系统设计 1 jive设计思路 2 jive的工作内幕 3 Jive源代码研究 4 Jive中的设计模式 5 jive学习...2 RetroGuard的使用方法 &lt;br&gt; JVM 1 JVM之class文件结构 2 JVM 內的資料處理

    jedis使用指南

    具体事务和监听请参考文章:redis学习笔记之事务 暂时找到三种实现方式: 1. 通过jedis.setnx(key,value)实现 import java.util.Random; import org.apache.commons.pool.impl.GenericObjectPool.Config; import ...

    java线程池概念.txt

    当然,现在用过的东西并不是代表以后还能娴熟的使用,做好笔记非常重要; 1:必须明白为什么要使用线程池:(这点很重要)  a:手上项目所需,因为项目主要的目的是实现多线程的数据推送;需要创建多线程的话,那...

Global site tag (gtag.js) - Google Analytics