class字节码结构(三)(字段集合的结构)

《Java虚拟机原理图解》1.4 class文件中的字段表集合--field字段在class文件中是怎样组织的

字段区:包括了字段计数器和字段数据区:

字段是指在类中定义的静态或者非静态的全局变量,而不是在类中的方法内定义的局部变量。

Field字段应该包含的信息:

field字段的访问标志(access_flags)占有两个字节16位,通过每一位来表示不同的特征

 

字段的数据类型表示:

 

field_info结构体中,存储的是指向了常量池中该字段的名称和字段描述符的索引。

属性表集合:

针对final修饰的字段会有一个ConstantValue属性,将字段的值关联起来,存放的是指向常量池的字段值的索引。

 

特别注意的是:

1,为什么会存在多个ConstantValue(有点奇怪,要么就是上图画错了,应该是字段存在多个属性,而不是多个constantValue)。
2,还有就是有个说法只有static的字段 才有这个属性,但是我测试,只有final修饰的才有这个属性。

我的测试:(只有final有constantValue属性)

代码的全局变量:
int aa=11;
static int aaa=12;
final int b=22;
static final int c=33;

字节码字段区域如下:(javap -v xxx类名xxx打开 显示的效果,不是索引,可能和虚拟机版本也有关系吧)
 int aa;
   descriptor: I
   flags:

 static int aaa;
   descriptor: I
   flags: ACC_STATIC

 final int b;
   descriptor: I
   flags: ACC_FINAL
   ConstantValue: int 22

 static final int c;
   descriptor: I
   flags: ACC_STATIC, ACC_FINAL
   ConstantValue: int 33

关于字段赋值的时机:

public static final int MAX=100;  
public  int count=0;  
a,对于非静态的field字段的赋值将会出现在实例构造方法<init>()中
b,对于静态的field字段,有两个选择:
1、在静态构造方法<cinit>()中进行; 2、使用ConstantValue属性进行赋值

编译器对于静态field字段的初始化赋值策略:

如果final和static同时修饰一个字段,并且这个字段是基本类型或者String类型的,
那么编译器在编译这个字段的时候,会在对应的field_info结构体中增加一个ConstantValue类型的结构体,在赋值的时候使用这个ConstantValue进行赋值;
如果该field字段并没有被final修饰,或者不是基本类型或者String类型,那么将在类构造方法中赋值。 感觉有点问题:测试结果是有final就有ConstantValue属性。

一个例子:

public class Simple {  
    private  transient static final String str ="This is a test";  
}  

总结:对于字段区域是对接口和类的全局字段的描述,将字段各部分关联起来。

 

最后有一个疑问:对于不是final修饰的字段,常量池没有存放它的值,字段数据区域也没有存放。程序运行后怎么得到它的值的。

只能看接下,构造方法是否有存放了。

补充:对于全局变量的值是被编译在构造器中赋值的:

例子:Java字节码浅析(—)

public class SimpleClass {
    public int simpleField = 100;
}

字节码中字段信息:

public int simpleField;
     Signature: I
     flags: ACC_PUBLIC

字节的构造方法中赋值:

 1 public SimpleClass();
 2   Signature: ()V
 3   flags: ACC_PUBLIC
 4   Code:
 5     stack=2, locals=1, args_size=1
 6        0: aload_0
 7        1: invokespecial #1                  // Method java/lang/Object."<init>":()V
 8        4: aload_0
 9        5: bipush        100
10        7: putfield      #2                  // Field simpleField:I
11       10: return
aload_0    
从局部变量数组中加载一个对象引用到操作数栈的栈顶。尽管这段代码看起来没有构造方法,

但是在编译器生成的默认的构造方法里,就会包含这段初始化的代码。第一个局部变量正好是this引用,

于是aload_0把this引用压到操作数栈中。aload_0是aload_指令集中的一条,这组指令会将引用加载到操作数栈中。

n对应的是局部变量数组中的位置,并且也只能是0,1,2,3。还有类似的加载指令,它们加载的并不是对象引用,

比如iload_,lload_,fload_,和dload_, 这里i代表int,l代表long,f代表float,d代表double。

局部变量的在数组中的位置大于3的,得通过iload,lload,fload,dload,和aload进行加载,

这些指令都接受一个操作数,它代表的是要加载的局部变量的在数组中的位置。

invokespecial    
这条指令可以用来调用对象实例的构造方法,私有方法和父类中的方法。

它是方法调用指令集中的一条,其它的还有invokedynamic, invokeinterface, invokespecial, invokestatic, invokevirtual.

这里的invokespecial指令调用的是父类也就是java.lang.Object的构造方法。

bipush    它是用来把一个字节作为整型压到操作数栈中的,在这里100会被压到操作数栈里。
putfield    
它接受一个操作数,这个操作数引用的是运行时常量池里的一个字段,在这里这个字段是simpleField。

赋给这个字段的值,以及包含这个字段的对象引用,在执行这条指令的时候,都 会从操作数栈顶上pop出来。

前面的aload_0指令已经把包含这个字段的对象压到操作数栈上了,而后面的bipush又把100压到栈里。

最后putfield指令会将这两个值从栈顶弹出。执行完的结果就是这个对象的simpleField这个字段的值更新成了100。
View Code

 

posted @ 2018-01-22 23:28  假程序猿  阅读(509)  评论(0编辑  收藏  举报