原创 吴就业 115 0 2020-04-19
本文为博主原创文章,未经博主允许不得转载。
本文链接:https://wujiuye.com/article/2e377f84a9404d4986dbda2688111185
作者:吴就业
链接:https://wujiuye.com/article/2e377f84a9404d4986dbda2688111185
来源:吴就业的网络日记
本文为博主原创文章,未经博主允许不得转载。
本篇文章写于2020年04月19日,从公众号|掘金|CSDN手工同步过来(博客搬家),本篇为原创文章。
准备阶段是为类中定义的静态变量分配内存并设置初始化值的阶段,这里的初始值通常情况下指的是对应类型的零值,比如int类型的零值为0。而给静态字段赋值通常是在编译器生成的类初始化方法<clinit>
方法中完成的。
public class ClassLoaderTest {
static inttestIntStaticField = 123;
static {
System.out.println("my name is ClassLoaderTest!");
}
}
ClassLoaderTest
的静态字段testIntStaticField
是int
类型,编译后可以在<clinit>
方法中找到赋值语句。编译后的<clinit>
方法代码如下图所示。
从图中可以看出,静态字段testIntStaticField
的赋值是在初始化阶段调用类的<clinit>
方法才开始赋值的,而在准备阶段只是赋予零值。
那么在hotspot
源码中,准备阶段是什么时候开始的呢,我们回到前面分析的ClassFileParser
的parseClassFile
方法,在字节码流解析生成存储到方法区的InstanceKlass
实例并完成一些如验证类是否重写final
方法等验证之后,会调用java_lang_Class::create_mirror
方法,代码如下。
// Allocate mirror and initialize static fields
java_lang_Class::create_mirror(this_klass, class_loader, protection_domain,
CHECK_(nullHandle));
create_mirror
方法用于创建一个java.lang.Class
对象,这个对象存储在堆中,也称为InstanceKlass
的镜像。从注释中可以看出,调用该方法还会初始化静态字段。create_mirror
方法中会调用initialize_mirror_fields
方法初始化镜像的字段,initialize_mirror_fields
方法的部分代码如下。
// 初始化镜像java.lang.Class的字段
void java_lang_Class::initialize_mirror_fields(KlassHandle k,Handle mirror,
Handle protection_domain,TRAPS) {
......
// Initialize static fields
// 初始化静态字段
InstanceKlass::cast(k())->do_local_static_fields(&initialize_static_field, mirror, CHECK);
}
在调用InstanceKlass
的do_local_static_fields
方法时,传递了一个方法指针initialize_static_field
,该方法会被回调执行,因此我们直接看initialize_static_field
方法的实现,代码如下。
// 初始化静态字段
static void initialize_static_field(fieldDescriptor* fd, Handle mirror, TRAPS) {
assert(mirror.not_null() && fd->is_static(), "just checking");
// 是否有初始值
if (fd->has_initial_value()) {
BasicType t = fd->field_type();
// 根据类型设置零值
switch (t) {
case T_BYTE:
mirror()->byte_field_put(fd->offset(), fd->int_initial_value());
break;
case T_BOOLEAN:
mirror()->bool_field_put(fd->offset(), fd->int_initial_value());
break;
case T_CHAR:
mirror()->char_field_put(fd->offset(), fd->int_initial_value());
break;
case T_SHORT:
mirror()->short_field_put(fd->offset(), fd->int_initial_value());
break;
case T_INT:
mirror()->int_field_put(fd->offset(), fd->int_initial_value());
break;
case T_FLOAT:
mirror()->float_field_put(fd->offset(), fd->float_initial_value());
break;
case T_DOUBLE:
mirror()->double_field_put(fd->offset(), fd->double_initial_value());
break;
case T_LONG:
mirror()->long_field_put(fd->offset(), fd->long_initial_value());
break;
case T_OBJECT:
{
// 如果字段是引用类型,ConstantValue 只支持字符串类型
#ifdef ASSERT
TempNewSymbol sym = SymbolTable::new_symbol("Ljava/lang/String;", CHECK);
assert(fd->signature() == sym, "just checking");
#endif
// 初始化值为:" "
oop string = fd->string_initial_value(CHECK);
mirror()->obj_field_put(fd->offset(), string);
}
break;
default:
// 抛出类文件格式错误,无效常量属性
THROW_MSG(vmSymbols::java_lang_ClassFormatError(),
"Illegal ConstantValue attribute in class file");
}
}
}
initialize_static_field
方法首先会判断这个字段是否有初始值,有初始值才会给该静态字段赋值为初始值。
怎么判断是否有初始化值?就是判断该字段是否有一个ConstantValue_attribute
属性。但是在本例中ClassLoaderTest
的testIntStaticField
并没有这个属性,因此不会为testIntStaticField
字段赋值为初始值。在vm/classfile/javaClasses.cpp
文件中,在initialize_static_field
方法添加如下日记打印。
static void initialize_static_field(fieldDescriptor* fd, Handle mirror, TRAPS) {
assert(mirror.not_null() && fd->is_static(), "just checking");
// 日记打印
const char* plog = "testIntStaticField";
if(!strncmp((const char*)fd->name()->bytes(), plog, strlen(plog))){
printf("java_lang_Class static initialize_mirror_fields %s,%s, %d \n",
fd->signature()->as_utf8(), fd->name()->as_utf8(),fd->has_initial_value());
}
// end 日记打印
if (fd->has_initial_value()) {
......
}
}
编写测试代码加载ClassLoaderTest
,测试代码如下。
Class<?> classLoaderTestClass = Class.forName(
"com.wujiuye.asmbytecode.book.fourth.ClassLoaderTest");
重新编译openjdk
后,使用编译后的jdk
的java
命令运行测试代码,程序输出日记如下。
java_lang_Class static initialize_mirror_fields I,testIntStaticField, 0
现在我们将ClassLoaderTest
的testIntStaticField
字段改为静态常量,代码如下。
public class ClassLoaderTest {
final static int testIntStaticField = 123;
static {
System.out.println("my name is ClassLoaderTest!");
}
}
将静态字段testIntStaticField
改为常量后,日记打印输出如下。
java_lang_Class static initialize_mirror_fields I,testIntStaticField, 1
initialize_static_field
方法日记打印输出的has_initial_value
为1,说明该字段已经存在一个ConstantValue_attribute
属性,我们可以使用classpy
工具查看,如下图所示。
现在再看编译器生成的<clinit>
方法。
<clinit>
方法已经没有为变量赋值的字节码指令了。而此时该字段已经存在一个ConstantValue_attribute
属性,所以在准备阶段就为该字段赋值为初始值123
。
因此我们可以得出结论,如果字段存在ConstantValue_attribute
属性,那么字段将会在类加载的准备阶段被赋值为初始化值,即ConstantValue_attribute
属性保存的初始值。
声明:公众号、CSDN、掘金的曾用名:“Java艺术”,因此您可能看到一些早期的文章的图片有“Java艺术”的水印。
`Redis`多数据库是我在`Redis`设计中最糟糕的决定,我希望在某种程度上,我们可以放弃多个数据库的支持,但我认为可能已经太晚了,因为有很多人在工作中使用这个特性。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。