JVM之Rewriter-字节码重写

178 阅读3分钟

JMV对类的链接时,会对字节码进行重写,主要作用是实现常量池缓存,以及对方法的字节码重写,提高JVM字节码执行的性能。

bool InstanceKlass::link_class_impl(TRAPS) {
  // 判断是否已经链接
  if (is_linked()) {
    return true;
  }
  JavaThread* jt = THREAD;
  //首先链接所有的父类、
  Klass* super_klass = super();
  if (super_klass != nullptr) {
    if (super_klass->is_interface()) {  
      ResourceMark rm(THREAD);
      Exceptions::fthrow(
        THREAD_AND_LOCATION,
        vmSymbols::java_lang_IncompatibleClassChangeError(),
        "class %s has interface %s as super class",
        external_name(),
        super_klass->external_name()
      );
      return false;
    }
    InstanceKlass* ik_super = InstanceKlass::cast(super_klass);
    ik_super->link_class_impl(CHECK_false);
  }
  //然后链接所有的接口
  Array<InstanceKlass*>* interfaces = local_interfaces();
  int num_interfaces = interfaces->length();
  for (int index = 0; index < num_interfaces; index++) {
    InstanceKlass* interk = interfaces->at(index);
    interk->link_class_impl(CHECK_false);
  }
  // 判断是否链接完成
  if (is_linked()) {
    return true;
  }
  // 验证并重写字节码
  {
    LockLinkState init_lock(this, jt);
    // 判断是否重写完成
    if (!is_linked()) {
      if (!is_rewritten()) {
        {
          bool verify_ok = verify_code(THREAD);
          if (!verify_ok) {
            return false;
          }
        }
        // 如果已经链接,直接借宿
        if (is_linked()) {
          return true;
        }
        //重新类的字节码
        rewrite_class(CHECK_false);
      } 
      //链接方法
      link_methods(CHECK_false);
      // 初始化vtable 和 itable  
      bool need_init_table = true;
     if (need_init_table) {                      vtable().initialize_vtable_and_check_constraints(CHECK_false);
        itable().initialize_itable_and_check_constraints(CHECK_false);
      }
      set_initialization_state_and_notify(linked, THREAD);
    }
  }
  return true;
}

重新写类中所有方法的字节码,重写的时机发生在验证字节码之后,但是在第一个方法执行之前。

void InstanceKlass::rewrite_class(TRAPS) {
  Rewriter::rewrite(this, CHECK);
  set_rewritten();
}

重写主要是在常量池添加缓存,并重写方法的字节码指令。

void Rewriter::rewrite(InstanceKlass* klass, TRAPS) {
  ResourceMark rm(THREAD);
  constantPoolHandle cpool(THREAD, klass->constants());
  Rewriter     rw(klass, cpool, klass->methods(), CHECK);
}

Rewriter::Rewriter(InstanceKlass* klass, const constantPoolHandle& cpool, Array<Method*>* methods, TRAPS)
  : _klass(klass),
    _pool(cpool),
    _methods(methods),
    _cp_map(cpool->length()),
    _cp_cache_map(cpool->length() / 2),
    _reference_map(cpool->length()),
    _resolved_references_map(cpool->length() / 2),
    _invokedynamic_references_map(cpool->length() / 2),
    _method_handle_invokers(cpool->length()),
    _invokedynamic_index(0)
{
  // 重写类字节码
  rewrite_bytecodes(CHECK);
  //添加常量池缓存
  make_constant_pool_cache(THREAD);
  // 重写方法中jsr字节码指令
  int len = _methods->length();
  for (int i = len-1; i >= 0; i--) {
    methodHandle m(THREAD, _methods->at(i));
    if (m->has_jsrs()) {
      m = rewrite_jsrs(m, THREAD);
      methods->at_put(i, m());
    }
  }
}

重写字节码逻辑如下 \

  1. 调用compute_index_maps方法计算索引映射。
  2. 重写java.lang.Object类的初始化的字节码指令。
  3. 重写方法的字节码指令。
void Rewriter::rewrite_bytecodes(TRAPS) {
  assert(_pool->cache() == nullptr, "constant pool cache must not be set yet");
    
  compute_index_maps();

  if (RegisterFinalizersAtInit && _klass->name() == vmSymbols::java_lang_Object()) {
    bool did_rewrite = false;
    int i = _methods->length();
    while (i-- > 0) {
      Method* method = _methods->at(i);
      if (method->intrinsic_id() == vmIntrinsics::_Object_init) {
        methodHandle m(THREAD, method);
        rewrite_Object_init(m, CHECK);
        did_rewrite = true;
        break;
      }
    }
    assert(did_rewrite, "must find Object::<init> to rewrite it");
  }
  int len = _methods->length();
  bool invokespecial_error = false;
  for (int i = len-1; i >= 0; i--) {
    Method* method = _methods->at(i);
    scan_method(THREAD, method, false, &invokespecial_error);
    if (invokespecial_error) {
      THROW_MSG(vmSymbols::java_lang_InternalError(),
                "This classfile overflows invokespecial for interfaces and cannot be loaded");
      return;
     }
  }
}

计算常量池和缓存索引映射。

void Rewriter::compute_index_maps() {
  const int length  = _pool->length();
  init_maps(length);
  bool saw_mh_symbol = false;
  for (int i = 0; i < length; i++) {
    int tag = _pool->tag_at(i).value();
    switch (tag) {
      case JVM_CONSTANT_InterfaceMethodref:
      case JVM_CONSTANT_Fieldref          : // fall through
      case JVM_CONSTANT_Methodref         : // fall through
        add_cp_cache_entry(i);
        break;
      case JVM_CONSTANT_Dynamic:
        assert(_pool->has_dynamic_constant(), "constant pool's _has_dynamic_constant flag not set");
        add_resolved_references_entry(i);
        break;
      case JVM_CONSTANT_String            : // fall through
      case JVM_CONSTANT_MethodHandle      : // fall through
      case JVM_CONSTANT_MethodType        : // fall through
        add_resolved_references_entry(i);
        break;
      case JVM_CONSTANT_Utf8:
        if (_pool->symbol_at(i) == vmSymbols::java_lang_invoke_MethodHandle() ||
            _pool->symbol_at(i) == vmSymbols::java_lang_invoke_VarHandle()) {
          saw_mh_symbol = true;
        }
        break;
    }
  }
  if (saw_mh_symbol) {
    _method_handle_invokers.at_grow(length, 0);
  }
}

添加常量池缓存如下 \

  1. 获取constantPool对应InstanceKlass的ClassLoaderData对象
  2. 分配ConstantPoolCache对象。
  3. 设置常量池缓存对象。
  4. 初始化已经解析的引用。
void Rewriter::make_constant_pool_cache(TRAPS) {
  ClassLoaderData* loader_data = _pool->pool_holder()->class_loader_data();
  ConstantPoolCache* cache =
      ConstantPoolCache::allocate(loader_data, _cp_cache_map,
    _invokedynamic_references_map, _initialized_indy_entries, CHECK);
   // 设置常量池缓存对象
  _pool->set_cache(cache);
  cache->set_constant_pool(_pool());
  //初始化已经解析的引用
 _pool->initialize_resolved_references(loader_data, _resolved_references_map, _resolved_reference_limit,THREAD);

总结
本文主要分析了JVM对字节码进行重写,主要作用是实现常量池缓存,以及对方法的字节码重写,提高JVM字节码执行的性能。