美文网首页
static final修饰的基础变量(加String类型)的奇

static final修饰的基础变量(加String类型)的奇

作者: 睦月MTK | 来源:发表于2020-04-27 10:43 被阅读0次

声明:我在此只是陈述我看到的现象,并根据该现象去推断发生的理由,所以有些结论只是个人猜测,错误之处请指正


一、问题代码及实验结果
  • 1-1 TestClassOne.java
public class TestClassOne {
    public static final String finalFieldString = "final field String";
    public static final Integer finalFieldInteger = 20;
    public static final int finalFieldInt = 20;
}

名称未大写,注意

  • 1-2 Main.java
public class Main {

    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        TestClassOne to = new TestClassOne();
        Field uf = Unsafe.class.getDeclaredField("theUnsafe");
        uf.setAccessible(true);
        Unsafe U = (Unsafe) uf.get(null);

        Field toFinalFieldString = to.getClass().getDeclaredField("finalFieldString");
        U.putObjectVolatile(U.staticFieldBase(toFinalFieldString) , U.staticFieldOffset(toFinalFieldString) , "final field String modified");
        System.out.println(toFinalFieldString.get(null));
        System.out.println(TestClassOne.finalFieldString);

        System.out.println("***************************分隔线*****************");

        Field toFinalFieldInteger = to.getClass().getDeclaredField("finalFieldInteger");
        U.putObjectVolatile(U.staticFieldBase(toFinalFieldInteger) , U.staticFieldOffset(toFinalFieldInteger) , Integer.valueOf(22));
        System.out.println(toFinalFieldInteger.get(null));
        System.out.println(TestClassOne.finalFieldInteger);

        System.out.println("***************************分隔线*****************");

        Field toFinalFieldInt = to.getClass().getDeclaredField("finalFieldInt");
        U.putIntVolatile(U.staticFieldBase(toFinalFieldInt) , U.staticFieldOffset(toFinalFieldInt) , 22);
        System.out.println(toFinalFieldInt.get(null));
        System.out.println(TestClassOne.finalFieldInt);
    }

}
  • 1-3 输出结果
final field String modified
final field String
***************************分隔线*****************
22
22
***************************分隔线*****************
22
20
  • 1-4 javap -v -p TestClassOne.class(省略无关部分)
Classfile 隐藏/TestClassOne.class
  Last modified 2020-4-27; size 606 bytes
  MD5 checksum 23375c3bbaa1a10b0d97d201a6ac77f3
  Compiled from "TestClassOne.java"
public class cn.mtk.ust.TestClassOne
  minor version: 0
  major version: 55
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #5.#25         // java/lang/Object."<init>":()V
   #2 = Methodref          #26.#27        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
   #3 = Fieldref           #4.#28         // cn/mtk/ust/TestClassOne.finalFieldInteger:Ljava/lang/Integer;
   #4 = Class              #29            // cn/mtk/ust/TestClassOne
   #5 = Class              #30            // java/lang/Object
   #6 = Utf8               finalFieldString
   #7 = Utf8               Ljava/lang/String;
   #8 = Utf8               ConstantValue
   #9 = String             #31            // final field String
  #10 = Utf8               finalFieldInteger
  #11 = Utf8               Ljava/lang/Integer;
  #12 = Utf8               finalFieldInt
  #13 = Utf8               I
  #14 = Integer            20
  #15 = Utf8               <init>
  #16 = Utf8               ()V
  #17 = Utf8               Code
  #18 = Utf8               LineNumberTable
  #19 = Utf8               LocalVariableTable
  #20 = Utf8               this
  #21 = Utf8               Lcn/mtk/ust/TestClassOne;
  #22 = Utf8               <clinit>
  #23 = Utf8               SourceFile
  #24 = Utf8               TestClassOne.java
  #25 = NameAndType        #15:#16        // "<init>":()V
  #26 = Class              #32            // java/lang/Integer
  #27 = NameAndType        #33:#34        // valueOf:(I)Ljava/lang/Integer;
  #28 = NameAndType        #10:#11        // finalFieldInteger:Ljava/lang/Integer;
  #29 = Utf8               cn/mtk/ust/TestClassOne
  #30 = Utf8               java/lang/Object
  #31 = Utf8               final field String
  #32 = Utf8               java/lang/Integer
  #33 = Utf8               valueOf
  #34 = Utf8               (I)Ljava/lang/Integer;
{
  public static final java.lang.String finalFieldString;
    descriptor: Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: String final field String

  public static final java.lang.Integer finalFieldInteger;
    descriptor: Ljava/lang/Integer;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL

  public static final int finalFieldInt;
    descriptor: I
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
    ConstantValue: int 20

  public cn.mtk.ust.TestClassOne();
    //...构造方法描述略

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: bipush        20
         2: invokestatic  #2                  // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
         5: putstatic     #3                  // Field finalFieldInteger:Ljava/lang/Integer;
         8: return
      LineNumberTable:
        line 7: 0
}
SourceFile: "TestClassOne.java"
  • 1-5 javap -v- p Main.class(省略无关部分)
Classfile 隐藏/Main.class
  Last modified 2020-4-27; size 2011 bytes
  MD5 checksum 4af93f788bfb219460e95b2975aede4a
  Compiled from "Main.java"
public class cn.mtk.ust.Main
  minor version: 0
  major version: 55
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   //...常量池略
{
  public cn.mtk.ust.Main();
     //...Main方法描述略

  public static void main(java.lang.String[]) throws java.lang.NoSuchFieldException, java.lang.IllegalAccessException;
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=5, locals=7, args_size=1
        //...略
        71: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
        74: ldc           #17                 // String final field String
        76: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        79: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
        82: ldc           #19                 // String ***************************分隔线*****************
        84: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        //...略
       131: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
       134: getstatic     #22                 // Field cn/mtk/ust/TestClassOne.finalFieldInteger:Ljava/lang/Integer;
       137: invokevirtual #16                 // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
       140: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
       143: ldc           #19                 // String ***************************分隔线*****************
       145: invokevirtual #18                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
        //...略
       189: getstatic     #15                 // Field java/lang/System.out:Ljava/io/PrintStream;
       192: bipush        20
       194: invokevirtual #25                 // Method java/io/PrintStream.println:(I)V
       197: return
      LineNumberTable:
        //...略
      LocalVariableTable:
        //...本地变量表略
    Exceptions:
      throws java.lang.NoSuchFieldException, java.lang.IllegalAccessException
}
SourceFile: "Main.java"

二、问题分析及推论
  • 问题提出

    从1-3输出结果,可以看到,只有static final修饰的Integer变量,在被使用Unsafe直接在内存中修改对应值后,修改的结果反应在了类.静态变量上,其他两个(String类型和基本类型)修改后无效。

  • 猜测:是否是虚拟机对代码进行了优化呢?

    查看1-5 Main的字节码文件,在main方法的描述中,可以看到System.out.println(TestClassOne.finalFieldString);System.out.println(TestClassOne.finalFieldInt);被优化成了直接输出值,而不是和System.out.println(TestClassOne.finalFieldInteger);一样会通过134: getstatic #22 // Field cn/mtk/ust/TestClassOne.finalFieldInteger:Ljava/lang/Integer;去获取对应引用的值。
    查看1-4 TestClassOne的字节码文件,可以看到常量池中类型为FieldRef的符号常量只有一个,那就是finalFieldInteger,而且看static方法块中,也可以发现也就只有finalFieldInteger在进行着操作。也就是说虚拟机把static final修饰的String类型和基础变量类型都不当成“变量”了,直接给写死。

  • 推论,虚拟机虽然不把static final修饰的String类型和基础变量类型都当成“变量”,但是在对应的Class对象中还是会存储这些静态“变量”的值

    在1-2代码中U.staticFieldBase(toFinalFieldString)的返回值实际上是class cn.mtk.ust.TestClassOne,由此可以推得静态变量是存在于对应Class对象中的。
    可以参考:java中的静态变量和Class对象究竟存放在哪个区域?


参考文档:

相关文章

网友评论

      本文标题:static final修饰的基础变量(加String类型)的奇

      本文链接:https://www.haomeiwen.com/subject/bvbewhtx.html