jvm(4) 类的连接与初始化源码

vm初始化后,通过启动类加载器解析出了class文件,并且在堆中分配了java class对象的空间,这仅仅完成了加载,后续还有连接和初始化,才能在runtime使用

参考文档
https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-5.html#jvms-5.3

类被初始化可以有多种方式,主要是这三种

1.Class.forName

最后调用到forName0这个native方法,默认参数initialize为true,会调用clinit
clsss.c
Java_java_lang_Class_forName0
JVM_FindClassFromCaller
jvm.cpp
JVM_FindClassFromCaller
find_class_from_class_loader
SystemDictionary::resolve_or_fail

jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init,
                                    Handle loader, Handle protection_domain,
                                    jboolean throwError, TRAPS) {
  //类加载已经分析过,这里只会解析出class文件对应的对象并分配好元空间,类型为class InstanceKlass: public Klass
  Klass* klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL);

  // Check if we should initialize the class
  //判断class.forName传入的是否初始化参数&&已经初始化,调用InstanceKlass的initialize方法
  if (init && klass->is_instance_klass()) {
    klass->initialize(CHECK_NULL);
  }
  return (jclass) JNIHandles::make_local(THREAD, klass->java_mirror());
}

2.new关键字

new创建对象如果未被加载过则会触发初始化
代码中的new字段通过Bytecodes映射可以找到187这个代码
具体new这类关键字怎么实现的再起一篇

3.Java_java_lang_ClassLoader_defineClass0

通过Classloader加载class文件的byte数组,最后根据是否需要初始化调用initialize或者link方法
这个口子是给从非jar包方式加载class,比如drools规则生成器,翻译了脚本后生成了代码类,通过define加载运行时的这些类
源码在jvm.cpp中的
jvm_lookup_define_class最后会调用到初始化的方法

  if (init) {
    ik->initialize(CHECK_NULL);
  } else {
    ik->link_class(CHECK_NULL);
  }

连接与初始化过程

要注意连接之前ClassFileParser解析的时候已经校验过了,把class中用到的类,父类,接口等都加载了
由于加了锁,所以类加载能够保证static代码块这种只会被加载一次

void InstanceKlass::initialize_impl(TRAPS) {
  HandleMark hm(THREAD);

  // Make sure klass is linked (verified) before initialization
  // A class could already be verified, since it has been reflected upon.
  /**
   * 连接过程大概就是
     1.连接父类
     2.连接当前类所实现的接口
     3.verify_code 校验代码异常相关的信息
     4.rewrite_class  暂时没看太明白,感觉是不同虚拟机实现的指令不一样,对class文件中的指令要重写成自己的(字面上还感觉是overriding这种的重写)
     5.link方法 ,方法重写了就需要重新关联
     jvm specs中最后提到连接会创建数据结构,也可能会oom
   */
  link_class(CHECK);

  DTRACE_CLASSINIT_PROBE(required, -1);

  bool wait = false;

  JavaThread* jt = THREAD;

  // refer to the JVM book page 47 for description of steps
  //上面连接里jvm规范的 5.5. Initialization在描述这些step
  // Step 1
  {
    //对当前线程初始化加锁
    Handle h_init_lock(THREAD, init_lock());
    ObjectLocker ol(h_init_lock, jt);

    // Step 2
    // If we were to use wait() instead of waitInterruptibly() then
    // we might end up throwing IE from link/symbol resolution sites
    // that aren't expected to throw.  This would wreak havoc.  See 6320309.
    while (is_being_initialized() && !is_reentrant_initialization(jt)) {
      wait = true;
      jt->set_class_to_be_initialized(this);
      ol.wait_uninterruptibly(jt);
      jt->set_class_to_be_initialized(NULL);
    }

    // Step 3
    //如果锁进到这里一定是当前线程递归的
    if (is_being_initialized() && is_reentrant_initialization(jt)) {
      DTRACE_CLASSINIT_PROBE_WAIT(recursive, -1, wait);
      return;
    }

    // Step 4
    if (is_initialized()) {
      DTRACE_CLASSINIT_PROBE_WAIT(concurrent, -1, wait);
      return;
    }

    // Step 5
    //初始化出现了错误,不可能成功,直接报错
    if (is_in_error_state()) {
      DTRACE_CLASSINIT_PROBE_WAIT(erroneous, -1, wait);
      ResourceMark rm(THREAD);
      const char* desc = "Could not initialize class ";
      const char* className = external_name();
      size_t msglen = strlen(desc) + strlen(className) + 1;
      char* message = NEW_RESOURCE_ARRAY(char, msglen);
      if (NULL == message) {
        // Out of memory: can't create detailed error message
          THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), className);
      } else {
        jio_snprintf(message, msglen, "%s%s", desc, className);
          THROW_MSG(vmSymbols::java_lang_NoClassDefFoundError(), message);
      }
    }

    // Step 6
    //当前线程能够继续执行,解锁(实际代码没看到解锁)
    set_init_state(being_initialized);
    set_init_thread(jt);
  }

  // Step 7
  // Next, if C is a class rather than an interface, initialize it's super class and super
  // interfaces.
  // 父类触发初始化,失败就整体抛错
  if (!is_interface()) {
    Klass* super_klass = super();
    if (super_klass != NULL && super_klass->should_be_initialized()) {
      //递归调用父类初始化
      super_klass->initialize(THREAD);
    }
    // If C implements any interface that declares a non-static, concrete method,
    // the initialization of C triggers initialization of its super interfaces.
    // Only need to recurse if has_nonstatic_concrete_methods which includes declaring and
    // having a superinterface that declares, non-static, concrete methods
    if (!HAS_PENDING_EXCEPTION && has_nonstatic_concrete_methods()) {
      initialize_super_interfaces(THREAD);
    }

    // If any exceptions, complete abruptly, throwing the same exception as above.
    if (HAS_PENDING_EXCEPTION) {
      Handle e(THREAD, PENDING_EXCEPTION);
      CLEAR_PENDING_EXCEPTION;
      {
        EXCEPTION_MARK;
        // Locks object, set state, and notify all waiting threads
        set_initialization_state_and_notify(initialization_error, THREAD);
        CLEAR_PENDING_EXCEPTION;
      }
      DTRACE_CLASSINIT_PROBE_WAIT(super__failed, -1, wait);
      THROW_OOP(e());
    }
  }

  //规范上step 8说的是断言判断,这里直接就是第九步
  // Step 8
  {
    DTRACE_CLASSINIT_PROBE_WAIT(clinit, -1, wait);
    if (class_initializer() != NULL) {
      // Timer includes any side effects of class initialization (resolution,
      // etc), but not recursive entry into call_class_initializer().
      PerfClassTraceTime timer(ClassLoader::perf_class_init_time(),
                               ClassLoader::perf_class_init_selftime(),
                               ClassLoader::perf_classes_inited(),
                               jt->get_thread_stat()->perf_recursion_counts_addr(),
                               jt->get_thread_stat()->perf_timers_addr(),
                               PerfClassTraceTime::CLASS_CLINIT);
      //调用clinit
      call_class_initializer(THREAD);
    } else {
      // The elapsed time is so small it's not worth counting.
      if (UsePerfData) {
        ClassLoader::perf_classes_inited()->inc();
      }
      call_class_initializer(THREAD);
    }
  }

  // Step 9
  //通知类初始化完成
  if (!HAS_PENDING_EXCEPTION) {
    set_initialization_state_and_notify(fully_initialized, CHECK);
    {
      debug_only(vtable().verify(tty, true);)
    }
  }
  else {
    // Step 10 and 11
    Handle e(THREAD, PENDING_EXCEPTION);
    CLEAR_PENDING_EXCEPTION;
    // JVMTI has already reported the pending exception
    // JVMTI internal flag reset is needed in order to report ExceptionInInitializerError
    JvmtiExport::clear_detected_exception(jt);
    {
      EXCEPTION_MARK;
      set_initialization_state_and_notify(initialization_error, THREAD);
      CLEAR_PENDING_EXCEPTION;   // ignore any exception thrown, class initialization error is thrown below
      // JVMTI has already reported the pending exception
      // JVMTI internal flag reset is needed in order to report ExceptionInInitializerError
      JvmtiExport::clear_detected_exception(jt);
    }
    DTRACE_CLASSINIT_PROBE_WAIT(error, -1, wait);
    if (e->is_a(vmClasses::Error_klass())) {
      THROW_OOP(e());
    } else {
      JavaCallArguments args(e);
      THROW_ARG(vmSymbols::java_lang_ExceptionInInitializerError(),
                vmSymbols::throwable_void_signature(),
                &args);
    }
  }
  DTRACE_CLASSINIT_PROBE_WAIT(end, -1, wait);
}

call_class_initializer获取的就是clinit方法(这个方法在javac编译class的时候都会添加),通过javaCall调用

void InstanceKlass::call_class_initializer(TRAPS) {
  if (ReplayCompiles &&
      (ReplaySuppressInitializers == 1 ||
       (ReplaySuppressInitializers >= 2 && class_loader() != NULL))) {
    // Hide the existence of the initializer for the purpose of replaying the compile
    return;
  }

  methodHandle h_method(THREAD, class_initializer());
  assert(!is_initialized(), "we cannot initialize twice");
  LogTarget(Info, class, init) lt;
  if (lt.is_enabled()) {
    ResourceMark rm(THREAD);
    LogStream ls(lt);
    ls.print("%d Initializing ", call_class_initializer_counter++);
    name()->print_value_on(&ls);
    ls.print_cr("%s (" INTPTR_FORMAT ")", h_method() == NULL ? "(no method)" : "", p2i(this));
  }
  if (h_method() != NULL) {
    JavaCallArguments args; // No arguments
    JavaValue result(T_VOID);
    JavaCalls::call(&result, h_method, &args, CHECK); // Static call (no args)
  }
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容