声明:我在此只是陈述我看到的现象,并根据该现象去推断发生的理由,所以有些结论只是个人猜测,错误之处请指正
一、问题代码及实验结果
- 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对象究竟存放在哪个区域?
参考文档:
网友评论