JVM5-class文件结构

30 篇文章 0 订阅
3 篇文章 0 订阅

1.Class文件

1.0 字符与字节

**ASCII码:**一个英文字母(不分大小写)占一个字节的空间。一个二进制数字序列,在计算机中作为一个数字单元,一般为8位二进制数。换算为十进制 ,最小值-128,最大值127。如一个ASCII码就是一个字节。

**UTF-8编码:**一个英文字符等于一个字节,一个中文(含繁体)等于三个字节。中文标点占三个字节,英文标点占一个字节

**Unicode编码:**一个英文等于两个字节,一个中文(含繁体)等于两个字节。中文标点占两个字节,英文标点占两个字节

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文件版本
  • 常量池
  • 访问标志
  • 类索引,父类索引,接口索引集合
  • 字段表集合
  • 方法表集合
  • 属性表集合

Class文件格式

Class File format

typedescriptor[number]remark
u4magic[1]0xCAFEBABE
u2minor_version[1]
u2major_version[1]
u2constant_pool_count[1]
cp_infoconstant_pool[1][cosntant_pool_count – 1]index 0 is invalid
u2access_flags[1]
u2this_class[1]
u2super_class[1]
u2interfaces_count[1]
u2interfaces[interfaces_count]
u2fields_count[1]
field_infofields[fields_count]
u2methods_count[1]
method_infomethods[methods_count]
u2attributes_count[1]
attribute_infoattributes[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文件:

HelloWorld-Class-Binary.png

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

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

typedescriptor[number]remark
u4magic[1]0xCAFEBABE
u2minor_version[1]
u2major_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)

typedescriptor[number]remark
u2constant_pool_count[1]
cp_infoconstant_pool[cosntant_pool_count – 1]index 0 is invalid

参考-Class_常量池参考文章

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

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

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

2.2.1 常量池类型/含义列表

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

类型标志描述
0表示不引用任何常量
CONSTANT_utf8_info1UTF-8编码的字符串
CONSTANT_Integer_info3整形字面量
CONSTANT_Float_info4浮点型字面量
CONSTANT_Long_info5长整型字面量
CONSTANT_Double_info6双精度浮点型字面量
CONSTANT_Class_info7类或接口的符号引用
CONSTANT_String_info8字符串类型字面量
CONSTANT_Fieldref_info9字段的符号引用
CONSTANT_Methodref_info10类中方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中方法的符号引用
CONSTANT_NameAndType_info12字段或方法的符号引用
__15表示方法句柄
CONSTANT_MethodType_info16标志方法类型
CONSTANT_InvokeDynamic_info18表示一个动态方法调用点

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

typedescriptorremark
u1tag该类型数目
u1info[]表的集合

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

typedescriptorremark
u1tagCONSTANT_Utf8 (1)
u2lengthbytes所代表的字符串的长度
u1bytes[length]字符串的byte数据,可以通过DataInputStream中的readUtf()方法(实例方法或静态方法读取该二进制的字符串的值。)

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

typedescriptorremark
u1tagCONSTANT_Integer (3)
u4bytes整型常量值

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

typedescriptorremark
u1tagCONSTANT_Float(4)
u4bytes单精度浮点型常量值

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

typedescriptorremark
u1tagCONSTANT_Long (5)
u4high_bytes长整型的高四位值
u4low_bytes长整型的低四位值

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

typedescriptorremark
u1tagCONSTANT_Double(6)
u4high_bytes双精度浮点的高四位值
u4low_bytes双精度浮点的低四位值

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

typedescriptorremark
u1tagCONSTANT_Class (7)
u2name_indexconstant_pool中的索引,CONSTANT_Utf8_info类型。表示类或接口名。

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

typedescriptorremark
u1tagCONSTANT_String(8)
u2string_indexconstant_pool中的索引,CONSTANT_Utf8_info类型。表示String类型值。

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

typedescriptorremark
u1tagCONSTANT_Fieldref(9)
u2class_indexconstant_pool中的索引,CONSTANT_Class_info类型。记录定义该字段的类或接口。
u2name_and_type_indexconstant_pool中的索引,CONSTANT_NameAndType_info类型。指定类或接口中的字段名(name)和字段描述符(descriptor)。

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

typedescriptorremark
u1tagCONSTANT_Methodref(10)
u2class_indexconstant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的类。
u2name_and_type_indexconstant_pool中的索引,CONSTANT_NameAndType_info类型。指定类中扽方法名(name)和方法描述符(descriptor)。

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

typedescriptorremark
u1tagCONSTANT_InterfaceMethodref(11)
u2class_indexconstant_pool中的索引,CONSTANT_Class_info类型。记录定义该方法的接口。
u2name_and_type_indexconstant_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:)。

typedescriptorremark
u1tagCONSTANT_NameAndType (12)
u2name_indexconstant_pool中的索引,CONSTANT_Utf8_info类型。指定字段或方法的名称。
u2descriptor_indexconstant_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 文件结构-访问标志

typedescriptor[number]remark
u2access_flags[1]

2.3.1 访问标志概述

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

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

标志名称标志值含义代码中表示
ACC_PUBLIC0x00 01是否为Public类型public
ACC_FINAL0x00 10是否被声明为final,只有类可以设置final
ACC_SUPER0x00 20是否允许使用invokespecial字节码指令的新语义.extends
ACC_INTERFACE0x02 00标志这是一个接口interface
ACC_ABSTRACT0x04 00是否为abstract类型,对于接口或者抽象类来说,次标志值为真,其他类型为假abstract
ACC_SYNTHETIC0x10 00标志这个类并非由用户代码产生所有类型
ACC_ANNOTATION0x20 00标志这是一个注解annotation
ACC_ENUM0x40 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

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

HelloWorldInterface.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)

typedescriptor[number]remark
u2this_class[1]
u2super_class[1]
u2interfaces_count
u2interfaces[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

二进制查看如下:

HelloExtendImp-Class-Binary.png

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)表集合

typedescriptor[number]remark
u2fields_count[1]
field_infofields[fields_count]

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

2.5.1 概述

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

2.5.1.1 字段(变量)表的结构

field_info:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count
2.5.1.2 字段访问标志

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

标志名称标志值含义
ACC_PUBLIC0x00 01字段是否为public
ACC_PRIVATE0x00 02字段是否为private
ACC_PROTECTED0x00 04字段是否为protected
ACC_STATIC0x00 08字段是否为static
ACC_FINAL0x00 10字段是否为final
ACC_VOLATILE0x00 40字段是否为volatile
ACC_TRANSTENT0x00 80字段是否为transient
ACC_SYNCHETIC0x10 00字段是否为由编译器自动产生
ACC_ENUM0x40 00字段是否为enum

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

二进制查看如下:

HelloWorldField-Class-Binary.png

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 文件结构-方法表集合

typedescriptor[number]remark
u2methods_count[1]
method_infomethods[methods_count]

2.6.1 概述

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

2.6.1.1 方法表的结构

field_info:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count
2.6.1.2 方法访问标志

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

标志名称标志值含义
ACC_PUBLIC0x00 01方法是否为public
ACC_PRIVATE0x00 02方法是否为private
ACC_PROTECTED0x00 04方法是否为protected
ACC_STATIC0x00 08方法是否为static
ACC_FINAL0x00 10方法是否为final
ACC_SYHCHRONRIZED0x00 20方法是否为synchronized
ACC_BRIDGE0x00 40方法是否是有编译器产生的方法
ACC_VARARGS0x00 80方法是否接受参数
ACC_NATIVE0x01 00方法是否为native
ACC_ABSTRACT0x04 00方法是否为abstract
ACC_STRICTFP0x08 00方法是否为strictfp
ACC_SYNTHETIC0x10 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

HelloWorldMethod-Class-Binary.png

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 文件结构-属性表集合

typedescriptor[number]remark
u2attributes_count[1]
attribute_infoattributes[attributes_count]

2.7.1 概述

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

2.7.1.1 属性表定义的结构

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

类型名称数量
u2attribute_name_index1
u2attribute_length1
u1infoattribute_length
2.7.1.2 虚拟机中预定义的属性
属性名称使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表final关键字定义的常量池
Deprecated类,方法,字段表被声明为deprecated的方法和字段
Exceptions方法表方法抛出的异常
EnclosingMethod类文件仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass类文件内部类列表
LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部便狼描述
StackMapTableCode属性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属性来说并不是必须参在的
结构:

类型名称数量备注
u2attribute_name_index1属性名称
u4attribute_length1属性长度
u2max_stack1最大栈深度
u2max_locals1局部变量表所需空间
u4code_length1
u1codecode_length
u2exception_table_length1
exception_infoexception_tableexception_length
u2attributes_count1
attribute_infoattributesattributes_count
2.7.1.4 Exceptions属性详解

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

类型名称数量
u2attribute_name_index1
u2attribute_lrngth1
u2attribute_of_exception1
u2exception_index_tsblenumber_of_exceptions

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

2.7.1.5 LineNumberTable属性详解
类型名称数量
u2attribute_name_index1
u4attribute_length1
u2line_number_table_length1
line_number_infoline_number_tableline_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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值