专注虚拟机与编译器研究

第3.5篇-触发类的装载

首先来介绍一下类加载的时机,下面5种情况会导致类初始化,所以必然在此之前对类进行加载,如下:

  • 当虚拟机启动时加载主类,之前已经详细介绍过主类的加载过程;

  • 使用java.lang.reflect包的方法对类进行反射调用的时候,如果类还没有初始化过,则需要进行初始化。

  • new一个类的对象,调用类的静态成员(除了由final修饰的常量外)和静态方法,无论是在解析执行还是编译执行的情况下,都会在处理newgetstaticputstatic invokestatic字节码指令时对类进行初始化,在第9章会介绍使用new字节码指令创建对象的过程,其中就会有触发类加载的逻辑判断;

  • 当初始化一个类,如果其父类没有被初始化,则先初始化其父类,后续在介绍函数InstanceKlass::initialize_impl()时会看到这个判断逻辑;

  • 使用JDK7的动态语言支持时,如果一个java.lang.invoke.MethodHandle对象最后的解析结果REF_ getStaticREF_ putStaticREF_ invokeStatic的方法句柄,并且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化。我们暂不讨论动态语言相关知识点,所以不在过多介绍。

可以通过调用ClassLoader类的loadClass()方法加载类,还可以通过调用java.lang.Class.forName()方法通过反射的方式完成加载类,但是loadClass()方法只是将Class文件加载到HotSpot VM中,而forName()方法会完成类的加载、链接和初始化过程。

forName()方法的实现如下: 

源代码位置:openjdk/jdk/src/share/classes/java/lang/Class.java

public static Class<?> forName(String className) throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        // 第2个参数的值为true,表示要对类进行初始化 
        return forName0(className, true, ClassLoader.getClassLoader(caller), caller);
}

调用的forName0()是一个本地静态方法,HotSpot VM提供了这个方法的本地接口实现,如下:

源代码位置:openjdk/jdk/src/share/native/java/lang/Class.c

JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
                              jboolean initialize, jobject loader)
{
    char *clname;
    jclass cls = 0;
  
    cls = JVM_FindClassFromClassLoader(env, clname, initialize, loader, JNI_FALSE);
  
    return cls;
}

调用JVM_FindClassFromClassLoader()函数实现如下:

源代码位置:openjdk/hotspot/src/share/vm/prims/jvm.cpp

JVM_ENTRY(jclass, JVM_FindClassFromClassLoader(JNIEnv* env, const char* name,
                                               jboolean init, jobject loader,
                                               jboolean throwError))
  ...
  TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
  Handle h_loader(THREAD, JNIHandles::resolve(loader));
  jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
                                               Handle(), throwError, THREAD);
  return result;
JVM_END

调用的find_class_from_class_loader()函数的实现如下:

源代码位置:openjdk/hotspot/src/share/vm/prims/jvm.cpp

jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init, 
Handle loader, Handle protection_domain,
jboolean throwError, TRAPS) {
  // 遵循双亲委派机制加载类
  Klass* klass = SystemDictionary::resolve_or_fail(name, loader, 
          protection_domain, throwError != 0, CHECK_NULL);
 
  KlassHandle klass_handle(THREAD, klass);
  if (init && klass_handle->oop_is_instance()) { // init的值为true
    klass_handle->initialize(CHECK_NULL); // 对类进行初始化操作
  }
  return (jclass) JNIHandles::make_local(env, klass_handle->java_mirror());
}

调用SystemDictionary::resolve_or_fail()函数在之前介绍过,函数会遵循双亲委派机制来加载类,通常创建或从Dictionary中查询已经加载的InstanceKlass实例,不涉及到对类的连接、初始化等操作。通过forName()方法调用函数时,调用initialize()函数执行类的初始化操作,我们在后面专门介绍类的初始化,这里暂不介绍。

 

posted on 2020-07-21 14:43  鸠摩(马智)  阅读(678)  评论(0编辑  收藏  举报

导航