zoukankan      html  css  js  c++  java
  • Java主类的装载

    在JavaMain()函数(定义在openjdk/jdk/src/share/bin/java.c文件中)中调用LoadMainClass()函数加载Java主类。LoadMainClass()函数的实现如下:

    源代码位置:openjdk/jdk/src/share/bin/java.c
    
    /*
     * Loads a class and verifies that the main class is present and it is ok to
     * call it for more details refer to the java implementation.
     */
    static jclass LoadMainClass(JNIEnv *env, int mode, char *name){
    
        jmethodID   mid;
        jstring     str;
        jobject     result;
        jlong       start, end;
    
        // 加载sun.launcher.LauncherHelper类
        jclass cls = GetLauncherHelperClass(env);
    
        // 获取sun.launcher.LauncherHelper类中定义的checkAndLoadMain()方法的指针
        NULL_CHECK0(mid = (*env)->GetStaticMethodID(env,cls,"checkAndLoadMain","(ZILjava/lang/String;)Ljava/lang/Class;"));
    
        // 调用sun.launcher.LauncherHelper类中的checkAndLoadMain()方法
        str = NewPlatformString(env, name);
        result = (*env)->CallStaticObjectMethod(env, cls, mid, USE_STDERR, mode, str);
    
        return (jclass)result;
    } 
    

    下面介绍如上函数调用的一些函数。 

    1、GetLauncherHelperClass()函数

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

    jclass GetLauncherHelperClass(JNIEnv *env){
        if (helperClass == NULL) {
            NULL_CHECK0(helperClass = FindBootStrapClass(env,"sun/launcher/LauncherHelper"));
        }
        return helperClass;
    }
    
    
    /*
     * The implementation for finding classes from the bootstrap
     * class loader, refer to java.h
     */
    static FindClassFromBootLoader_t *findBootClass = NULL;
    
    // 参数classname的值为"sun/launcher/LauncherHelper"。
    jclass FindBootStrapClass(JNIEnv *env, const char* classname){
       if (findBootClass == NULL) {
           // 返回指向JVM_FindClassFromBootLoader()函数的函数指针
           findBootClass = (FindClassFromBootLoader_t *)dlsym(RTLD_DEFAULT,"JVM_FindClassFromBootLoader"); 
       }
       return findBootClass(env, classname);
    }
    

    通过函数指针findBootClass来调用JVM_FindClassFromBootLoader()函数。JVM_FindClassFromBootLoader()函数的实现如下:

    JVM_ENTRY(jclass, JVM_FindClassFromBootLoader(JNIEnv* env,const char* name))
    
      TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
      Klass* k = SystemDictionary::resolve_or_null(h_name, CHECK_NULL);
      if (k == NULL) {
        return NULL;
      }
    
      return (jclass) JNIHandles::make_local(env, k->java_mirror());
    JVM_END
    

    调用的SystemDictionary::resolve_or_null()函数在前面已经详细介绍过,这里不再介绍。

    2、GetStaticMethodID()函数

    在通过JNI的方式调用Java方法时,首先要获取到方法的methodID。调用GetStaticMethodID()函数查找Java启动方法(Java主类中的main()方法)的methodID。调用GetStaticMethodID()函数其实调用的是jni_GetStaticMethodID()函数,实现如下:

    // 传递的参数name为"checkAndLoadMain",而sig为"(ZILjava/lang/String;)Ljava/lang/Class;"。
    JNI_ENTRY(jmethodID, jni_GetStaticMethodID(JNIEnv *env, jclass clazz,const char *name, const char *sig))
      jmethodID ret = get_method_id(env, clazz, name, sig, true, thread);
      return ret;
    JNI_END
    

    get_method_id()函数的实现如下:

    static jmethodID get_method_id(JNIEnv *env, jclass clazz, const char *name_str,
                                   const char *sig, bool is_static, TRAPS) {
      // %%%% This code should probably just call into a method in the LinkResolver
      //
      // The class should have been loaded (we have an instance of the class
      // passed in) so the method and signature should already be in the symbol
      // table.  If they're not there, the method doesn't exist.
      const char *name_to_probe = (name_str == NULL)
                            ? vmSymbols::object_initializer_name()->as_C_string()
                            : name_str;
      TempNewSymbol name = SymbolTable::probe(name_to_probe, (int)strlen(name_to_probe));
      TempNewSymbol signature = SymbolTable::probe(sig, (int)strlen(sig));
    
      KlassHandle klass(THREAD,java_lang_Class::as_Klass(JNIHandles::resolve_non_null(clazz)));
    
      // Make sure class is linked and initialized before handing id's out to
      // Method*s.
      klass()->initialize(CHECK_NULL);
    
      Method* m;
      if (name == vmSymbols::object_initializer_name() || // name为<init>
          name == vmSymbols::class_initializer_name()) {  // name为<clinit>
        // Never search superclasses for constructors
        if (klass->oop_is_instance()) { // 在查找构造函数时,只查找当前类中的构造函数,不查找超类构造函数
          m = InstanceKlass::cast(klass())->find_method(name, signature);
        } else {
          m = NULL;
        }
      } else {
        m = klass->lookup_method(name, signature); // 在特定类中查找方法
        if (m == NULL &&  klass->oop_is_instance()) {
          m = InstanceKlass::cast(klass())->lookup_method_in_ordered_interfaces(name, signature);
        }
      }
    
      return m->jmethod_id(); // 获取方法对应的methodID,methodID指定后不会变,所以可以重复使用methodID
    }
    

    查找构造函数时调用InstanceKlass类中的find_method()方法,这个方法不会查找超类;查找普通方法时,调用Klass中的lookup_method()或InstanceKlass类中的lookup_method_in_ordered_interfaces()方法,这两个方法会从父类中查找,例如lookup_method()方法的实现如下:

    Method* lookup_method(Symbol* name, Symbol* signature) const {
        return uncached_lookup_method(name, signature);
    }
    
    // uncached_lookup_method searches both the local class methods array and all
    // superclasses methods arrays, skipping any overpass methods in superclasses.
    Method* InstanceKlass::uncached_lookup_method(Symbol* name, Symbol* signature) const {
      Klass* klass = const_cast<InstanceKlass*>(this);
      bool dont_ignore_overpasses = true;  // For the class being searched, find its overpasses.
      while (klass != NULL) {
        Method* method = InstanceKlass::cast(klass)->find_method(name, signature);
        if ((method != NULL) && (dont_ignore_overpasses || !method->is_overpass())) {
          return method;
        }
        klass = InstanceKlass::cast(klass)->super();
        dont_ignore_overpasses = false;  // Ignore overpass methods in all superclasses.
      }
      return NULL;
    }

    如果调用find_method()无法从当前类中查找到对应的方法,那么通过while循环一直从继承链往上查找,如果找到就直接返回,否则返回NULL。

    find_method()方法的实现如下:

    // find_method looks up the name/signature in the local methods array
    Method* InstanceKlass::find_method(Symbol* name, Symbol* signature) const {
      return InstanceKlass::find_method(methods(), name, signature);
    }
    
    // find_method looks up the name/signature in the local methods array
    Method* InstanceKlass::find_method(Array<Method*>* methods, Symbol* name, Symbol* signature) {
      int hit = find_method_index(methods, name, signature);
      return hit >= 0 ? methods->at(hit): NULL;
    }
    
    // Used directly for default_methods to find the index into the
    // default_vtable_indices, and indirectly by find_method
    // find_method_index looks in the local methods array to return the index
    // of the matching name/signature
    int InstanceKlass::find_method_index(Array<Method*>* methods, Symbol* name, Symbol* signature) {
      int hit = binary_search(methods, name); // 从methods中通过二分算法来查找名称为name的方法
      if (hit != -1) {
        Method* m = methods->at(hit);
        // Do linear search to find matching signature.  First, quick check for common case
        if (m->signature() == signature)
        	return hit;
    
        // search downwards through overloaded methods
        int i;
        for (i = hit - 1; i >= 0; --i) {
            Method* m = methods->at(i);
            if (m->name() != name)
            	break;
            if (m->signature() == signature)
            	return i;
        }
    
        // search upwards
        for (i = hit + 1; i < methods->length(); ++i) {
            Method* m = methods->at(i);
            if (m->name() != name)
            	break;
            if (m->signature() == signature)
            	return i;
        }
        // not found
      }
    
      return -1;
    }
    

    当前的方法存储在instanceKlass类的_methods属性中,并且是按一定的顺序存储,这样就可以使用二分查找算法加快查找速度了,如果找到方法,则返回对应在数组中的下标位置,否则返回-1。 

    3、CallStaticObjectMethod()函数

    在LoadMainClass()函数中调用(*env)->CallStaticObjectMethod()函数会执行sun.launcher.LauncherHelper类的checkAndLoadMain()方法。CallStaticObjectMethod()方法定义在jni.cpp文件中,实现时会通过jni_invoke_static()函数执行checkAndLoadMain()方法。jni_invoke_static()函数的实现如下:

    static void jni_invoke_static(JNIEnv *env, JavaValue* result, jobject receiver, JNICallType call_type,
    		jmethodID method_id, JNI_ArgumentPusher *args, TRAPS) {
      methodHandle method(THREAD, Method::resolve_jmethod_id(method_id));
    
      // Create object to hold arguments for the JavaCall, and associate it with
      // the jni parser
      ResourceMark rm(THREAD);
      int number_of_parameters = method->size_of_parameters();
      // 这里进一步将要传给Java的参数转换为JavaCallArguments对象传下去
      JavaCallArguments java_args(number_of_parameters);
      args->set_java_argument_object(&java_args);
    
      assert(method->is_static(), "method should be static");
    
      // Fill out JavaCallArguments object
      args->iterate( Fingerprinter(method).fingerprint() );
      // Initialize result type
      result->set_type(args->get_ret_type());
    
      // Invoke the method. Result is returned as oop.
      // 供C/C++程序调用Java方法
      JavaCalls::call(result, method, &java_args, CHECK);
    
      // Convert result
      if (result->get_type() == T_OBJECT || result->get_type() == T_ARRAY) {
         result->set_jobject(JNIHandles::make_local(env, (oop) result->get_jobject()));
      }
    }
    

    函数最终通过JavaCalls::call()方法调用Java方法,在介绍方法执行引擎时将会详细介绍。方法看起来逻辑很多,其实都是因为JNI调用时,需要对参数进行转换,在JNI环境下只能使用句柄来访问虚拟机对象,而在虚拟机操作时,由于实现逻辑的需要,必须要操作虚拟机对象,所以不可避免在每次方法的开始和结束都需要对参数进行转换。如调用Method::resolve_jmethod_id()、调用JNIHandles::make_local()等方法。  

    最后看一下调用JavaCalls::call()方法执行的Java方法checkAndLoadMain()方法的实现,如下:

    源代码位置如下:/home/mazhi/workspace/openjdk/jdk/src/share/classes/sun/launcher/LauncherHelper.java

    /**
         * This method does the following:
         * 1. gets the classname from a Jar's manifest, if necessary
         * 2. loads the class using the System ClassLoader
         * 3. ensures the availability and accessibility of the main method,
         *    using signatureDiagnostic method.
         *    a. does the class exist
         *    b. is there a main
         *    c. is the main public
         *    d. is the main static
         *    e. does the main take a String array for args
         * 4. if no main method and if the class extends FX Application, then call
         *    on FXHelper to determine the main class to launch
         * 5. and off we go......
         *
         * @param printToStderr if set, all output will be routed to stderr
         * @param mode LaunchMode as determined by the arguments passed on the
         * command line
         * @param what either the jar file to launch or the main class when using
         * LM_CLASS mode
         * @return the application's main class
         */
        public static Class<?> checkAndLoadMain(boolean printToStderr,
                                                int mode,
                                                String what) {
            initOutput(printToStderr);
            // get the class name
            String cn = null;
            switch (mode) { 
                case LM_CLASS:  
                    cn = what;
                    break;
                case LM_JAR:
                    cn = getMainClassFromJar(what);
                    break;
                default:
                    // should never happen
                    throw new InternalError("" + mode + ": Unknown launch mode");
            }
            cn = cn.replace('/', '.');
            Class<?> mainClass = null;
            try {
                mainClass = scloader.loadClass(cn); // 加载主类
            } catch (NoClassDefFoundError | ClassNotFoundException cnfe) {
                ...
            }
            // set to mainClass
            appClass = mainClass;
    
            return mainClass;
        }
    

    从这里可以看出加载main方法类的加载器是系统类加载器,而系统类加载器其实就是AppClassLoader。所以,main方法默认加载器是AppClassLoder,并且传给当前线程上下文的加载器也是AppClassLoader。

    AppClassLoader/ExtClassLoader都是Launcher的内部类。先初始化ExtClassLoader,并将ExtClassLoader作为父加载器传给AppClassLoder

    scloader是全局变量,定义如下:
    private static final ClassLoader scloader = ClassLoader.getSystemClassLoader();

    调用scloader的loadClass()方法会调用到java.lang.ClassLoader的loadClass()方法,之前已经介绍过这个方法,首先通过findLoadedClass()方法判断当前加载器是否已经加载了指定的类,如果没有加载并且parent不为NULL,调用parent.loadClass()方法来完成,而AppClassLoader的父加载器是ExtClassLoader,这是加载JDK中的扩展类,并不会加载Java主类,所以只能调用this.findClass()方法来完成主类的加载。对于AppClassLoader来说,调用的是URLClassLoader中实现的findClass()方法,最终会调用本地方法defineClass1()来完成,这个方法在介绍类的双亲委派机制时详细介绍过,这里不再介绍。

    相关文章的链接如下:

    1、在Ubuntu 16.04上编译OpenJDK8的源代码 

    2、调试HotSpot源代码

    3、HotSpot项目结构 

    4、HotSpot的启动过程 

    5、HotSpot二分模型(1)

    6、HotSpot的类模型(2)  

    7、HotSpot的类模型(3) 

    8、HotSpot的类模型(4)

    9、HotSpot的对象模型(5)  

    10、HotSpot的对象模型(6) 

    11、操作句柄Handle(7)

    12、句柄Handle的释放(8)

    13、类加载器 

    14、类的双亲委派机制 

    15、核心类的预装载

    作者持续维护的个人博客classloading.com

    关注公众号,有HotSpot源码剖析系列文章!

      

      

  • 相关阅读:
    LeetCode: Maximum Product Subarray 解题报告
    LeetCode: Populating Next Right Pointers in Each Node II 解题报告
    LeetCode: Populating Next Right Pointers in Each Node 解题报告
    LeetCode: Word Search 解题报告
    C语言文件操作
    多线程
    C语言函数指针的使用
    进程
    网络编程
    进程间通信——管道通信
  • 原文地址:https://www.cnblogs.com/mazhimazhi/p/13353233.html
Copyright © 2011-2022 走看看