Class.forName后发生了什么

1,999 阅读7分钟

第一次认识Class.forName是在大学时学习使用jdbc连接数据库,当时不知道这个句话什么意思,只是简单的给背下来。

Java层面

先对Class.forName有一个大概的认识:

方法签名:

    public static Class<?> forName(String className){...}

入参为String类型,返回值为一个Class对象。

    /**
     * Returns the {@code Class} object associated with the class or
     * interface with the given string name.  Invoking this method is
     * equivalent to:
     *
     * <blockquote>
     *  {@code Class.forName(className, true, currentLoader)}
     * </blockquote>
     *
     * where {@code currentLoader} denotes the defining class loader of
     * the current class.
     */

和我们猜的一样,该方法返回的为给定className所关联的Class对象。

那么...

什么是Class对象?

由于Class只有一个私有的构造方法,很明显,我们无法直接创建其实例对象。

java.lang.Class类的注释很长,我们这里只看其中的一段话:

 /**
 * <p> {@code Class} has no public constructor. Instead a {@code Class}
 * object is constructed automatically by the Java Virtual Machine
 * when a class loader invokes one of the
 * {@link ClassLoader#defineClass(String,byte[], int,int) defineClass} methods
 * and passes the bytes of a {@code class} file.
 */

当类加载器中的ClassLoader#defineClass方法被调用时,虚拟机会自动构造对应的Class对象。

类加载过程

简单复习一下一个类的加载过程

根据java虚拟机规范(docs.oracle.com/javase/spec…),一个类加载到内存并供我们使用分为以下三个阶段:

  • Loading 根据一个类的全限定名获取其二进制二进制字节流,并创建java.lang.Class对象。
  • Linking 将类组合为运行时状态,包括为变量赋默认值,解析符号引用,和验证二进制格式等。
  • Initializing 调用初始化方法<clinit>

而对应的ClassLoader#defineClass正式位于类加载的第一个过程,Loading中。

类与对象关系

让我们想象这样一个极简化的Java虚拟机运行时状态:方法区中只加载了两个类,java.lang.Object和java.lang.Class;堆中只通过new指令分配了一个对象Object。

如图所示,在方法区中,class1和class2分别是java.lang.Object和java.lang.Class类的数据。

而堆中object1和object2分别代表java.lang.Object和java.lang.Class的类对象,Object3是单独的Object实例。

其中,每一个实例对象都位于有一个指向方法区中自己class类型的指针。同时,因为反射的需要,每一个方法区中的类数据也维护了一个指向堆中对应Class实例的指针(图中的JClass字段)。

Native层面

Class.forName中的主要逻辑都位于native方法forName0中,我们结合openJdk的源码,看一下其中都做了什么。

以openJdk12为例,对应jni实现位于src/java.base/share/native/libjava/Class.c中。

JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
                              jboolean initialize, jobject loader, jclass caller)

可以看出,forName的主要实现逻辑位于JVM_FindClassFromCaller方法中。

/*
 * Find a class from a given class loader.  Throws ClassNotFoundException.
 *  name:   name of class
 *  init:   whether initialization is done
 *  loader: class loader to look up the class. This may not be the same as the caller's
 *          class loader.
 *  caller: initiating class. The initiating class may be null when a security
 *          manager is not installed.
 */
JNIEXPORT jclass JNICALL
JVM_FindClassFromCaller(JNIEnv *env, const char *name, jboolean init,
                        jobject loader, jclass caller);

其功能由函数名就可以看出:在指定的classLoader中查找对应的class。该方法的实现位于src/hotpot/share/prims/jvm.cpp中。

// Find a class with this name in this loader, using the caller's protection domain.
JVM_ENTRY(jclass, JVM_FindClassFromCaller(JNIEnv* env, const char* name,
                                          jboolean init, jobject loader,
                                          jclass caller))
  JVMWrapper("JVM_FindClassFromCaller throws ClassNotFoundException");
  // Java libraries should ensure that name is never null...
  if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
    // It's impossible to create this class;  the name cannot fit
    // into the constant pool.
    THROW_MSG_0(vmSymbols::java_lang_ClassNotFoundException(), name);
  }

  TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);

  oop loader_oop = JNIHandles::resolve(loader);
  oop from_class = JNIHandles::resolve(caller);
  oop protection_domain = NULL;
  // If loader is null, shouldn't call ClassLoader.checkPackageAccess; otherwise get
  // NPE. Put it in another way, the bootstrap class loader has all permission and
  // thus no checkPackageAccess equivalence in the VM class loader.
  // The caller is also passed as NULL by the java code if there is no security
  // manager to avoid the performance cost of getting the calling class.
  if (from_class != NULL && loader_oop != NULL) {
    protection_domain = java_lang_Class::as_Klass(from_class)->protection_domain();
  }

  Handle h_loader(THREAD, loader_oop);
  Handle h_prot(THREAD, protection_domain);
  jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
                                               h_prot, false, THREAD);

  if (log_is_enabled(Debug, class, resolve) && result != NULL) {
    trace_class_resolution(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(result)));
  }
  return result;
JVM_END

这个函数代码量不算多,我们将按该函数的调用顺序分析其过程:

SymbolTable

该类位于src/hotspot/share/classfile/symbolTable.cpp中,他其实是对哈希表的一个实现。

如果symbolTable中没有对应的数据,则调用do_add_if_needed函数将其加入哈希表。

find_class_from_class_loader

在将当前类加入到SymbolTable中后,调用find_class_from_class_loader函数,在指定的classLoader中查找该类。

jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init,
                                    Handle loader, Handle protection_domain,
                                    jboolean throwError, TRAPS) {
  // Security Note:
  //   The Java level wrapper will perform the necessary security check allowing
  //   us to pass the NULL as the initiating class loader.  The VM is responsible for
  //   the checkPackageAccess relative to the initiating class loader via the
  //   protection_domain. The protection_domain is passed as NULL by the java code
  //   if there is no security manager in 3-arg Class.forName().
  Klass* klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL);

  // Check if we should initialize the class
  if (init && klass->is_instance_klass()) {
    klass->initialize(CHECK_NULL);
  }
  return (jclass) JNIHandles::make_local(env, klass->java_mirror());
}

SystemDictionary::resolve_or_fail

根据类名&类加载器去查找class对象。

在该方法中,直接封装了SystemDictionary::resolve_or_null方法。

SystemDictionary::resolve_or_null

负责转发,分别将数组对象和普通对象转至resolve_array_class_or_null方法和resolve_instance_class_or_null_helper方法。

// Forwards to resolve_array_class_or_null or resolve_instance_class_or_null

Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {
  if (FieldType::is_array(class_name)) {
    return resolve_array_class_or_null(class_name, class_loader, protection_domain, THREAD);
  } else {
    return resolve_instance_class_or_null_helper(class_name, class_loader, protection_domain, THREAD);
  }
}

SystemDictionary::resolve_instance_class_or_null_helper

如果*class_name是L开头,;结尾的字符串的话,需要将其开头,结尾截取掉。

// name may be in the form of "java/lang/Object" or "Ljava/lang/Object;"
InstanceKlass* SystemDictionary::resolve_instance_class_or_null_helper(Symbol* class_name,
                                                                       Handle class_loader,
                                                                       Handle protection_domain,
                                                                       TRAPS) {
  assert(class_name != NULL && !FieldType::is_array(class_name), "must be");
  if (FieldType::is_obj(class_name)) {
    ResourceMark rm(THREAD);
    // Ignore wrapping L and ;.
    TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1,
                                   class_name->utf8_length() - 2, CHECK_NULL);
    return resolve_instance_class_or_null(name, class_loader, protection_domain, THREAD);
  } else {
    return resolve_instance_class_or_null(class_name, class_loader, protection_domain, THREAD);
  }
}

SystemDictionary::resolve_instance_class_or_null

这个方法略长...完整代码我就不在文章里面贴了,这里只分析下关键过程,感兴趣的可以自己看下src/hotspot/share/classfile/systemDictionary.cpp

  • 首先,判断一下该类是否已经加载过(是否已存在对应的class实例对象)。
  // Do lookup to see if class already exist and the protection domain
  // has the right access
  // This call uses find which checks protection domain already matches
  // All subsequent calls use find_class, and set has_loaded_class so that
  // before we return a result we call out to java to check for valid protection domain
  // to allow returning the Klass* and add it to the pd_set if it is valid
  {
    InstanceKlass* probe = dictionary->find(d_hash, name, protection_domain);
    if (probe != NULL) return probe;
  }
  • 将要加载的类名hash加入placeholders,防止重复加载。
  unsigned int p_hash = placeholders()->compute_hash(name);
  int p_index = placeholders()->hash_to_index(p_hash);
  • 获取对象锁。
  // Class is not in SystemDictionary so we have to do loading.
  // Make sure we are synchronized on the class loader before we proceed
  Handle lockObject = compute_loader_lock_object(class_loader, THREAD);
  check_loader_lock_contention(lockObject, THREAD);
  ObjectLocker ol(lockObject, THREAD, DoObjectLock);
  • 再次确认类是否已加载,或正在被加载中(通过placeholder)。
{
    MutexLocker mu(SystemDictionary_lock, THREAD);
    InstanceKlass* check = find_class(d_hash, name, dictionary);
    if (check != NULL) {
      // InstanceKlass is already loaded, so just return it
      class_has_been_loaded = true;
      k = check;
    } else {
      placeholder = placeholders()->get_entry(p_index, p_hash, name, loader_data);
      if (placeholder && placeholder->super_load_in_progress()) {
         super_load_in_progress = true;
         if (placeholder->havesupername() == true) {
           superclassname = placeholder->supername();
           havesupername = true;
         }
      }
    }
  }
  • 真正的加载类。
    k = load_instance_class(name, class_loader, THREAD);
  • 加载完成后移出placeholder
    if (load_instance_added == true) {
      // clean up placeholder entries for LOAD_INSTANCE success or error
      // This brackets the SystemDictionary updates for both defining
      // and initiating loaders
      MutexLocker mu(SystemDictionary_lock, THREAD);
      placeholders()->find_and_remove(p_index, p_hash, name, loader_data, PlaceholderTable::LOAD_INSTANCE, THREAD);
      SystemDictionary_lock->notify_all();
    }
  • 发布event通知
  if (class_load_start_event.should_commit()) {
    post_class_load_event(&class_load_start_event, k, loader_data);
  }

SystemDictionary::load_instance_class

该方法内有两个逻辑分支,如下:

  if (class_loader.is_null()) {
  //...
  }else {
  // Use user specified class loader to load class. Call loadClass operation on class_loader.
  //...
  }
  
  • 传入的class_loader为空

    被加载的类为系统类,使用启动类加载器去加载:

// Called by the boot classloader to load classes
InstanceKlass* ClassLoader::load_class(Symbol* name, bool search_append_only, TRAPS) {
}

最后,通过读取到的字节流去创建InstanceKlass

InstanceKlass* KlassFactory::create_from_stream(ClassFileStream* stream,
                                                Symbol* name,
                                                ClassLoaderData* loader_data,
                                                Handle protection_domain,
                                                const InstanceKlass* unsafe_anonymous_host,
                                                GrowableArray<Handle>* cp_patches,
                                                TRAPS) {
  • 传入的class_loader不为空 使用用户定义的类加载器去加载。(在jvm层面,类加载器只分为boot classLoader和user defined classLoader。平常说的extClassLoader也归到了user defined classLoader里面)。
    // Call public unsynchronized loadClass(String) directly for all class loaders.
    // For parallelCapable class loaders, JDK >=7, loadClass(String, boolean) will
    // acquire a class-name based lock rather than the class loader object lock.
    // JDK < 7 already acquire the class loader lock in loadClass(String, boolean).
    JavaCalls::call_virtual(&result,
                            class_loader,
                            spec_klass,
                            vmSymbols::loadClass_name(),
                            vmSymbols::string_class_signature(),
                            string,
                            CHECK_NULL);

这里,其实就是调用的java中的ClassLoader.loadClass()方法。

总结

至此,我们已经看完了Class.forName中的主要流程。

这里只是很初略的跟了一下调用过程,其中涉及的权限检查,数组类加载,异常处理等都直接略过了。

我们先回顾一下最后的调用栈:

其实,我们可以认为,当jvm中尚未加载一个类的时候,Class.forName可以促使JVM去去装载指定类。这也就是为什么在jdbc连接数据库的时候,有时不使用Class.forName也一样可以成功。

参考资料

  openJdk12源码

《自己动手写java虚拟机》

《深入理解java虚拟机》