
Java进阶高手 专栏收录该内容,点击查看专栏更多内容原创 吴就业 323 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`设计中最糟糕的决定,我希望在某种程度上,我们可以放弃多个数据库的支持,但我认为可能已经太晚了,因为有很多人在工作中使用这个特性。
订阅
订阅新文章发布通知吧,不错过精彩内容!
输入邮箱,提交后我们会给您发送一封邮件,您需点击邮件中的链接完成订阅设置。