JVM八:class文件结构(2)

下面我们接着为访问标志,类索引,父类索引,接口索引集合,字段集合,方法表集合

 访问标志:

常量池结束后紧接着的两个字节代表访问标志,用来标识一些类或接口的访问信息,包括:这个Class是类还是接口;是否定义为public;是否定义为abstract;如果是类的话,是否被声明为final等。具体的标志位以及含义如下表:

标志名称

标志值

含义

ACC_PUBLIC

0x0001

是否是public

ACC_FINAL

0x0010

是否被声明为final,只有类可以设置

ACC_SUPER

0x0020

是否允许使用invokespecial字节码指令的新语义,JDK1.0.2之后编译出来的类的这个标志默认为真

ACC_INTERFACE

0x0200

标识是一个接口

ACC_ABSTRACT

0x0400

是否是abstract,对于接口和抽象类来说为真,其他类都为假

ACC_SYNITHETIC

0x1000

标识这个类并非由用户代码产生

ACC_ANNOTATION

0x2000

标识这是一个注解

ACC_ENUM

0x4000

标识这是一个枚举类

类索引(2个字节)、父类索引(2个字节)与接口索引(2个字节接口数+接口)集合:

在访问标志access_flags后接下来就是类索引(this_class)和父类索引(super_class),这两个数据都是u2类型的,而接下来的接口索引集合是一个u2类型的集合,class文件由这三个数据项来确定类的继承关系。由于Java中是单继承,所以父类索引只有一个;但Java类可以实现多个接口,所以接口索引是一个集合。

类索引用来确定这个类的全限定名,这个全限定名就是说一个类的类名包含所有的包名,然后使用"/"代替"."。比如Object的全限定名是java.lang.Object。父类索引确定这个类的父类的全限定名,除了Object之外,所有的类都有父类,所以除了Object之外所有类的父类索引都不为0.接口索引集合存储了implements语句后面按照从左到右的顺序的接口

类索引和父类索引都是一个索引,这个索引指向常量池中的CONSTANT_Class_info类型的常量。然后再CONSTANT_Class_info常量中的索引就可以找到常量池中类型为CONSTANT_Utf8_info的常量,而这个常量保存着类的全限定名。

字段表集合:

      字段表用来描述接口或类中声明的变量。字段包括类级变量和实例级变量,但不包括方法内变量。所谓的类级变量就是静态变量,这个变量不属于这个类的任何实例,可以不用定义类实例就可以使用;实例级变量不是静态变量,是和类实例相关联的,需要定义类实例才能使用。

那么,声明一个变量需要哪些信息呢?有:字段的作用域(public、private和protected修饰符)、是实例变量还是类变量(static修饰符)、可变性(final修饰符)、并发可见性(volatile修饰符)、是否可被序列化(transient修饰符)、字段的数据类型(基本类型、对象、数组)以及字段名称。包含的信息有点多,不过不需要的可以不写。这些信息中,各个修饰符可以用布尔值表示。而字段叫什么名字、字段被定义为什么类型数据都是无法固定的,只能用常量池中的常量来表示。下面是字段表的格式:

类型

名称

数量

U2

access_flags

1

U2

name_index

1

U2

descriptor_index

1

U2

attributes_count

1

attribute_info

attributes

attributes_count

其中的字段修饰符access_flags,和类中的access_flags类似,对于字段来说可以设置的标志位及含义如下:

标志名称

标志值

含义

ACC_PUBLIC

0x0001

字段是否是public

ACC_PRIVATE

0x0002

字段是否是private

ACC_PROTECTED

0x0004

字段是否是protected

ACC_STATIC

0x0008

字段是否是static

ACC_FINAL

0x0010

字段是否是final

ACC_VOLATILE

0x0040

字段是否是volatile

ACC_TRANSIENT

0x0080

字段是否是transient

ACC_SYNTHETIC

0x1000

字段是否是由编译器自动产生的

ACC_ENUM

0x4000

字段是否是enum

显然,ACC_PUBLIC、ACC_PRIVATE和ACC_PROTECTED只能选择一个,ACC_FINAL和ACC_VOLATILE不能同时选择。接口中的字段必须有ACC_PUBLIC、ACC_STATIC和ACC_FINAL标志,这是Java语言本身的规则决定的。

access_flags给出了字段中所有可以用布尔值表示的修饰符,剩下的信息就是字段的名字、变量类型等信息。access_flags后面的是name_index和descriptor_index,前者是字段名的常量池索引,后者是字段描述符的常量池索引。name_index可以描述字段的名字,descriptor_index可以描述字段的数据类型。不过,对于方法的描述符来说就要复杂一些,因为一个方法除了返回值类型,还有参数类型,而且参数的个数还不确定。根据描述符规则,这些类型都使用一个大写字母来表示,如下表:

标识字符

含义

标识字符

含义

B

byte

J

long

C

char

S

short

D

double

Z

boolean

F

float

V

void

I

int

L

对象类型,如Ljava/lang/Object

对于数组类型,每一个维度将使用一个前置的“[”字符来描述。比如定义一个java.lang.String[][]类型的二维数组,将记录为"[[Ljava/lang/String",一个double数组"double[]"将标记为"[D"。

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

descriptor_info后面是属性信息,这会在后面属性表集合中介绍。

方法表集合:

   在字段表集合中介绍了字段的描述符和方法的描述符,对于理解方法表有很大帮助。class文件存储格式中对方法的描述和对字段的描述几乎相同,方法表的结构也和字段表相同,这里就不再列出。不过,方法表的访问标志和字段的不同,列出如下:

标识名称

标志值

含义

ACC_PUBLIC

0x0001

方法是否是public

ACC_PRIVATE

0x0002

方法是否是private

ACC_PUBLICPROTECTED

0x0004

方法是否是protected

ACC_STATIC

0x0008

方法是否是static

ACC_FINAL

0x0010

方法是否是final

ACC_SYNCHRONIZED

0x0020

方法是否是synchronized

ACC_BRIDGE

0x0040

方法是否是由编译器产生的桥接方法

ACC_VARARGS

0x0080

方法是否接受不定参数

ACC_NATIVE

0x0100

方法是否是native

ACC_ABSTRACT

0x0400

方法是否是abstract

ACC_STRICTFP

0x0800

方法是否是strictfp

ACC_SYNTHETIC

0x1000

方法是否是由编译器自动产生的

下面我们来一个例子

JavaCode

public class Test{
	
	public int a;
	private String b;
	private int add(int arg1,int arg2){
		return arg1+arg2; 
	}
	
} 

二机制解析

Javap解析

C:\Users\GH\Desktop>javap -verbose Test.class
Classfile /C:/Users/GH/Desktop/Test.class
  Last modified 2018-8-8; size 287 bytes
  MD5 checksum 430dad91948d95b5532953a32356174c
  Compiled from "Test.java"
public class Test
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #3.#16         // java/lang/Object."<init>":()V
   #2 = Class              #17            // Test
   #3 = Class              #18            // java/lang/Object
   #4 = Utf8               a
   #5 = Utf8               I
   #6 = Utf8               b
   #7 = Utf8               Ljava/lang/String;
   #8 = Utf8               <init>
   #9 = Utf8               ()V
  #10 = Utf8               Code
  #11 = Utf8               LineNumberTable
  #12 = Utf8               add
  #13 = Utf8               (II)I
  #14 = Utf8               SourceFile
  #15 = Utf8               Test.java
  #16 = NameAndType        #8:#9          // "<init>":()V
  #17 = Utf8               Test
  #18 = Utf8               java/lang/Object
{
  public int a;
    descriptor: I
    flags: ACC_PUBLIC

  public Test();
    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
}
SourceFile: "Test.java"

C:\Users\GH\Desktop>

猜你喜欢

转载自blog.csdn.net/weixin_40234548/article/details/81508934