JVM扩展知识点记录(一) - 各种变量的加载与内存中的存储方法

本文主要介绍java种各种变量的加载方式与存储方法
包括:
1、static final修饰的常量;
2、final修饰的成员变量;
3、普通类变量
4、普通成员变量

一、static final修饰的常量,值为基础数据类型、String值或对象
class A{
	static final int i1 = 101;	/*static final String str1 = "abc"; */
	static final String str1 = new String("abc");	/*static final String str1 = "abc"; */
	static final float f1 = 101.0f;
	public static void main(String args[]){
		float f2 = f1+1.0f;
	}
}

/* 查看静态常量池 */
Constant pool:
	   #1 = Methodref          #8.#33         // java/lang/Object."<init>":()V
	   #2 = Class              #34            // A
	   #3 = Float              102.0f
	   #4 = Class              #35            // java/lang/String
	   #5 = String             #36            // abc
	   #6 = Methodref          #4.#37         // java/lang/String."<init>":(Ljava/lang/String;)V
	   #7 = Fieldref           #2.#38         // A.str1:Ljava/lang/String;
	   #8 = Class              #39            // java/lang/Object
	   #9 = Utf8               i1
	  #10 = Utf8               I
	  #11 = Utf8               ConstantValue
	  #12 = Integer            101
	  #13 = Utf8               str1
	  #14 = Utf8               Ljava/lang/String;
	  #15 = Utf8               f1
	  #16 = Utf8               F
	  #17 = Float              101.0f
	  #18 = Utf8               <init>
	  #19 = Utf8               ()V
	  #20 = Utf8               Code
	  #21 = Utf8               LineNumberTable
	  #22 = Utf8               LocalVariableTable
	  #23 = Utf8               this
	  #24 = Utf8               LA;
	  #25 = Utf8               main
	  #26 = Utf8               ([Ljava/lang/String;)V
	  #27 = Utf8               args
	  #28 = Utf8               [Ljava/lang/String;
	  #29 = Utf8               f2
	  #30 = Utf8               <clinit>
	  #31 = Utf8               SourceFile
	  #32 = Utf8               A.java
	  #33 = NameAndType        #18:#19        // "<init>":()V
	  #34 = Utf8               A
	  #35 = Utf8               java/lang/String
	  #36 = Utf8               abc
	  #37 = NameAndType        #18:#40        // "<init>":(Ljava/lang/String;)V
	  #38 = NameAndType        #13:#14        // str1:Ljava/lang/String;
	  #39 = Utf8               java/lang/Object
	  #40 = Utf8               (Ljava/lang/String;)V
  
/* 任何类变量和成员变量都必须在Field表中有对应的field_info */
static final int i1;
    descriptor: I
    flags: ACC_STATIC, ACC_FINAL
    ConstantValue: int 101

static final java.lang.String str1;
    descriptor: Ljava/lang/String;
    flags: ACC_STATIC, ACC_FINAL

static final float f1;
    descriptor: F
    flags: ACC_STATIC, ACC_FINAL
    ConstantValue: float 101.0f
1、final static常量赋值为基础数据类型或String值

final static修饰的变量如果值是基础数据类型或String值,前期编译后就会在字节码文件的字段表中为这个字段添加ConstantValue属性并将值也放在属性表中,可以通过上面javap反编译后看到字段表中所有字段以及有ConstantValue属性和值的字段。

那么常量是在类加载的哪一步被加载到运行时数据区的呢?我们可以倒推出来结果:
(1) 首先明确的就是,基础数据类型常量的值是在常量池中的 (从Constant pool可以很明显的看到),也就是这些基础数据类型的值存放在逻辑方法区内;
(2) 既然是在方法区内,那么一定是 类加载-准备阶段 进行加载的;

注意:准备阶段是对类变量进行加载的阶段,如果是ConstantValue标识则直接在方法区存入值。如果是对象则是在方法区生成引用,引用先指向默认值null,然后在初始化阶段再将引用的值初始化成正确的对象。
观察Javap反编译的结果,可以发现都没有i1、f1对应的CONSTANT_Field_info常量项,也证明了i1和f1并不是作为一个字段引用存在于常量池中,而是直接将值放在静态常量池中(加载后在运行时常量池内),地址映射给变量名,当用到的时候,直接从常量池获取即Float_info常量项即可,而不需要用getstatic Field_info来获取字段常量项(会有专门的分析);

2、final static常量赋值为对象

如果值是new出的对象,那么在前期编译的时候就无法确定对象真实的地址,所以给这种情况的常量赋值则会涉及到两个步骤:
(1) 所以会在类加载的链接 - 解析阶段进行符号引用转化为直接引用的操作;【通用过程,会将常量池中的所有符号引用类型全部替换为直接引用】
(2) 在类加载 - 初始化阶段,将引用str1真正指向一个堆区的对象[ new String(“abc”); ],从javap反编译结果的static{}中的相关指令可以确定我们的这个说法,如下:

  static {};									/* static代码块是在 类加载-初始化 阶段用来给字段赋值的,除了ConstantValue的基础数据类型,其他的类变量都是在static{}中真正初始化值的 */
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=3, locals=0, args_size=0
         0: new           #4                  // class java/lang/String			/*创建一个对象,并将其引用值压入栈顶*/
         3: dup																	/*复制栈顶数值并将复制值压入栈顶*/
         4: ldc           #5                  // String abc		/* 将int, float或String型常量值从常量池中推送至栈顶,#5就是"utf8=abc" */
         6: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V			/* 调用超类构造方法,实例初始化方法,私有方法 */
         9: putstatic     #7                  // Field str1:Ljava/lang/String;	/* 为指定的类的静态域赋值,就是把栈顶new出来的对象赋值给#7字段 */
        12: return								/* 从当前方法返回void */

注意:java并不是说一个方法生成一个栈帧,其实准确来说是一个{ }对应一个栈帧,这也是为什么变量作用域会有很严格的区域的原因。所以static{ }本身也是一个独立栈帧的;

二、static修饰的类便俩个,值为基础数据类型或对象
class A{
	static int i1 = 101;	/*static final String str1 = "abc"; */
	static String str1 = new String("abc");	/*static final String str1 = "abc"; */
	static float f1 = 101.0f;
	public static void main(String args[]){
		float f2 = f1+1.0f;
	}
}

/* 静态常量池 */
Constant pool:
	   #1 = Methodref          #10.#32        // java/lang/Object."<init>":()V
	   #2 = Fieldref           #9.#33         // A.f1:F
	   #3 = Fieldref           #9.#34         // A.i1:I
	   #4 = Class              #35            // java/lang/String
	   #5 = String             #36            // abc
	   #6 = Methodref          #4.#37         // java/lang/String."<init>":(Ljava/lang/String;)V
	   #7 = Fieldref           #9.#38         // A.str1:Ljava/lang/String;
	   #8 = Float              101.0f
	   #9 = Class              #39            // A
	  #10 = Class              #40            // java/lang/Object
	  #11 = Utf8               i1
	  #12 = Utf8               I
	  #13 = Utf8               str1
	  #14 = Utf8               Ljava/lang/String;
	  #15 = Utf8               f1
	  #16 = Utf8               F
	  #17 = Utf8               <init>
	  #18 = Utf8               ()V
	  #19 = Utf8               Code
	  #20 = Utf8               LineNumberTable
	  #21 = Utf8               LocalVariableTable
	  #22 = Utf8               this
	  #23 = Utf8               LA;
	  #24 = Utf8               main
	  #25 = Utf8               ([Ljava/lang/String;)V
	  #26 = Utf8               args
	  #27 = Utf8               [Ljava/lang/String;
	  #28 = Utf8               f2
	  #29 = Utf8               <clinit>
	  #30 = Utf8               SourceFile
	  #31 = Utf8               A.java
	  #32 = NameAndType        #17:#18        // "<init>":()V
	  #33 = NameAndType        #15:#16        // f1:F
	  #34 = NameAndType        #11:#12        // i1:I
	  #35 = Utf8               java/lang/String
	  #36 = Utf8               abc
	  #37 = NameAndType        #17:#41        // "<init>":(Ljava/lang/String;)V
	  #38 = NameAndType        #13:#14        // str1:Ljava/lang/String;
	  #39 = Utf8               A
	  #40 = Utf8               java/lang/Object
	  #41 = Utf8               (Ljava/lang/String;)V

/* 字段表 */
static int i1;
    descriptor: I
    flags: ACC_STATIC

static java.lang.String str1;
    descriptor: Ljava/lang/String;
    flags: ACC_STATIC

static float f1;
    descriptor: F
    flags: ACC_STATIC
    
/* static代码块 */
 static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=3, locals=0, args_size=0
         0: bipush        101
         2: putstatic     #3                  // Field i1:I
         5: new           #4                  // class java/lang/String
         8: dup
         9: ldc           #5                  // String abc
        11: invokespecial #6                  // Method java/lang/String."<init>":(Ljava/lang/String;)V
        14: putstatic     #7                  // Field str1:Ljava/lang/String;
        17: ldc           #8                  // float 101.0f
        19: putstatic     #2                  // Field f1:F
        22: return
1、static修饰类变量赋值为基础数据类型

先对比一下static赋值为基础数据类型和static final赋值基础数据类型的区别:
(1) int类型常量会在常量池中生成对应的Integer常量项,而单独的类变量不会;
(2) static final修饰的常量不需要在静态常量池中声明Field_info的,而类变量则需要有对应的Field_info;
(3) 常量不需要初始化阶段,而类变量需要;

根据上面三点的区别,我们则可以对比和猜测出类变量的加载过程以及存放方式:
1、类变量也是在方法区内,但是不位于

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值