(转)《深入理解java虚拟机》学习笔记5——Java Class类文件结构

Java语言从诞生之时就宣称一次编写,到处运行的跨平台特性,其实现原理是源码文件并没有直接编译成机器指令,而是编译成Java虚拟机可以识别和运行的字节码文件(Class类文件,*.class),字节码文件是一种平台无关的中间编译结果,字节码文件由java虚拟机读取,解析和执行,java虚拟机屏蔽了不同操作系统和硬件平台的差异性。

如今的java虚拟机已经称为一种通用平台,不但能够运行java语言,Groovy,JRuby,Jython等一大批动态语言也可以直接在Java虚拟机上运行,其原理也是这些动态语言的编译器将源码文件编译为和Java相同的字节码文件,这样Java虚拟机就可以像执行java语言一样执行这些动态语言了。

字节码class类文件是由一系列字节码命令组成,用于表示程序中各种常量、变量、关键字和运算符号的语义等等。Java的Class类文件是一组以8为字节为单位的二进制流,各个数据项严格按照顺序紧凑地排列在Class类文件之中,中间没有添加任何分隔符,当遇到需要占用8位字节以上空间的数据项时,按照高位在前的方式分割成若干个8位字节进行存储。

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

(1).无符号数:

属于基本类型的数据,以u1, u2, u4, u8来分别代表1个字节,2个字节,4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码的字符串值。

(2).表:

由多个无符号数或其他表作为数据项构成的复合数据类型,所以表都习惯性地以“_info“结尾。表用于描述有层次关系的复合结构数据,整个Class文件本质就是一张表。

Java Class类文件结构如下:


类型


名称


数量


u4


magic


1


u2


minor_version


1


u2


major_version


1


u2


constant_pool_count


1


cp_info


constant_pool


constant_pool_count-1


u2


access_flags


1


u2


this_class


1


u2


super_class


1


u2


interfaces_count


1


u2


interfaces


interfaces_count


u2


fields_count


1


field_info


fields


fields_count


u2


methods_count


1


method_info


methods


methods_count


u2


attributes_count


1


attribute_info


attributes


attributes_count

Class类文件没有任何分隔符,是严格按照这个结构表顺序排列,下面具体介绍各个名称含义:

(1).magic:

每个Class文件的头4个字节被称为魔数,它的唯一作用是用于确定这个文件是否为一个能被java虚拟机所接收的Class类文件,即用于判定文件是否是符合规范的java Class文件。虽然说后缀名“.class”可以表明文件是一个Class文件,但是文件后缀名是可以随意被改动的,基于安全的考虑,很多文件都通过魔数值来唯一确定文件类型,java的Class文件魔数是:0xCAFEBABE.

(2).minor_version和major_version:

每个Class文件的第5和第6个字节代表Class文件的次版本号,第7和第8个字节代表Class文件的主版本号。

Class文件的主、次版本号是由JDK决定的,JDK1.0~JDK1.1使用了45.0~45.3的版本号(45是主版本好,点”.“之后的是次版本号),从JDK1.1开始,每个大版本的JDK主版本号加1.

Class主、次版本号的一个作用时,高版本的Java虚拟机可以向前兼容,运行低版本JDK编译的Class字节码文件,而低版本的java虚拟机不能运行高版本JDK编译的Class字节码文件。当低版本的java虚拟机运行高版本JDK编译的Class字节码文件时,通常会报类似如下的异常:

[java] view plaincopy

  1. Exception in thread "main" java.lang.UnsupportedClassVersionError: a (Unsupporte
  2. d major.minor version 49.0)

JDK1.0~JDK1.1使用了45.0~45.3的版本号,JDK1.2使用了46.0~46.65535的版本号,JDK1.3使用了47.0~47.65535的版本号,JDK1.4使用了48.0~48.65535的版本号,JDK1.5使用了49.0~49.65535的版本号,JDK1.6使用了50.0~50.65535的版本号,JDK1.7使用51.0~51.65535的版本号。

在编译时可以通过指定-target参数来改变主版本号,如JDK1.6编译时如果没有给定target参数,则编译出来的Class文件的主版本号是50,如果给定”-target 1.4 -source 1.4”参数之后,则主版本将变为48,如果给定”-target 1.5 ”参数之后,则主版本将变为49。

(3). constant_pool_count和constant_pool:

constant_pool_count代表Class文件中常量池的数目,由于常量池的计数从1开始,因此常量池的容量是constant_pool_count-1。

第0项常量空出做特殊考虑,为了满足一些指向常量池的索引值在某些特定情况下需要表达“不指向任何一个常量池”的意思。

constant_pool常量池是Class类文件中出现的第一个表类型数据,常量池主要存放两大类常量:

a.字面量(Literal):包括文本字符串、final类型常量值。

b.符号引用(SymbolicReferences):包括类和接口的全限定名、字段的名称和描述符、方 法的名称和描述符。

(4). access_flags:

用于表示Class或接口层次的访问标志,即类或接口层面的访问控制信息,通常存储的信息包括:Class类文件是类、接口、枚举或是注解;是否定义为public类型;是否定义为abstract类型;类是否被定义为final等等。

(5). this_class、super_class和interfaces:

this_class类索引用于确定类的全限定名,super_class父类索引用于确定父类的全限定名,interfaces接口索引用于确定接口的全限定名,由于java中可以实现多个接口,因此使用interfaces_count来存储接口数量。

(6). field:

field_info字段表用于描述接口或者类中声明的变量,field字段包括了类级变量(静态变量)和实例级变量(成员变量),但不包括方法内部的局部变量。

fields_count字段数目表示Class文件中的类和实例变量总数,字段存放的信息包括:字段访问标志、是否静态、是否final、是否并发可见volatile、是否可序列化transient、数据类型、字段名称等等。

注意:字段表中不包含从父类或者接口中继承而来的字段,但是会添加原本代码中不存在的字段,例如this,以及内部类对外部类访问而自动添加的外部类实例字段等。

(7).method:

method_info方法表用于描述类或者接口中声明的方法,methods_count用于表示Class文件中方法总数,method方法存储了方法的访问标识、是否静态、是否final、是否同步synchronized、是否本地方法native、是否抽象方法abstract、方法返回值类型、方法名称、方法参数列表等信息。

方法的代码指令并没有直接存放在方法表中,而是存放着属性表中的方法表Code中。

注意:如果父类的方法在子类没有被重写,方法表中不会出现来自父类的方法信息,但是编译器会自动添加类构造器”<clinit>”方法和实例构造器”<init>”方法。

Java编译器的方法特征签名只包括:方法名称、参数顺序和参数类型,不包括方法返回值类型,因此java的方法重载不能通过方法的返回值类区别,但是在Class文件中,方法特征签名包括方法的返回值类型,因此Class文件中可以共存两个名称和参数完全相同而返回值类型不同的方法。

(8). attribute:

attribute_info属性表是Class文件格式中最具扩展性的一种数据项目,用于存放field_info字段表、method_info方法表以及Class文件的专有信息,属性表不要求各个属性有严格顺序,只要求不与已有的属性名字重复即可,属性表中存放的常用信息如下:


属性名称


使用位置


含义


Code


方法表


Java代码编译后的字节码指令


ConstantValue


字段表


final关键字定义的常量值


Deprecated


类、方法表、字段表


被声明为Deprecated的字段或方法


Exception


方法表


方法抛出的异常


InnerClasses


类文件


内部类列表


LineNumberTable


Code属性


java源码行号和字节码指令的对应关系


LocalVariableTable


Code属性


方法的局部变量描述


SourceFile


类文件


源文件名称


Synthetic


类、方法表、字段表


标识方法或字段为编译器自动生成

Class文件是二进制文件,使用支持二进制的文本编辑器打开之后显示的全是二进制数据,非常的不便于阅读和理解,使用JDK提供的javap工具可以简单将Class反编译,编译理解Class文件的结构,例子如下:

源码:

[java] view plaincopy

  1. public class Test {
  2. public int getNum(int i) {
  3. return i + 1;
  4. }
  5. }

javap反编译之后的字节码文件:

[java] view plaincopy

  1. public class Test extends java.lang.Object
  2. SourceFile: "Test.java"
  3. minor version: 0
  4. major version: 50
  5. //常量池
  6. Constant pool:
  7. const #1 = class        #2;
  8. const #2 = Asciz        Test;
  9. const #3 = class        #4;
  10. const #4 = Asciz        java/lang/Object;
  11. const #5 = Asciz        <init>;  //实例构造器
  12. const #6 = Asciz        ()V;  //void返回类型
  13. const #7 = Asciz        Code;  //属性表Code属性
  14. const #8 = Method       #3.#9;  //方法特征签名  java/lang/Object."<init>":()V
  15. const #9 = NameAndType  #5:#6;//  方法名称和返回值"<init>":()V
  16. const #10 = Asciz       LineNumberTable;  //属性表源码行号和字节码指令对应表
  17. const #11 = Asciz       LocalVariableTable;  //属性表方法局部变量表
  18. const #12 = Asciz       this;  //Test类实例对象本身
  19. const #13 = Asciz       LTest;;  //对象类型,Test类
  20. const #14 = Asciz       getNum;  //方法名称
  21. const #15 = Asciz       (I)I;  //方法参数列表为一个int类型和返回值为int类型
  22. const #16 = Asciz       i;  //参数名称i
  23. const #17 = Asciz       I;  //参数类型int
  24. const #18 = Asciz       SourceFile;
  25. const #19 = Asciz       Test.java;
  26. //方法表
  27. {
  28. //构造函数(默认构造方法)
  29. public Test();
  30. Code:  //属性表Code属性
  31. Stack=1, Locals=1, Args_size=1
  32. 0:   aload_0
  33. 1:   invokespecial   #8; //Method java/lang/Object."<init>":()V
  34. 4:   return
  35. LineNumberTable:
  36. line 2: 0
  37. LocalVariableTable:  //属性表方法局部变量表
  38. Start  Length  Slot  Name   Signature
  39. 0      5      0    this       LTest;
  40. //自定义方法
  41. public int getNum(int);
  42. Code:
  43. Stack=2, Locals=2, Args_size=2
  44. 0:   iload_1
  45. 1:   iconst_1
  46. 2:   iadd
  47. 3:   ireturn
  48. LineNumberTable:
  49. line 4: 0
  50. LocalVariableTable:
  51. Start  Length  Slot  Name   Signature
  52. 0      4      0    this       LTest;
  53. 0      4      1    i       I
  54. }
时间: 2024-10-07 13:56:01

(转)《深入理解java虚拟机》学习笔记5——Java Class类文件结构的相关文章

《深入Java虚拟机学习笔记》- 第18章 finally子句

本章主要介绍字节码实现的finally子句.包括相关指令以及这些指令的使用方式.此外,本章还介绍了Java源代码中finally子句所展示的一些令人惊讶的特性,并从字节码角度对这些特征进行了解释. 1.微型子例程 字节码中的finally子句表现的很像"微型子例程".Java虚拟机在每个try语句块和与其相关的catch子句的结尾处都会"调用"finally子句的子例程.finally子句结束后(这里的结束指的是finally子句中最后一条语句正常执行完毕,不包括抛

《深入Java虚拟机学习笔记》- 第4章 网络移动性

Java虚拟机学习笔记(四)网络移动性 <深入Java虚拟机学习笔记>- 第4章 网络移动性,布布扣,bubuko.com

《深入Java虚拟机学习笔记》- 第7章 类型的生命周期

一.类型生命周期的开始 如图所示 初始化时机 所有Java虚拟机实现必须在每个类或接口首次主动使用时初始化: 以下几种情形符合主动使用的要求: 当创建某个类的新实例时(或者通过在字节码中执行new指令,或者通过不明确的创建.反射.克隆和反序列化): 当调用某个类的静态方法时(即在字节码中执行invokestatic指令): 当使用某个类或接口的静态字段,或者对该字段赋值时(用final修饰的静态字段除外,它被初始化为一个编译时常量表达式): 当调用Java API中的某些反射方法: 当初始化某个

《深入Java虚拟机学习笔记》- 第13章 逻辑运算

<深入Java虚拟机学习笔记>- 第13章 浮点运算 <深入Java虚拟机学习笔记>- 第13章 逻辑运算,布布扣,bubuko.com

《深入Java虚拟机学习笔记》- 第12章 整数运算

Java虚拟机提供几种进行整数算术运算的操作码,他们执行基于int和long类型的运算.当byte.short和char类型值参与算术运算时,首先会将它们转换为int类型.这些操作码都不会抛出异常,溢出在这里通常可以被忽略. 整数加法 操作码 操作数 说明 iadd (无) 从栈中弹出两个int类型数,相加,然后将所得int类型结果压回栈 ladd (无) 从栈中弹出两个long类型数,相加,然后将所得long类型结果压回栈 将一个常量与局部变量相加 操作码 操作数 说明 iinc vindex

《深入Java虚拟机学习笔记》- 第2章 平台无关

Java虚拟机学习笔记(二)平台无关 <深入Java虚拟机学习笔记>- 第2章 平台无关,布布扣,bubuko.com

《深入Java虚拟机学习笔记》- 第14章 浮点运算

<深入Java虚拟机学习笔记>- 第13章 浮点运算

《深入Java虚拟机学习笔记》- 第19章 方法的调用与返回

<深入Java虚拟机学习笔记>- 第19章 方法的调用与返回

《深入Java虚拟机学习笔记》- 第5章 Java虚拟机

一.JVM的生命周期 当启动一个Java程序时,一个Java虚拟机实例就诞生了:当该程序关闭退出时,这个Java虚拟机也就随之消亡: JVM实例通过调用某个初始类的main方法来运行一个Java程序:这个main方法必须是public.static的,而且返回值必须是void:任何一个拥有这样的main方法的类都可以作为Java程序运行的起点: Java程序初始类中的main方法,将作为该程序初始线程的起点,其它任何线程都是由这个初始线程启动的: 守护线程和非守护线程 守护线程通常是由虚拟机自己

《深入Java虚拟机学习笔记》- 第17章 异常

<深入Java虚拟机学习笔记>- 第17章 异常