前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >类加载阶段之准备阶段

类加载阶段之准备阶段

作者头像
吴就业
发布2020-07-10 11:26:39
7210
发布2020-07-10 11:26:39
举报
文章被收录于专栏:Java艺术Java艺术

准备阶段是为类中定义的静态变量分配内存并设置初始化值的阶段,这里的初始值通常情况下指的是对应类型的零值,比如int类型的零值为0。而给静态字段赋值通常是在编译器生成的类初始化方法<clinit>方法中完成的。

代码语言:javascript
复制
public class ClassLoaderTest {
    static inttestIntStaticField = 123;
    static {
        System.out.println("my name is ClassLoaderTest!");
    }
}

ClassLoaderTest的静态字段testIntStaticFieldint类型,编译后可以在<clinit>方法中找到赋值语句。编译后的<clinit>方法代码如下图所示。

从图中可以看出,静态字段testIntStaticField的赋值是在初始化阶段调用类的<clinit>方法才开始赋值的,而在准备阶段只是赋予零值。

那么在hotspot源码中,准备阶段是什么时候开始的呢,我们回到前面分析的ClassFileParserparseClassFile方法,在字节码流解析生成存储到方法区的InstanceKlass实例并完成一些如验证类是否重写final方法等验证之后,会调用java_lang_Class::create_mirror方法,代码如下。

代码语言:javascript
复制
// 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方法的部分代码如下。

代码语言:javascript
复制
// 初始化镜像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);
}

在调用InstanceKlassdo_local_static_fields方法时,传递了一个方法指针initialize_static_field,该方法会被回调执行,因此我们直接看initialize_static_field方法的实现,代码如下。

代码语言:javascript
复制
// 初始化静态字段
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属性。但是在本例中ClassLoaderTesttestIntStaticField并没有这个属性,因此不会为testIntStaticField字段赋值为初始值。

vm/classfile/javaClasses.cpp文件中,在initialize_static_field方法添加如下日记打印。

代码语言:javascript
复制
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,测试代码如下。

代码语言:javascript
复制
Class<?> classLoaderTestClass = Class.forName(
             "com.wujiuye.asmbytecode.book.fourth.ClassLoaderTest");

重新编译openjdk后,使用编译后的jdkjava命令运行测试代码,程序输出日记如下。

代码语言:javascript
复制
java_lang_Class static initialize_mirror_fields  I,testIntStaticField, 0

现在我们将ClassLoaderTesttestIntStaticField字段改为静态常量,代码如下。

代码语言:javascript
复制
public class ClassLoaderTest {
    final static int testIntStaticField = 123;
    static {
        System.out.println("my name is ClassLoaderTest!");
    }
}

将静态字段testIntStaticField改为常量后,日记打印输出如下。

代码语言:javascript
复制
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属性保存的初始值。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-04-19,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Java艺术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
对象存储
对象存储(Cloud Object Storage,COS)是由腾讯云推出的无目录层次结构、无数据格式限制,可容纳海量数据且支持 HTTP/HTTPS 协议访问的分布式存储服务。腾讯云 COS 的存储桶空间无容量上限,无需分区管理,适用于 CDN 数据分发、数据万象处理或大数据计算与分析的数据湖等多种场景。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档