JVM类加载器-源码分析

96
Justlearn
2017.04.30 14:00* 字数 1030

前言

我们在JVM类加载器-原理一文中了解了JVM类加载器的基本原理。现在我们一起通过ClassLoader类及其相关源码来详细分析、理解JVM类加载器的体系,深入理解JVM类加载器的原理与实现。

ClassLoader的主要责任就是加载类。ClassLoader通过一个类的名字,定位并且把这个class文件加载进JVM的内存里,生成可以表示这个类的结构。ClassLoader是JDK为我们提供的一个基础的类加载器,它本身是一个抽象类,我们在实现自己特殊需求的类加载器的时候,只需要根据我们自己的需要,覆写findClass方法(通过类的全限定名查找该类的class文件)。

ClassLoader的创建

ClassLoader的构造函数是private的,所以不能直接new一个ClassLoader实例。而是在ClassLoader中提供了一些静态方法,产生特定ClassLoader。如:

//该方法返回系统类加载器,该类加载器也是典型的用来启动应用的类加载器。
//该方法在运行时的启动序列里被首次调用,在这个时间点上,该方法构造的类加载器被设置为调用线程的上下文类加载器。
@CallerSensitive
    public static ClassLoader getSystemClassLoader() {
        initSystemClassLoader();
        if (scl == null) {
            return null;
        }
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            checkClassLoaderPermission(scl, Reflection.getCallerClass());
        }
        return scl;
    }
private static synchronized void initSystemClassLoader() {
        if (!sclSet) {
            if (scl != null)
                throw new IllegalStateException("recursive invocation");
            sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
            if (l != null) {
                Throwable oops = null;
                scl = l.getClassLoader();
                try {
                    scl = AccessController.doPrivileged(
                        new SystemClassLoaderAction(scl));
                } catch (PrivilegedActionException pae) {
                    oops = pae.getCause();
                    if (oops instanceof InvocationTargetException) {
                        oops = oops.getCause();
                    }
                }
                if (oops != null) {
                    if (oops instanceof Error) {
                        throw (Error) oops;
                    } else {
                        // wrap the exception
                        throw new Error(oops);
                    }
                }
            }
            sclSet = true;
        }
    }
private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
//判断类加载器是否支持并发
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();//并发情况下,每个线程通过同一个ClassLoader实例进行类的加载时,都会获得各自的锁
            package2certs = new ConcurrentHashMap<>();
            domains =
                Collections.synchronizedSet(new HashSet<ProtectionDomain>());
            assertionLock = new Object();
        } else {
            // no finer-grained lock; lock on the classloader instance
            parallelLockMap = null;
            package2certs = new Hashtable<>();
            domains = new HashSet<>();
            assertionLock = this;
        }
    }
  • 类加载的入口是从loadClass方法开始的,而类加载器的双亲委派模型也是在这里实现的。源码如下:
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    /**
     * Loads the class with the specified <a href="#name">binary name</a>.  The
     * default implementation of this method searches for classes in the
     * following order:
     * 该方法通过具体的类的名字加载这个类。默认的实现按以下的顺序搜寻这个类
     * <p><ol>
     *
     *   <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
     *   has already been loaded.  </p></li>
     * 调用findLoadedClass(String)方法检查该类是否已经被加载过
     *   <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
     *   on the parent class loader.  If the parent is <tt>null</tt> the class
     *   loader built-in to the virtual machine is used, instead.  </p></li>
     * 调用父类加载器的loadClass(String)方法来加载这个类,如果父类加载器为null则使用虚拟机内置的类加载器。
     *
     * <p> If the class was found using the above steps, and the
     * <tt>resolve</tt> flag is true, this method will then invoke the {@link
     * #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
     * 
     * <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
     * #findClass(String)}, rather than this method.  </p>
     * 官方建议继承ClassLoader的子类去覆写findClass方法而非loadClass方法
     * <p> Unless overridden, this method synchronizes on the result of
     * {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
     * during the entire class loading process.
     * 除非被覆写,该方法会在类加载的整个过程中持有getClassLoadingLock方法返回的锁
     */
    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
         //类加载期间持有一把锁
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

具体的逻辑分析如下:

  • 在加载类期间,调用线程会一直持有一把锁。基于loadClass方法的逻辑,我们可以很容易理解为什么loadClass方法的主要逻辑需要在一个同步块里。后续的逻辑里有很多对ClassLoader共享变量的操作如parent的赋值等。
/**
     * Returns the lock object for class loading operations.
     * For backward compatibility, the default implementation of this method
     * behaves as follows. If this ClassLoader object is registered as
     * parallel capable, the method returns a dedicated object associated
     * with the specified class name. Otherwise, the method returns this
     * ClassLoader object. </p>
     * 为了向前兼容,该方法实现如下:如果ClassLoader对象被注册为支持并发,那么该方法会new一个Object作为锁返回;否则,同步块的锁就是ClassLoader本身;
     * @since  1.7
     */
    protected Object getClassLoadingLock(String className) {
        Object lock = this;
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);//parallelLockMap是一个ConcurrentHashMap
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }
  • 检查类是否已经加载。
 protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }
//简单的检查加载的类的名字是否为空或是无效
private boolean checkName(String name) {
        if ((name == null) || (name.length() == 0))
            return true;
        if ((name.indexOf('/') != -1)
            || (!VM.allowArraySyntax() && (name.charAt(0) == '[')))
            return false;
        return true;
    }
private native final Class findLoadedClass0(String name);

我们发现findLoadedClass0是一个native方法。查了下openjdk源码如下:

JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findLoadedClass0(JNIEnv *env, jobject loader,
                                           jstring name)
{
    if (name == NULL) {
        return 0;
    } else {
        return JVM_FindLoadedClass(env, loader, name);
    }
JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name))
  JVMWrapper("JVM_FindLoadedClass");
  ResourceMark rm(THREAD);

  Handle h_name (THREAD, JNIHandles::resolve_non_null(name));
  Handle string = java_lang_String::internalize_classname(h_name, CHECK_NULL);

  const char* str   = java_lang_String::as_utf8_string(string());
  // Sanity check, don't expect null
  if (str == NULL) return NULL;

  const int str_len = (int)strlen(str);
  if (str_len > Symbol::max_length()) {
    //类名长度有限制
    // It's impossible to create this class;  the name cannot fit
    // into the constant pool.
    return NULL;
  }
  //
  TempNewSymbol klass_name = SymbolTable::new_symbol(str, str_len, CHECK_NULL);

  // Security Note:
  //   The Java level wrapper will perform the necessary security check allowing
  //   us to pass the NULL as the initiating class loader.
  Handle h_loader(THREAD, JNIHandles::resolve(loader));
  if (UsePerfData) {
    is_lock_held_by_thread(h_loader,
                           ClassLoader::sync_JVMFindLoadedClassLockFreeCounter(),
                           THREAD);
  }
  //全局符号字典查找该类的符号是否存在
  klassOop k = SystemDictionary::find_instance_or_array_klass(klass_name,
                                                              h_loader,
                                                              Handle(),
                                                              CHECK_NULL);

  return (k == NULL) ? NULL :
            (jclass) JNIHandles::make_local(env, Klass::cast(k)->java_mirror());
JVM_END
}

HotSpot虚拟机在永久代中增加了符号表。该表为哈希表用于将直接引用与运行时常量池的符号引用作射。所以该native方法的实质就是在jvm的常量池中查找该类的符号引用是否存在。

  • 接着的逻辑就是类的双亲加载机制的实现。把类加载的任务交给父类加载器执行,直到父类加载器为空,此时会返回通过JDK提供的系统启动类加载器加载的类。
private Class findBootstrapClassOrNull(String name)
    {
        if (!checkName(name)) return null;

        return findBootstrapClass(name);
    }

    // return null if not found
    private native Class findBootstrapClass(String name);
  • 如果最后发现该类没有被加载过,则由当前类加载器调用findClass方法来继续加载该类。

findClass

该方法和它的名字一样,就是根据类的名字ClassLoader不提供该方法的具体实现,要求我们根据自己的需要来覆写该方法。
所以我们可以看一看URLClassLoader对findClass方法的实现,类加载的工作又被代理给了defineClass方法:

protected Class<?> More ...findClass(final String name)
351         throws ClassNotFoundException
352    {
353        try {
354            return AccessController.doPrivileged(
355                new PrivilegedExceptionAction<Class>() {
356                    public Class More ...run() throws ClassNotFoundException {
357                        String path = name.replace('.', '/').concat(".class");
358                        Resource res = ucp.getResource(path, false);
359                        if (res != null) {
360                            try {
361                                return defineClass(name, res);
362                            } catch (IOException e) {
363                                throw new ClassNotFoundException(name, e);
364                            }
365                        } else {
366                            throw new ClassNotFoundException(name);
367                        }
368                    }
369                }, acc);
370        } catch (java.security.PrivilegedActionException pae) {
371            throw (ClassNotFoundException) pae.getException();
372        }
373    }

defineClass

defineClass方法主要是把字节数组转化为类的实例。同时definClass方法为final的,故不可以覆写。
同时defineClass也是一个native方法,具体也是由虚拟机实现,源码如下:

// common code for JVM_DefineClass() and JVM_DefineClassWithSource()
// and JVM_DefineClassWithSourceCond()
static jclass jvm_define_class_common(JNIEnv *env, const char *name,
                                      jobject loader, const jbyte *buf,
                                      jsize len, jobject pd, const char *source,
                                      jboolean verify, TRAPS) {
  if (source == NULL)  source = "__JVM_DefineClass__";

  assert(THREAD->is_Java_thread(), "must be a JavaThread");
  JavaThread* jt = (JavaThread*) THREAD;

  PerfClassTraceTime vmtimer(ClassLoader::perf_define_appclass_time(),
                             ClassLoader::perf_define_appclass_selftime(),
                             ClassLoader::perf_define_appclasses(),
                             jt->get_thread_stat()->perf_recursion_counts_addr(),
                             jt->get_thread_stat()->perf_timers_addr(),
                             PerfClassTraceTime::DEFINE_CLASS);

  if (UsePerfData) {
    ClassLoader::perf_app_classfile_bytes_read()->inc(len);
  }

  // Since exceptions can be thrown, class initialization can take place
  // if name is NULL no check for class name in .class stream has to be made.
  TempNewSymbol class_name = NULL;
  if (name != NULL) {
    const int str_len = (int)strlen(name);
    if (str_len > Symbol::max_length()) {
      // It's impossible to create this class;  the name cannot fit
      // into the constant pool.
      THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name);
    }
    //为类创建符号
    class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL);
  }

  ResourceMark rm(THREAD);
  ClassFileStream st((u1*) buf, len, (char *)source);
  Handle class_loader (THREAD, JNIHandles::resolve(loader));
  if (UsePerfData) {
    is_lock_held_by_thread(class_loader,
                           ClassLoader::sync_JVMDefineClassLockFreeCounter(),
                           THREAD);
  }
  Handle protection_domain (THREAD, JNIHandles::resolve(pd));
  //从字节文件中为该类解析创建一个klassOop对象,表示Java类
  klassOop k = SystemDictionary::resolve_from_stream(class_name, class_loader,
                                                     protection_domain, &st,
                                                     verify != 0,
                                                     CHECK_NULL);

  if (TraceClassResolution && k != NULL) {
    trace_class_resolution(k);
  }

  return (jclass) JNIHandles::make_local(env, Klass::cast(k)->java_mirror());
}

AppClassLoader/ExtClassLoader/BootstrapClassLoader

系统为我们提供的几个类记载器主要是在sun.misc.Launcher类找那个定义。sun.misc.Launcher$AppClassLoader主要用于加载java.class.path目录下的类,sun.misc.Launcher$ExtClassLoader主要用于加载<JAVA_HOME>\lib\ext或java.ext.dirs指定路径下的类库。这些类的具体继承关系如下:


classloaderext.png

启动类加载器是无法被Java程序直接引用,它是在虚拟机启动时创建的,需要它来加载类时,都是通过navtive方法findBootstrapClass来简洁使用的。

总结

类加载器的双亲委派模型的实现逻辑十分简单,都在ClassLoader的loadClass方法中,我们在实现自己的类加载器时,最好遵守这种模型,由于在jvm中,一个类类型实例的唯一性是由加载他的类加载器与这个类的类型共同确定的。这对保证Java程序的稳定运作十分重要。

参考引用

  • JDK1.7源码
技术开发总结
Gupao