JVM5-class文件结构

1.Class文件

1.1 Class文件简介和发展历史

javac编译java文件
class 二进制查看CA FE BA BY开头(魔数).
class的规范从第1版到第8版改动很小.

1.2 Class文件结构概述

1.2.1 概述

  • 理解下CA FE BA BE:字符C为16进制(4位,0~15),CA(2个4位,8位,1个字节). 是无符号数.
  • Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件之中,中间没有添加任何分隔符,整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。
  • 当遇到8位字节以上的空间的数据项时,则会按照高位在前的方式分割成若干个8位字节进行存储。
  • Class文件中有两种数据类型,分别是无符号数(约比喻基础数据类型)和表(约比喻引用数据类型,表可以包含无符号数和表)。
    优点: 节省存储空间,提高程序性能

1.2.2 Class文件结构

  • 魔数
  • Class文件版本
  • 常量池
  • 访问标志
  • 类索引,父类索引,接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

JVM5-class文件结构

Class File format

type descriptor[number] remark
u4 magic[1] 0xCAFEBABE
u2 minor_version[1]
u2 major_version[1]
u2 constant_pool_count[1]
cp_info constant_pool[1][cosntant_pool_count – 1] index 0 is invalid
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]

一个U代表一个字节(两个字符,8个bit). u4代表4个字节

1.3 Class文件设计理念以及意义

语法->编译器->字节码->JVM
自己实现语法和编译器: Clojure,groovy,Jruby,Jython,Scala(运行在jvm之上的语言)

2 文件结构

//源码
public class HelloWorld {
    public static void main(String []args) {
        System.out.println("Hello World ..");
    }
}

编译后class 解读的原生class文件链接 ; 图片链接

通过ue查看class文件:

JVM5-class文件结构

位(bit):0或1
字节(Byte): 8个二进制位构成一个字节,存储空间的基本单位.1个字节存一个英文字符或者半个汉字.
16进制数字:每一个16进制数字占4个bit,所以2个16进制字符占8个bit为一个字节.

2.1 文件结构-魔数,版本号(magic,minor_version,major_version)

type descriptor[number] remark
u4 magic[1] 0xCAFEBABE
u2 minor_version[1]
u2 major_version[1]

CAFE BABE 4个字节开头1行0列,1行1列,1行2列,1行3列;
然后版本号:占4个字节:
前2字节(5,6位)是小版本号,
后2字节是大版本号(7,8位).

大版本号对应数字(十进制的数字,class通过二进制文件查看的为16进制,需要转换)
JDK 1.8 = 52
JDK 1.7 = 51
JDK 1.6 = 50
JDK 1.5 = 49
JDK 1.4 = 48
JDK 1.3 = 47
JDK 1.2 = 46
JDK 1.1 = 45

在上图中的HelloWorld.class中1行4列,1行5列,1行6列,1行7列是00 00 00 34—十进制—>0,52,也就是JDK1.8

2.2 文件结构-常量池(constant_pool_count,constant_pool)

type descriptor[number] remark
u2 constant_pool_count[1]
cp_info constant_pool[cosntant_pool_count – 1] index 0 is invalid

参考-Class_常量池参考文章

参考-Class文件中的常量池详解(上)

参考-Class文件中的常量池详解(下)

参考-Java字节码(.class文件)格式详解(一)[这个常量池画的很清楚]

2.2.1 常量池类型/含义列表

常量池中每一项常量都是一个表(14种表)

类型 标志 描述
0 表示不引用任何常量
CONSTANT_utf8_info 1 UTF-8编码的字符串
CONSTANT_Integer_info 3 整形字面量
CONSTANT_Float_info 4 浮点型字面量
CONSTANT_Long_info 5 长整型字面量
CONSTANT_Double_info 6 双精度浮点型字面量
CONSTANT_Class_info 7 类或接口的符号引用
CONSTANT_String_info 8 字符串类型字面量
CONSTANT_Fieldref_info 9 字段的符号引用
CONSTANT_Methodref_info 10 类中方法的符号引用
CONSTANT_InterfaceMethodref_info 11 接口中方法的符号引用
CONSTANT_NameAndType_info 12 字段或方法的符号引用
15 表示方法句柄
CONSTANT_MethodType_info 16 标志方法类型
CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

格式模板==cp_info format(长度标示)

type descriptor remark
u1 tag 该类型数目
u1 info[] 表的集合

1==CONSTANT_Utf8_info
记录字符串的值(represent constant string values. String content is encoded in modified UTF-8.)

type descriptor remark
u1 tag CONSTANT_Utf8 (1)
u2 length bytes所代表的字符串的长度
u1 bytes[length] 字符串的byte数据,可以通过DataInputStream中的readUtf()方法(实例方法或静态方法读取该二进制的字符串的值。)

3==CONSTANT_Integer_info
用于记录int类型的常量值(represent 4-byte numeric (int) constants:)

type descriptor remark
u1 tag CONSTANT_Integer (3)
u4 bytes 整型常量值

4==CONSTANT_Float_info
用于记录float类型的常量值(represent 4-byte numeric (float) constants:)

type descriptor remark
u1 tag CONSTANT_Float(4)
u4 bytes 单精度浮点型常量值

5==CONSTANT_Long_info
用于记录long类型的常量值(represent 8-byte numeric (long) constants:)

type descriptor remark
u1 tag CONSTANT_Long (5)
u4 high_bytes 长整型的高四位值
u4 low_bytes 长整型的低四位值

6==CONSTANT_Double_info
用于记录double类型的常量值(represent 8-byte numeric (double) constants:)

type descriptor remark
u1 tag CONSTANT_Double(6)
u4 high_bytes 双精度浮点的高四位值
u4 low_bytes 双精度浮点的低四位值

7==CONSTANT_Class_info format
用于记录类或接口名(used to represent a class or an interface)

type descriptor remark
u1 tag CONSTANT_Class (7)
u2 name_index constant_pool中的索引,CONSTANT_Utf8_info类型。表示类或接口名。

8==CONSTANT_String_info
用于记录常量字符串的值(represent constant objects of the type String:)

type descriptor remark
u1 tag CONSTANT_String(8)
u2 string_index constant_pool中的索引,CONSTANT_Utf8_info类型。表示String类型值。

9==CONSTANT_Fieldref_info
用于记录字段信息(包括类或接口中定义的字段以及代码中使用到的字段)

type descriptor remark
u1 tag CONSTANT_Fieldref(9)
u2 class_index constant_pool中的索引,CONSTANT_Class_info类型。记录定义该字段的类或接口。
u2 name_and_type_index constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)。

10==CONSTANT_Methodref_info
用于记录方法信息(包括类中定义的方法以及代码中使用到的方法)

type descriptor remark
u1 tag CONSTANT_Methodref(10)
u2 class_index constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的类。
u2 name_and_type_index constant_pool中的索引,CONSTANT_NameAndType_info类型。指定类中扽方法名(name)和方法描述符(descriptor)。

11==CONSTANT_InterfaceMethodref_info
用于记录接口中的方法信息(包括接口中定义的方法以及代码中使用到的方法)。

type descriptor remark
u1 tag CONSTANT_InterfaceMethodref(11)
u2 class_index constant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的接口。
u2 name_and_type_index constant_pool中的索引,CONSTANT_NameAndType_info类型。指定接口中的方法名(name)和方法描述符(descriptor)。

12==CONSTANT_NameAndType_info
记录方法或字段的名称(name)和描述符(descriptor)(represent a field or method, without indicating which class or interface type it belongs to:)。

type descriptor remark
u1 tag CONSTANT_NameAndType (12)
u2 name_index constant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称。
u2 descriptor_index constant_pool中的索引,CONSTANT_utf8_info类型。指定字段或方法的描述符(见附录C)

16==CONSTANT_MethodType_info
暂缺
18==CONSTANT_InvokeDynamic_info
暂缺

2.2.3 解读

常量池长度(constant_pool_count)
占u2(2个字节):
HelloWorld.class的文件中 1行8列,1行9列 是00 22;00 22—十进制–>34个(组)常量.

第一组常量
1行a 即 0A—十进制—>10(10代表指向10=CONSTANT_Methodref_info,并且0A是第一个常量池(常量池编号为1)),
此时查看10=CONSTANT_Methodref_info的结构规范2.2.1中的10[u1|tag,u2|classindex,u2|name_and_type_index],
那就是后2个字节 1行b,1行c 即00 06为CONSTANT_Methodref_info的classindex,00 06–十进制–>6(指向第6个常量池位置).
再后2个字节 1行d,1行e 即00 14为CONSTANT_Methodref_info的name_and_type_index,00 14–十进制–>20(指向第20个常量池位置)

第二组常量
1行f列 即09—十进制—>9(9代表9=CONSTANT_Fieldref_info,并且09是第二个常量池);
此时查看9=CONSTANT_Fieldref_info的规范结构[u1|tag,u2|class_index,u2|name_and_type_index],
那就是后2个字节 2行0列,2行1列 即 00 15为class_index,00 15—十进制—>21(指向21号位置),
在后2个字节 2行2列,2行3列 即00 16为name_and_type_index,00 16—十进制—>22(指向22号位置),

第三组常量
2行4列 即08—十进制—>8(代表8=CONSTANT_String_info,并且08是第三个常量池);
此时查看8==CONSTANT_String_info的规范结构[u1|tag,u2|string_index],
那就是后2个字节 2行5列,2行6列 即00 17为string_index —十进制—>23(指向23号位置)

第四组常量
2行7列 即0A—十进制—>10(10=CONSTANT_Methodref_info,并且0A是第四个常量池);
此时查看10=CONSTANT_Methodref_info的结构规范[u1|tag,u2|classindex,u2|name_and_type_index],
那就是后2个字节 2行8,2行9 即00 18为classindex,00 18–十进制–>24(指向第24个常量池位置).
再后2个字节 2行a,2行b 即00 19为name_and_type_index,00 19–十进制–>25(指向第25个常量池位置)

第五组常量
2行c列 即07—十进制—>7(7=CONSTANT_Class_info format,并且07是第五个常量池);
此时查看7==CONSTANT_Class_info format的规范结构[u1|tag,u2|name_index],
那就是后2个字符 2行d列,2行e列 即00 1A为name_index;00 1A—十进制—>26(指向第26个常量池位置)

第六组常量
2行f列 即07—十进制—>7(7=CONSTANT_Class_info format,并且07是第六个常量池);
此时查看7==CONSTANT_Class_info format的规范结构[u1|tag,u2|name_index],
那就是后2个字符 3行0列,3行1列 即00 1B为name_index;00 1B—十进制—>27(指向第27个常量池位置)

第七组常量
3行2列 即01—十进制—>1(1=CONSTANT_Utf8_info,并且01是第七个常量池);
此时查看1==CONSTANT_Utf8_info的结构规范[u1|tag,u2|length,u1|bytes[length]],
那就是后2列 3行3列,3行4列 即00 06为length,00 06—十进制—>6,
再后6和长度 3行5列,3行6列,3行7列,3行8列,3行9列,3行10列 即3C 69 6E 74 3E为lbytes[length],
3C 69 6E 74 3E—十进制—>60 89 105 110 116 62 —对应字符—>
… …

可以一直往后读,直至34个(组)常量.

然后就可用工具读了. java工具 javap,读完如下:
会发现:
#1 指向#6(java/lang/Object),#20(#7(),#8(()V)),然后后面就显示了#6,#20的值.

javap -verbose HelloWorld.class
Classfile /D:/workspace/***/HelloWorld.class
  Last modified 2017-12-27; size 536 bytes
  MD5 checksum a77b7f0d7e173b1c9b6040311535c45d
  Compiled from "HelloWorld.java"
public class HelloWorld
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #6.#20         // java/lang/Object."<init>":()V
   #2 = Fieldref           #21.#22        // java/lang/System.out:Ljava/io/Prin
Stream;
   #3 = String             #23            // Hello World ..
   #4 = Methodref          #24.#25        // java/io/PrintStream.println:(Ljava
lang/String;)V
   #5 = Class              #26            // HelloWorld
   #6 = Class              #27            // java/lang/Object
   #7 = Utf8               <init>
   #8 = Utf8               ()V
   #9 = Utf8               Code
  #10 = Utf8               LineNumberTable
  #11 = Utf8               LocalVariableTable
  #12 = Utf8               this
  #13 = Utf8               LHelloWorld;
  #14 = Utf8               main
  #15 = Utf8               ([Ljava/lang/String;)V
  #16 = Utf8               args
  #17 = Utf8               [Ljava/lang/String;
  #18 = Utf8               SourceFile
  #19 = Utf8               HelloWorld.java
  #20 = NameAndType        #7:#8          // "<init>":()V
  #21 = Class              #28            // java/lang/System
  #22 = NameAndType        #29:#30        // out:Ljava/io/PrintStream;
  #23 = Utf8               Hello World ..
  #24 = Class              #31            // java/io/PrintStream
  #25 = NameAndType        #32:#33        // println:(Ljava/lang/String;)V
  #26 = Utf8               HelloWorld
  #27 = Utf8               java/lang/Object
  #28 = Utf8               java/lang/System
  #29 = Utf8               out
  #30 = Utf8               Ljava/io/PrintStream;
  #31 = Utf8               java/io/PrintStream
  #32 = Utf8               println
  #33 = Utf8               (Ljava/lang/String;)V
{
  public HelloWorld();
    descriptor: ()V
    flags: ACC_PUBLIC
    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
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   LHelloWorld;

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #2                  // Field java/lang/System.out:Lja
a/io/PrintStream;
         3: ldc           #3                  // String Hello World ..
         5: invokevirtual #4                  // Method java/io/PrintStream.pri
tln:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 3: 0
        line 4: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  args   [Ljava/lang/String;
}
SourceFile: "HelloWorld.java"

2.3 文件结构-访问标志

type descriptor[number] remark
u2 access_flags[1]

2.3.1 访问标志概述

常量池后面就是访问标志。

u2|access_flags,占用2个字节(16位).
access_flags:

标志名称 标志值 含义 代码中表示
ACC_PUBLIC 0x00 01 是否为Public类型 public
ACC_FINAL 0x00 10 是否被声明为final,只有类可以设置 final
ACC_SUPER 0x00 20 是否允许使用invokespecial字节码指令的新语义. extends
ACC_INTERFACE 0x02 00 标志这是一个接口 interface
ACC_ABSTRACT 0x04 00 是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假 abstract
ACC_SYNTHETIC 0x10 00 标志这个类并非由用户代码产生 所有类型
ACC_ANNOTATION 0x20 00 标志这是一个注解 annotation
ACC_ENUM 0x40 00 标志这是一个枚举 enum

ACC_PUBLIC(public)、ACC_PRIVATE(private)、ACC_PROTECTED(protected)、ACC_FINAL(final)、ACC_SUPER(extends)、ACC_INTERFACE(接口)、ACC_ABSTRACT(抽象类)、ACC_ANNOTATION(注解类型)、ACC_ENUM(枚举类型)、ACC_DEPRECATED(标记了@Deprecated注解的类)、ACC_SYNTHETIC

JVM5-class文件结构
注意:图片中的ACC_INTERFACE的值不对,应该以上面表格为准.ACC_INTERFACE=0x0400.

2.3.2 解读

2.3.2.1 解读HelloWorld.class

根据2.2.3中根据javap工具解读的结果,能查看到最后一个常量:
#33 = Utf8 (Ljava/lang/String;)V
则再在HelloWorld-Class-Binary.png中找到#33的位置,则其后面就是访问标志的开始.
通过图片中会看到是从180h行0列开始为访问标志 .
180h行0列,180h行1列 即:00 21,对照2.3.1中的组合表.对表00 21=0x0001 & 0x0020,(即代表acc_public和acc_super[有个语意JDK1.0.2为界前后不同,如果acc_super有值1代表1.0.2之后版本,如果acc_super为0则表示1.0.2之前].),
所以含义代表 public Class.
也可以通过上面2.2.3 解读 javap -verbose 的结果中查看到
** flags: ACC_PUBLIC, ACC_SUPER **

2.3.2.2 解读HelloWorldInterface.class

//源码
public interface HelloWorldInterface {
}

编译后class HelloWorldInterface.class

JVM5-class文件结构

HelloWorldInterface.class 的javap -verbose 结果:

javap -verbose D:\workspace\HelloWorldInterface.class
Classfile /D:/workspace/HelloWorldInterface.class
 Last modified 2017-12-30; size 119 bytes
 MD5 checksum e8d0ce8260a978e105c60e9852245e1a
 Compiled from "HelloWorldInterface.java"
public interface HelloWorldInterface
 minor version: 0
 major version: 52
 flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT
Constant pool:
 #1 = Class              #5              // HelloWorldInterface
 #2 = Class              #6              // java/lang/Object
 #3 = Utf8               SourceFile
 #4 = Utf8               HelloWorldInterface.java
 #5 = Utf8               HelloWorldInterface
 #6 = Utf8               java/lang/Object
{
}
SourceFile: "HelloWorldInterface.java"

通过查看二进制的java/lang/Object后的2个字节 060h行1列,060h行2列 即:06 01 = 0x04 & 0x02 & 0x00 ===>ACC_PUBLIC ACC_ABSTRACT ACC_INTERFACE ,也可以在javap的结果中查看
flags: ACC_PUBLIC, ACC_INTERFACE, ACC_ABSTRACT 得到同样的结果.

2.2.3 参考文章

JVM-class文件完全解析-访问标志 (简明扼要)
[深入字节码 – ASM 关键接口 ClassVisitor] https://my.oschina.net/u/1166271/blog/163637(原理基础)

《Java虚拟机原理图解》1.3、class文件中的访问标志、类索引、父类索引、接口索引集合 [文中讲的很详尽]

2.4 文件结构-类索引(this_class,super_class,interfaces_count)

type descriptor[number] remark
u2 this_class[1]
u2 super_class[1]
u2 interfaces_count
u2 interfaces[interfaces_count]

2.4.1 概述

2.4.2 解读

接着解读HelloWorldInterface.class
060h行3列,060h行4列 即00 01 是this_class,类,00 01—十进制—>1,表示指向常量池的第1个位置,查看2.3.2.2的javap的结果,看到#1 = Class, #5 // HelloWorldInterface (也就是最终指向了HelloWorldInterface)

接着060h行5列,060h行6列 即00 02 是super_class,父类,00 02—十进制—>2,表示指向常量池的第2个位置,查看2.3.2.2的javape结果,看到#2 = Class, #6 // java/lang/Object (也就是最终指向了java/lang/Object)

接着060h行7列,060h行8列 即00 00 是interfaces_count,接口数量,00 00—十进制—>0,表示没有实现接口,

2.4.3 解读HelloExtendImp.class

2.4.3.1 信息准备

//源码
package classstruct;

import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.FileInputStream;
import java.io.FileNotFoundException;


public class HelloExtendImp extends FileInputStream implements Runnable,ActionListener{


    public HelloExtendImp(String name) throws FileNotFoundException {
        super(name);
    }

    @Override
    public void actionPerformed(ActionEvent e) {

    }

    @Override
    public void run() {

    }
}

HelloExtendImp.class

二进制查看如下:

JVM5-class文件结构

D:\IdeaProjects\out\production\jvmStu\classstruct>javap -verb
loExtendImp.class
Classfile /D:/IdeaProjects/out/production/jvmStu/classstruct/
tendImp.class
  Last modified 2017-12-31; size 703 bytes
  MD5 checksum b0aba17a3e3cf2b32f0ad93f5d6e9b96
  Compiled from "HelloExtendImp.java"
public class classstruct.HelloExtendImp extends java.io.FileInputStream i
ts java.lang.Runnable,java.awt.event.ActionListener
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#25         // java/io/FileInputStream."<in
java/lang/String;)V
   #2 = Class              #26            // classstruct/HelloExtendImp
   #3 = Class              #27            // java/io/FileInputStream
   #4 = Class              #28            // java/lang/Runnable
   #5 = Class              #29            // java/awt/event/ActionListene
   #6 = Utf8               <init>
   #7 = Utf8               (Ljava/lang/String;)V
   #8 = Utf8               Code
   #9 = Utf8               LineNumberTable
  #10 = Utf8               LocalVariableTable
  #11 = Utf8               this
  #12 = Utf8               Lclassstruct/HelloExtendImp;
  #13 = Utf8               name
  #14 = Utf8               Ljava/lang/String;
  #15 = Utf8               Exceptions
  #16 = Class              #30            // java/io/FileNotFoundExceptio
  #17 = Utf8               actionPerformed
  #18 = Utf8               (Ljava/awt/event/ActionEvent;)V
  #19 = Utf8               e
  #20 = Utf8               Ljava/awt/event/ActionEvent;
  #21 = Utf8               run
  #22 = Utf8               ()V
  #23 = Utf8               SourceFile
  #24 = Utf8               HelloExtendImp.java
  #25 = NameAndType        #6:#7          // "<init>":(Ljava/lang/String;
  #26 = Utf8               classstruct/HelloExtendImp
  #27 = Utf8               java/io/FileInputStream
  #28 = Utf8               java/lang/Runnable
  #29 = Utf8               java/awt/event/ActionListener
  #30 = Utf8               java/io/FileNotFoundException
{
  public classstruct.HelloExtendImp(java.lang.String) throws java.io.File
dException;
    descriptor: (Ljava/lang/String;)V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: aload_0
         1: aload_1
         2: invokespecial #1                  // Method java/io/FileInput
"<init>":(Ljava/lang/String;)V
         5: return
      LineNumberTable:
        line 13: 0
        line 14: 5
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       6     0  this   Lclassstruct/HelloExtendImp;
            0       6     1  name   Ljava/lang/String;
    Exceptions:
      throws java.io.FileNotFoundException

  public void actionPerformed(java.awt.event.ActionEvent);
    descriptor: (Ljava/awt/event/ActionEvent;)V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=2, args_size=2
         0: return
      LineNumberTable:
        line 19: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lclassstruct/HelloExtendImp;
            0       1     1     e   Ljava/awt/event/ActionEvent;

  public void run();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=0, locals=1, args_size=1
         0: return
      LineNumberTable:
        line 24: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       1     0  this   Lclassstruct/HelloExtendImp;
}
SourceFile: "HelloExtendImp.java"

2.4.2.2 解读

通过javap结果查看到:
#30 = Utf8 java/io/FileNotFoundException(1d0h行0列,1d0h行2列)为常量池最后结束.

1d0h行3列,1d0h行4列 即00 21 为access_flags.00 21=0x0001 & 0x0020==>flags: ACC_PUBLIC, ACC_SUPER.(具体解析2.3中)
1d0h行5列,1d0h行6列 即00 02为this_class,指向常量池中的#02,查看javap结果即为类(classstruct/HelloExtendImpclassstruct/HelloExtendImp).
1d0h行7列,1d0h行8列 即00 03为super_class,指向常量池中的#03,查看javap结果即为父类(java/io/FileInputStream).
1d0h行9列,1d0h行a列 即00 02为interfaces_count,接口数目为2个.
1d0h行b列,1d0h行c列 即00 04为interface1,指向常量池中的#04,查看javap结果即为接口1(java/lang/Runnable).
1d0h行d列,1d0h行e列 即00 05为interface2,指向常量池中的#05,查看javap结果即为接口2(java/awt/event/ActionListene).

2.5 文件结构-字段(field_info)表集合

type descriptor[number] remark
u2 fields_count[1]
field_info fields[fields_count]

JVM-class文件完全解析-字段表集合

2.5.1 概述

字段表(field_info)用于描述接口或者类中声明的变量.字段包括类级变量以及实例级变量,但是不包括在方法内部声明的局部变量.

2.5.1.1 字段(变量)表的结构

field_info:

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

2.5.1.2 字段访问标志

字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常相似的,都是一个u2的数据类型.

标志名称 标志值 含义
ACC_PUBLIC 0x00 01 字段是否为public
ACC_PRIVATE 0x00 02 字段是否为private
ACC_PROTECTED 0x00 04 字段是否为protected
ACC_STATIC 0x00 08 字段是否为static
ACC_FINAL 0x00 10 字段是否为final
ACC_VOLATILE 0x00 40 字段是否为volatile
ACC_TRANSTENT 0x00 80 字段是否为transient
ACC_SYNCHETIC 0x10 00 字段是否为由编译器自动产生
ACC_ENUM 0x40 00 字段是否为enum

跟随access_flags标志的是两项索引值:name_index和descriptor_index,它们都是对常量池的引用,分别代表着字段的简单名称以及字段方法和方法的描述符.
 
JVM5-class文件结构
 
 
 
JVM5-class文件结构

2.5.1.3 描述符标志含义

描述符的作用是用来描述字段的数据类型,方法的参数列表(包括数量,类型以及顺序)和返回值.根据描述符规则,基本数据类型以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符加L加对象名的全限定名来表示.

标志符 含义
B 基本数据类型byte
C 基本数据类型char
D 基本数据类型double
F 基本数据类型float
I 基本数据类型int
J 基本数据类型long
S 基本数据类型short
Z 基本数据类型boolean
V 基本数据类型void
L 对象类型

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

用描述符来描述方法时,按照先参数列表,后返回值的顺序来描述,参数列表按照参数的严格顺序放在一组小括号"()"之内.

字段表集合中不会列出从父类或者父接口中继承而来的字段,但有可能列出原来Java代码中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段.另外,在Java语言中字段是无法重载的,两个字段的数据类型,修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果连个字段的描述符不一致,那字段重名就是合法的.

2.5.2 解读

2.5.2.1 待解读信息

//源码
package classstruct;

public class HelloWorldField {
    private int a ;

    public byte b;

    public static Object obj;

    protected  Object [] ojbs;


}

HelloWorldField.class

二进制查看如下:

JVM5-class文件结构

D:\workspace\javap -verbose HelloWorldField.class
Classfile /D:/workspace/HelloWorldField.class
  Last modified 2017-12-31; size 398 bytes
  MD5 checksum 8efd9fc928289285f5f89ce3c9f101d3
  Compiled from "HelloWorldField.java"
public class classstruct.HelloWorldField
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#21         // java/lang/Object."<init>":()V
   #2 = Class              #22            // classstruct/HelloWorldField
   #3 = Class              #23            // java/lang/Object
   #4 = Utf8               a
   #5 = Utf8               I
   #6 = Utf8               b
   #7 = Utf8               B
   #8 = Utf8               obj
   #9 = Utf8               Ljava/lang/Object;
  #10 = Utf8               ojbs
  #11 = Utf8               [Ljava/lang/Object;
  #12 = Utf8               <init>
  #13 = Utf8               ()V
  #14 = Utf8               Code
  #15 = Utf8               LineNumberTable
  #16 = Utf8               LocalVariableTable
  #17 = Utf8               this
  #18 = Utf8               Lclassstruct/HelloWorldField;
  #19 = Utf8               SourceFile
  #20 = Utf8               HelloWorldField.java
  #21 = NameAndType        #12:#13        // "<init>":()V
  #22 = Utf8               classstruct/HelloWorldField
  #23 = Utf8               java/lang/Object
{
  public byte b;
    descriptor: B
    flags: ACC_PUBLIC

  public static java.lang.Object obj;
    descriptor: Ljava/lang/Object;
    flags: ACC_PUBLIC, ACC_STATIC

  protected java.lang.Object[] ojbs;
    descriptor: [Ljava/lang/Object;
    flags: ACC_PROTECTED

  public classstruct.HelloWorldField();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>
":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lclassstruct/HelloWorldField;
}
SourceFile: "HelloWorldField.java"

2.5.2.2 解读2.5.2.1

110h行b列,110h行c列 即00 21 为access_flags.00 21=0x0001 & 0x0020==>flags: ACC_PUBLIC,ACC_SUPER.(具体解析2.3中)
110h行d列,110h行e列 即00 02为this_class,指向常量池中的#02
110h行f列,120h行0列 即00 03为super_class,指向常量池中的#03
120h行1列,120h行2列 即00 00为interfaces_count,接口数目为0个.
接口数目将为0,则后面就没有interfaces了,然后就是fields_count了,即:
120h行3列,120h行4列 即00 04为4,指有4个field_info(类变量).
第一个field_info:
field_info的结构参考(2.5.1.1 字段表的结构):
120h行5列,120h行6列 即00 02为u2|access_flags,说明为private类型(根据2.5.1.2 字段访问标志).
120h行7列,120h行8列 即00 04为u2|name_index1,变量名称指向常量池中的#04,(#4 = Utf8 a)
120h行9列,120h行a列 即00 05为u2|descriptor_index,指向常量池中的#05(#5 = Utf8 I),I代表基本类型int(2.5.1.3 描述符标志含义).
120h行b列,120h行c列 即00 00为u2|attributes_count,长度为0,所以后面的attribute_info也就不占位置了,
那就可以下一个field_info了:

第二个field_info:
field_info的结构参考(2.5.1.1 字段表的结构):
120h行d列,120h行e列 即00 01为u2|access_flags,说明为private类型(根据2.5.1.2 字段访问标志).
120h行f列,130h行0列 即00 06为u2|name_index1,变量名称指向常量池中的#06,(#6 = Utf8 b)
130h行1列,130h行2列 即00 07为u2|descriptor_index,指向常量池中的#07(#7 = Utf8 B),B代表byte.(2.5.1.3 描述符标志含义).
130h行3列,130h行4列 即00 00为u2|attributes_count,长度为0,所以后面的attribute_info也就不占位置了,

那就可以下一个field_info了:

第三个field_info:
field_info的结构参考(2.5.1.1 字段表的结构):
130h行5列,130h行6列 即00 09为u2|access_flags,00 09=0x00 01 & 0x00 08==>public static根据2.5.1.2 字段访问标志).
130h行7列,130h行8列 即00 08为u2|name_index1,变量名称指向常量池中的#08,(#8 = Utf8 obj)
130h行9列,130h行a列 即00 09为u2|descriptor_index,指向常量池中的#09(#9 = Utf8 Ljava/lang/Object;),L代表对象类型.(2.5.1.3 描述符标志含义).
130h行b列,130h行c列 即00 00为u2|attributes_count,长度为0,所以后面的attribute_info也就不占位置了,

那就可以下一个field_info了:

第四个field_info:
field_info的结构参考(2.5.1.1 字段表的结构):
130d行5列,130h行e列 即00 04为u2|access_flags,s说明为(根据2.5.1.2 字段访问标志).
130h行f列,140h行0列 即00 0A为u2|name_index1,变量名称指向常量池中的#10,(#10 = Utf8 ojbs)
140h行1列,140h行2列 即00 0B为u2|descriptor_index,指向常量池中的#11(#11 = Utf8 [Ljava/lang/Object;),[L代表对象数组类型. (2.5.1.3 描述符标志含义).
140h行3列,140h行4列 即00 00为u2|attributes_count,长度为0,所以后面的attribute_info也就不占位置了,

2.6 文件结构-方法表集合

type descriptor[number] remark
u2 methods_count[1]
method_info methods[methods_count]

2.6.1 概述

方法表的构造如同字段表一样,依次包括了访问标志(access_flags),名称索引(name_index),描述符索引(descriptor_index),属性表集合(attributes)几项.

2.6.1.1 方法表的结构

field_info:

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

2.6.1.2 方法访问标志

字段修饰符放在access_flags项目中,它与类中的access_flags项目是非常相似的,都是一个u2的数据类型.

标志名称 标志值 含义
ACC_PUBLIC 0x00 01 方法是否为public
ACC_PRIVATE 0x00 02 方法是否为private
ACC_PROTECTED 0x00 04 方法是否为protected
ACC_STATIC 0x00 08 方法是否为static
ACC_FINAL 0x00 10 方法是否为final
ACC_SYHCHRONRIZED 0x00 20 方法是否为synchronized
ACC_BRIDGE 0x00 40 方法是否是有编译器产生的方法
ACC_VARARGS 0x00 80 方法是否接受参数
ACC_NATIVE 0x01 00 方法是否为native
ACC_ABSTRACT 0x04 00 方法是否为abstract
ACC_STRICTFP 0x08 00 方法是否为strictfp
ACC_SYNTHETIC 0x10 00 方法是否是有编译器自动产生的

方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为"Code"的属性里面,属性表作为calss文件格式中最具扩展的一种数据项目.

在Java语言中,要重载一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名中,因此Java语言里面是无法仅仅靠返回值的不同来堆一个已有方法进行重载的.但是在class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存.也就是说,如果两个方法有相同的名称和特征签名,但是返回值不同,那么也是可以合法共存与同一个class文件中的.

2.6.2 解读

会默认有一个构造方法. 编译的时候就会产生.在解读class的时候就会显示有2个方法.
会发现有: 
init 就是init方法. 构造方法
()V 表示方法和viod 构造方法 修饰属性
(II)I ()里面2个是入参类型int,()外的I是返回类型.

源码

package classstruct;

public class HelloWorldMethod {
    public int add(int a,int b){
        return a+b;
    }

    private  String conact(String str1,Object objStr){
        return str1+objStr;
    }
}

HelloWorldMethod.class

JVM5-class文件结构

javap结果

D:\workspacejavap -verbose HelloWorldMethod.class
Classfile /D:/workspace/HelloWorldMethod.class
  Last modified 2018-1-2; size 827 bytes
  MD5 checksum b8942bc40d0567eac771e2620646aee1
  Compiled from "HelloWorldMethod.java"
public class classstruct.HelloWorldMethod
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#29         // java/lang/Object."<init>":()V
   #2 = Class              #30            // java/lang/StringBuilder
   #3 = Methodref          #2.#29         // java/lang/StringBuilder."<init>":()
V
   #4 = Methodref          #2.#31         // java/lang/StringBuilder.append:(Lja
va/lang/String;)Ljava/lang/StringBuilder;
   #5 = Methodref          #2.#32         // java/lang/StringBuilder.append:(Lja
va/lang/Object;)Ljava/lang/StringBuilder;
   #6 = Methodref          #2.#33         // java/lang/StringBuilder.toString:()
Ljava/lang/String;
   #7 = Class              #34            // classstruct/HelloWorldMethod
   #8 = Class              #35            // java/lang/Object
   #9 = Utf8               <init>
  #10 = Utf8               ()V
  #11 = Utf8               Code
  #12 = Utf8               LineNumberTable
  #13 = Utf8               LocalVariableTable
  #14 = Utf8               this
  #15 = Utf8               Lclassstruct/HelloWorldMethod;
  #16 = Utf8               add
  #17 = Utf8               (II)I
  #18 = Utf8               a
  #19 = Utf8               I
  #20 = Utf8               b
  #21 = Utf8               conact
  #22 = Utf8               (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Stri
ng;
  #23 = Utf8               str1
  #24 = Utf8               Ljava/lang/String;
  #25 = Utf8               objStr
  #26 = Utf8               Ljava/lang/Object;
  #27 = Utf8               SourceFile
  #28 = Utf8               HelloWorldMethod.java
  #29 = NameAndType        #9:#10         // "<init>":()V
  #30 = Utf8               java/lang/StringBuilder
  #31 = NameAndType        #36:#37        // append:(Ljava/lang/String;)Ljava/la
ng/StringBuilder;
  #32 = NameAndType        #36:#38        // append:(Ljava/lang/Object;)Ljava/la
ng/StringBuilder;
  #33 = NameAndType        #39:#40        // toString:()Ljava/lang/String;
  #34 = Utf8               classstruct/HelloWorldMethod
  #35 = Utf8               java/lang/Object
  #36 = Utf8               append
  #37 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #38 = Utf8               (Ljava/lang/Object;)Ljava/lang/StringBuilder;
  #39 = Utf8               toString
  #40 = Utf8               ()Ljava/lang/String;
{
  public classstruct.HelloWorldMethod();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>
":()V
         4: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       5     0  this   Lclassstruct/HelloWorldMethod;

  public int add(int, int);
    descriptor: (II)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=3, args_size=3
         0: iload_1
         1: iload_2
         2: iadd
         3: ireturn
      LineNumberTable:
        line 5: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       4     0  this   Lclassstruct/HelloWorldMethod;
            0       4     1     a   I
            0       4     2     b   I
}
SourceFile: "HelloWorldMethod.java"

详细解读过程在之前的基础上继续分析即可.其中方法中包含的属性相关见2.7.
javap中的后面{}包含内容即和方法属性相关信息.

//这个代表2个入参I,出参也是一个I
 #17 = Utf8               (II)I 

2.7 文件结构-属性表集合

type descriptor[number] remark
u2 attributes_count[1]
attribute_info attributes[attributes_count]

2.7.1 概述

在class文件,字段表,方法表都可以携带自己的属性表集合(像前面方法表的时候就用到"code"这个属性表)以用于描述某些场景专有的信息

2.7.1.1 属性表定义的结构

对于每个属性,它的名称需要从常量池中引用一个CONSTANT_utf8_info类型的常量类表示,而属性值的结构则是完全自定义的,只需要通过一个u4的长度属性区说明属性值所占用的位数即可

类型 名称 数量
u2 attribute_name_index 1
u2 attribute_length 1
u1 info attribute_length

2.7.1.2 虚拟机中预定义的属性

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量池
Deprecated 类,方法,字段表 被声明为deprecated的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部便狼描述
StackMapTable Code属性 JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配
Signature 类,方法表,字段表 用于支持泛型情况下的方法签名
SourceFile 类文件 记录源文件名称
SourceDebugExtension 类文件 用于存储额外的调试信息
Synthetic 类,方法表,字段表 标志方法或字段为编译器自动生成的
LocalVariableTypeTable 使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations 类,方法表,字段表 为动态注解提供支持
RuntimeInvisibleAnnotations 表,方法表,字段表 用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotation 方法表 作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法
RuntimeInvisibleParameterAnnotation 方法表 作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数
AnnotationDefault 方法表 用于记录注解类元素的默认值
BootstrapMethods 类文件 用于保存invokeddynamic指令引用的引导方式限定符

2.7.1.3 Code属性详解

Java程序方法体中的代码经过Javac编译处理后,最终变为字节码指令存储在Code属性中.Code属性出现在方法表的属性集合中,但是并非所有的方法表都有这个属性.例如接口或类中的方法就不存在Code属性了.

在字节码指令之后的是方法的是方法的显式异常处理表集合,异常表对于Code属性来说并不是必须参在的
结构:

类型 名称 数量 备注
u2 attribute_name_index 1 属性名称
u4 attribute_length 1 属性长度
u2 max_stack 1 最大栈深度
u2 max_locals 1 局部变量表所需空间
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
exception_info exception_table exception_length
u2 attributes_count 1
attribute_info attributes attributes_count

2.7.1.4 Exceptions属性详解

Exception属性的作用是列出方法中能抛出的受查异常Check Exceptions,也就是方法描述时在throws关键字之后列举的异常

类型 名称 数量
u2 attribute_name_index 1
u2 attribute_lrngth 1
u2 attribute_of_exception 1
u2 exception_index_tsble number_of_exceptions

Exception属性中的number_of_exceptions项表示方法可能抛出的number_of_exceptions种受查异常,每一种受查异常使用一个exception_index_tsble项表示,exception_index_tsble是一个指向常量池中CONSTANT_Class_info型常量的索引,代表了该受查异常的类型.

2.7.1.5 LineNumberTable属性详解

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 line_number_table_length 1
line_number_info line_number_table line_number_table_length

line_number_table是一个数量为line_number_table_length,类型为line_number_info的集合,line_number_info表包括了start_PC和line_number两个u2类型的数据项,前者是字节码行号,后者是Java源代码行号.
 
 
  LineNumberTable属性用于描述Java源代码行号与字节码行号(字节码偏移量)之间的对应关系。它并不是运行时必须的属性,但默认会生成到Class文件之中,可以在Javac中使用-g:none或-g:lines选项来取消或要求生成这项信息。如果选择不生成LineNumberTable属性表,对程序运行产生的最主要的影响就是在抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候无法按照源码来设置断点

2.7.1.6 总结

虚拟机预定义的属性有20多个,就不意一一介绍,基本上和上述的几个属性差不多.理解含义后,主要通过工具来解读了.
javap 的{}内容为方法和属性的解读.

3 参考文章

这个系列将JVM都不错
这个系列将JVM都不错2