OpenJDK类加载实现浅析#3:并行加载

今天来看下OpenJDK类加载中关于并行加载的一些代码。还是一样要分别看看类加载库跟虚拟机,因为二者在这方面仍然是需要配合完成的。

类加载库所做的工作

在JDK7之前,ClassLoader#loadClass方法是synchronized的,

protected synchronized Class<?> loadClass(String name, boolean resolve)

也就是说,类加载的时候,直接是要锁住整个classloader的。

到了JDK7,这个地方终于做出了优化,直接来看下loadClass方法的代码,

    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;
        }
    }

改成了同步由getClassLoadingLock(name)返回的对象,看看这个方法,

    /**
     * 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>
     *
     * @param  className
     *         The name of the to-be-loaded class
     *
     * @return the lock for class loading operations
     *
     * @throws NullPointerException
     *         If registered as parallel capable and <tt>className</tt> is null
     *
     * @see #loadClass(String, boolean)
     *
     * @since  1.7
     */
    protected Object getClassLoadingLock(String className) {
        Object lock = this; //// 向后兼容
        if (parallelLockMap != null) {
            Object newLock = new Object();
            lock = parallelLockMap.putIfAbsent(className, newLock);
            if (lock == null) {
                lock = newLock;
            }
        }
        return lock;
    }

将要进行加载的类的类名映射到一个new出来的Object,对这个Object进行同步相当于给正在加载的类加锁了。所以其实是通过减小锁的粒度来进行了优化。

在JDK7的类库中,可以看到好多classloader都会调用ClassLoader#registerAsParallelCapable,这个方法是用来告诉虚拟机,这个classloader是支持并行加载的。虚拟机怎么使用的我们下面会说到,先来看看这个方法做了啥,

    /**
     * Registers the caller as parallel capable.</p>
     * The registration succeeds if and only if all of the following
     * conditions are met: <br>
     * 1. no instance of the caller has been created</p>
     * 2. all of the super classes (except class Object) of the caller are
     * registered as parallel capable</p>
     * Note that once a class loader is registered as parallel capable, there
     * is no way to change it back. </p>
     *
     * @return  true if the caller is successfully registered as
     *          parallel capable and false if otherwise.
     *
     * @since   1.7
     */
    @CallerSensitive
    protected static boolean registerAsParallelCapable() {
        Class<? extends ClassLoader> callerClass =
            Reflection.getCallerClass().asSubclass(ClassLoader.class);
        return ParallelLoaders.register(callerClass);
    }
    /**
     * Encapsulates the set of parallel capable loader types.
     */
    private static class ParallelLoaders {
        private ParallelLoaders() {}

        // the set of parallel capable loader types
        private static final Set<Class<? extends ClassLoader>> loaderTypes =
            Collections.newSetFromMap(
                new WeakHashMap<Class<? extends ClassLoader>, Boolean>());
        static {
            synchronized (loaderTypes) { loaderTypes.add(ClassLoader.class); }
        }

        /**
         * Registers the given class loader type as parallel capabale.
         * Returns {@code true} is successfully registered; {@code false} if
         * loader‘s super class is not registered.
         */
        static boolean register(Class<? extends ClassLoader> c) {
            synchronized (loaderTypes) {
                if (loaderTypes.contains(c.getSuperclass())) {
                    // register the class loader as parallel capable
                    // if and only if all of its super classes are.
                    // Note: given current classloading sequence, if
                    // the immediate super class is parallel capable,
                    // all the super classes higher up must be too.
                    loaderTypes.add(c);
                    return true;
                } else {
                    return false;
                }
            }
        }

        /**
         * Returns {@code true} if the given class loader type is
         * registered as parallel capable.
         */
        static boolean isRegistered(Class<? extends ClassLoader> c) {
            synchronized (loaderTypes) {
                return loaderTypes.contains(c);
            }
        }
    }

只是简单了将支持并行加载的classloader类型放到了一个Set里面,并提供了查询接口,这个查询接口只在构造函数里用到了,

    private ClassLoader(Void unused, ClassLoader parent) {
        this.parent = parent;
        if (ParallelLoaders.isRegistered(this.getClass())) {
            parallelLockMap = new ConcurrentHashMap<>();
            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;
        }
    }

需要留意一下这个parallelLockMap,这个map就是getClassLoadingLock方法用到的那个映射关系,除此之外,虚拟机还利用它来判断classloader是否支持并行加载,见下文。

虚拟机所做的工作

既然类加载库已经通过减小锁粒度来支持并行加载了,那为什么虚拟机还要再额外支持一把?理由很简单,in case they do not synchronize around FindLoadedClass/DefineClass

直接来看SystemDictionary::resolve_from_stream方法(不清楚类加载整体流程的,可以参考这篇文章),

klassOop SystemDictionary::resolve_from_stream(Symbol* class_name,
                                               Handle class_loader,
                                               Handle protection_domain,
                                               ClassFileStream* st,
                                               bool verify,
                                               TRAPS) {
  ///////1. 判断是否需要对classloader加锁
  // Classloaders that support parallelism, e.g. bootstrap classloader,
  // or all classloaders with UnsyncloadClass do not acquire lock here
  bool DoObjectLock = true;
  if (is_parallelCapable(class_loader)) {
    DoObjectLock = false;
  }

  ///////2. 根据上面的判断结果,如果需要的话则对classloader加锁
  // 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);

  ///////3. 解析class的二进制流
  TempNewSymbol parsed_name = NULL;

  // Parse the stream. Note that we do this even though this klass might
  // already be present in the SystemDictionary, otherwise we would not
  // throw potential ClassFormatErrors.
  //
  // Note: "name" is updated.
  // Further note:  a placeholder will be added for this class when
  //   super classes are loaded (resolve_super_or_fail). We expect this
  //   to be called for all classes but java.lang.Object; and we preload
  //   java.lang.Object through resolve_or_fail, not this path.

  instanceKlassHandle k = ClassFileParser(st).parseClassFile(class_name,
                                                             class_loader,
                                                             protection_domain,
                                                             parsed_name,
                                                             verify,
                                                             THREAD);

  ///////4. 安全检查
  const char* pkg = "java/";
  if (!HAS_PENDING_EXCEPTION &&
      !class_loader.is_null() &&
      parsed_name != NULL &&
      !strncmp((const char*)parsed_name->bytes(), pkg, strlen(pkg))) {
    // It is illegal to define classes in the "java." package from
    // JVM_DefineClass or jni_DefineClass unless you‘re the bootclassloader
    ResourceMark rm(THREAD);
    char* name = parsed_name->as_C_string();
    char* index = strrchr(name, ‘/‘);
    *index = ‘\0‘; // chop to just the package name
    while ((index = strchr(name, ‘/‘)) != NULL) {
      *index = ‘.‘; // replace ‘/‘ with ‘.‘ in package name
    }
    const char* fmt = "Prohibited package name: %s";
    size_t len = strlen(fmt) + strlen(name);
    char* message = NEW_RESOURCE_ARRAY(char, len);
    jio_snprintf(message, len, fmt, name);
    Exceptions::_throw_msg(THREAD_AND_LOCATION,
      vmSymbols::java_lang_SecurityException(), message);
  }

  if (!HAS_PENDING_EXCEPTION) {
    assert(parsed_name != NULL, "Sanity");
    assert(class_name == NULL || class_name == parsed_name, "name mismatch");
    // Verification prevents us from creating names with dots in them, this
    // asserts that that‘s the case.
    assert(is_internal_format(parsed_name),
           "external class name format used internally");

    ///////5. 写入SystemDictionary
    // Add class just loaded
    // If a class loader supports parallel classloading handle parallel define requests
    // find_or_define_instance_class may return a different instanceKlass
    if (is_parallelCapable(class_loader)) {
      k = find_or_define_instance_class(class_name, class_loader, k, THREAD);
    } else {
      define_instance_class(k, THREAD);
    }
  }

  ///////6. 如果异常了,需要清理placeholder
  // If parsing the class file or define_instance_class failed, we
  // need to remove the placeholder added on our behalf. But we
  // must make sure parsed_name is valid first (it won‘t be if we had
  // a format error before the class was parsed far enough to
  // find the name).
  if (HAS_PENDING_EXCEPTION && parsed_name != NULL) {
    unsigned int p_hash = placeholders()->compute_hash(parsed_name,
                                                       class_loader);
    int p_index = placeholders()->hash_to_index(p_hash);
    {
    /////// 需要对SystemDictionary_lock加锁
    MutexLocker mu(SystemDictionary_lock, THREAD);
    placeholders()->find_and_remove(p_index, p_hash, parsed_name, class_loader, THREAD);
    SystemDictionary_lock->notify_all();
    }
    return NULL;
  }

  ///////7. 加载成功后查询SystemDictionary再确认一下,debug_only
  // Make sure that we didn‘t leave a place holder in the
  // SystemDictionary; this is only done on success
  debug_only( {
    if (!HAS_PENDING_EXCEPTION) {
      assert(parsed_name != NULL, "parsed_name is still null?");
      Symbol*  h_name    = k->name();
      Handle h_loader (THREAD, k->class_loader());

      /////// 需要对SystemDictionary_lock加锁
      MutexLocker mu(SystemDictionary_lock, THREAD);

      klassOop check = find_class(parsed_name, class_loader);
      assert(check == k(), "should be present in the dictionary");

      klassOop check2 = find_class(h_name, h_loader);
      assert(check == check2, "name inconsistancy in SystemDictionary");
    }
  } );

  return k();
}

先来看下判断是否支持并行加载的方法SystemDictionary::is_parallelCapable

// ----------------------------------------------------------------------------
// Parallel class loading check

bool SystemDictionary::is_parallelCapable(Handle class_loader) {
  if (UnsyncloadClass || class_loader.is_null()) return true;
  if (AlwaysLockClassLoader) return false;
  return java_lang_ClassLoader::parallelCapable(class_loader());
}

UnsyncloadClassAlwaysLockClassLoader这些Flagglobals.hpp中定义,看下java_lang_ClassLoader::parallelCapable方法,

// For class loader classes, parallelCapable defined
// based on non-null field
// Written to by java.lang.ClassLoader, vm only reads this field, doesn‘t set it
bool java_lang_ClassLoader::parallelCapable(oop class_loader) {
  if (!JDK_Version::is_gte_jdk17x_version()
     || parallelCapable_offset == -1) {
     // Default for backward compatibility is false
     return false;
  }
  return (class_loader->obj_field(parallelCapable_offset) != NULL);
}

通过判断classloader在parallelCapable_offset这个offset上面的field是否为null来判断是否支持并行加载。这个字段其实就是上面我们提到的parallelLockMap

// Support for java_lang_ClassLoader
bool java_lang_ClassLoader::offsets_computed = false;
int  java_lang_ClassLoader::parallelCapable_offset = -1;

void java_lang_ClassLoader::compute_offsets() {
  assert(!offsets_computed, "offsets should be initialized only once");
  offsets_computed = true;

  // The field indicating parallelCapable (parallelLockMap) is only present starting in 7,
  klassOop k1 = SystemDictionary::ClassLoader_klass();
  compute_optional_offset(parallelCapable_offset,
    k1, vmSymbols::parallelCapable_name(), vmSymbols::concurrenthashmap_signature());
}

vmSymbols::parallelCapable_name()

  /* used to identify class loaders handling parallel class loading */                                              template(parallelCapable_name,                      "parallelLockMap")                                          \

回头去看ClassLoader的代码你会发现只有调用了registerAsParallelCapable方法,这个字段才不会是null

根据是否支持并发来加锁的逻辑在ObjectLocker中,

ObjectLocker::ObjectLocker(Handle obj, Thread* thread, bool doLock) {
  _dolock = doLock;
  _thread = thread;
  debug_only(if (StrictSafepointChecks) _thread->check_for_valid_safepoint_state(false);)
  _obj = obj;

  if (_dolock) {
    TEVENT (ObjectLocker) ;

    ObjectSynchronizer::fast_enter(_obj, &_lock, false, _thread);
  }
}

接下来看看比较关键的写入SystemDictionary这一步,

// Support parallel classloading
// All parallel class loaders, including bootstrap classloader
// lock a placeholder entry for this class/class_loader pair
// to allow parallel defines of different classes for this class loader
// With AllowParallelDefine flag==true, in case they do not synchronize around
// FindLoadedClass/DefineClass, calls, we check for parallel
// loading for them, wait if a defineClass is in progress
// and return the initial requestor‘s results
// This flag does not apply to the bootstrap classloader.
// With AllowParallelDefine flag==false, call through to define_instance_class
// which will throw LinkageError: duplicate class definition.
// False is the requested default.
// For better performance, the class loaders should synchronize
// findClass(), i.e. FindLoadedClass/DefineClassIfAbsent or they
// potentially waste time reading and parsing the bytestream.
// Note: VM callers should ensure consistency of k/class_name,class_loader
instanceKlassHandle SystemDictionary::find_or_define_instance_class(Symbol* class_name, Handle class_loader, instanceKlassHandle k, TRAPS) {

  ///////1. 计算class/class_loader在dictionary与placeholders中的index
  instanceKlassHandle nh = instanceKlassHandle(); // null Handle
  Symbol*  name_h = k->name(); // passed in class_name may be null

  unsigned int d_hash = dictionary()->compute_hash(name_h, class_loader);
  int d_index = dictionary()->hash_to_index(d_hash);

// Hold SD lock around find_class and placeholder creation for DEFINE_CLASS
  unsigned int p_hash = placeholders()->compute_hash(name_h, class_loader);
  int p_index = placeholders()->hash_to_index(p_hash);
  PlaceholderEntry* probe;

  {
    ///////2. 查找dictionary,确定是否已加载完成,如果已加载完成则直接返回
    /////// 需要对SystemDictionary_lock加锁
    MutexLocker mu(SystemDictionary_lock, THREAD);
    // First check if class already defined
    if (UnsyncloadClass || (is_parallelDefine(class_loader))) {
      klassOop check = find_class(d_index, d_hash, name_h, class_loader);
      if (check != NULL) {
        return(instanceKlassHandle(THREAD, check));
      }
    }

    ///////3. 查找placeholders
    ///////   如果没有该class/class_loader,则写入
    ///////   如果已有该class/class_loader,则阻塞
    // Acquire define token for this class/classloader
    probe = placeholders()->find_and_add(p_index, p_hash, name_h, class_loader, PlaceholderTable::DEFINE_CLASS, NULL, THREAD);
    // Wait if another thread defining in parallel
    // All threads wait - even those that will throw duplicate class: otherwise
    // caller is surprised by LinkageError: duplicate, but findLoadedClass fails
    // if other thread has not finished updating dictionary
    while (probe->definer() != NULL) {
      SystemDictionary_lock->wait();
    }

    ///////4. 根据上一步的判断有以下两种结果
    ///////   写入后,继续进行后续的操作
    ///////   阻塞并被唤醒后,表明该类的加载已由其他线程完成,可以返回结果了
    // Only special cases allow parallel defines and can use other thread‘s results
    // Other cases fall through, and may run into duplicate defines
    // caught by finding an entry in the SystemDictionary
    if ((UnsyncloadClass || is_parallelDefine(class_loader)) && (probe->instanceKlass() != NULL)) {
        probe->remove_seen_thread(THREAD, PlaceholderTable::DEFINE_CLASS);
        placeholders()->find_and_remove(p_index, p_hash, name_h, class_loader, THREAD);
        SystemDictionary_lock->notify_all();
#ifdef ASSERT
        klassOop check = find_class(d_index, d_hash, name_h, class_loader);
        assert(check != NULL, "definer missed recording success");
#endif
        return(instanceKlassHandle(THREAD, probe->instanceKlass()));
    } else {
      // This thread will define the class (even if earlier thread tried and had an error)
      probe->set_definer(THREAD);
    }
  }

  ///////5. 真正写入SystemDictionary
  define_instance_class(k, THREAD);

  Handle linkage_exception = Handle(); // null handle

  ///////6. 加载完成,清理placeholders并唤醒等待的线程
  // definer must notify any waiting threads
  {
    MutexLocker mu(SystemDictionary_lock, THREAD);
    PlaceholderEntry* probe = placeholders()->get_entry(p_index, p_hash, name_h, class_loader);
    assert(probe != NULL, "DEFINE_CLASS placeholder lost?");
    if (probe != NULL) {
      if (HAS_PENDING_EXCEPTION) {
        linkage_exception = Handle(THREAD,PENDING_EXCEPTION);
        CLEAR_PENDING_EXCEPTION;
      } else {
        probe->set_instanceKlass(k());
      }
      probe->set_definer(NULL);
      probe->remove_seen_thread(THREAD, PlaceholderTable::DEFINE_CLASS);
      placeholders()->find_and_remove(p_index, p_hash, name_h, class_loader, THREAD);
      SystemDictionary_lock->notify_all();
    }
  }

  // Can‘t throw exception while holding lock due to rank ordering
  if (linkage_exception() != NULL) {
    THROW_OOP_(linkage_exception(), nh); // throws exception and returns
  }

  return k;
}

所以其实也是通过减小了锁的粒度,只锁SystemDictionary_lock,来进行优化的。不过这个锁粒度个人感觉还是有点粗的,应该还可以想办法再优化一下:)

最后总结一下,

类加载库层面,

  • 不支持并发:同步classloader,synchronized方法;
  • 支持并发:同步classname对象,synchronized代码块;

虚拟机层面,

  • 不支持并发:对classloader加锁,ObjectLocker ol(lockObject, THREAD, DoObjectLock);
  • 支持并发:对systemdictionary加锁,MutexLocker mu(SystemDictionary_lock, THREAD);
时间: 2024-10-24 23:50:39

OpenJDK类加载实现浅析#3:并行加载的相关文章

LABjs学习(一)之无阻塞动态并行加载脚本文件以及管理执行顺序

什么是LABjs LABjs是一个动态的脚本加载器,LABjs的定义特性是能够在浏览器允许的范围内以最快的速度并行加载所有JavaScript文件,但是如果文件之间存在依赖关系,则可以选择确保正确的执行顺序. 总计来说就是:动态并行加载脚本文件以及管理加载脚本文件的执行顺序 使用方法 $LAB对象替代了<script>标签,然后.script()方法表示加载Javascript文件,不带参数的.wait()方法表示立即运行刚才加载的Javascript文件,带参数的.wait()方法也是立即运

js的并行加载以及顺序执行

重新温习了下这段内容,发现各个浏览器的兼容性真的是搞大了头,处理起来很是麻烦. 现在现总结下并行加载多个js的方法: 1,对于动态createElement('script')的方式,对所有浏览器都是异步并行加载的.这里所说的并行不仅仅指的是 js并行加载,也包括js和其他资源比如图片,iframe的加载.但是此种方式在Firefox的2.0 3.0 3.1版本和opera 9.63 下是可以顺序执行的.但是由于Kyle的提议,现代浏览器都可以通过对动态创建的script元素设置属性async=

js顺序加载与并行加载

前端优化过程中常提到js的加载方式,下面说下几种常用的加载方式: 1:head标签内插入<script>标签 <script type="text/javaScript" src="test.js"></script> 这是最常见的方法,但是这个方法有个最大的问题,就是当浏览器解析到<script>标签时,浏览器会停止解析其后的内容,而优先下载脚本文件,并执行其中的代码,是个阻塞的过程,这意味着,其后的test.css

head.js让网站并行加载但顺序执行JS

http://headjs.com/ 并行加载JS,但是执行的时候却按顺序执行,提高网站速度 <script src="js/head.min.js"></script> <script type="text/javascript"> head.js("js/jquery-1.6.1.min.js","js/jquery.validate.min.js","js/my_valida

js的并行加载与顺序执行

javaScript文件(下面简称脚本文件)需要被HTML文件引用才能在浏览器中运行.在HTML文件中可以通过不同的方式来引用脚本文件,我们需要关注的是,这些方式的具体实现和这些方式可能会带来的性能问题. 当浏览器遇到(内嵌)<script>标签时,当前浏览器无从获知javaScript是否会修改页面内容.因此,这时浏览器会停止处理页面,先执行javaScript代码,然后再继续解析和渲染页面.同样的情况也发生在使用 src 属性加在javaScript的过程中(即外链 javaScript)

自定义类加载器:从网上加载class到内存、实例化调用其中的方法

1.JDK 默认提供了如下三种ClassLoader: BootStrap ClassLoader:称为启动类加载器,C++实现的,是Java类加载层次中最顶层的类加载器(JVM启动后初始化的),负责加载JDK中的核心类库,如:rt.jar.resources.jar.charsets.jar等: ExtensionClassLoader:称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar.该加载器是有java实现的,由Bootst

async/await实现图片的串行、并行加载

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" con

OpenJDK类加载实现浅析#2:安全检查

今天来看下类加载过程中的一些安全检查.在那之前得先来了解下Java的Access Control. Access Control The access control architecture in the Java platform protects access to sensitive resources (for example, local files) or sensitive application code (for example, methods in a class). A

并行加载与顺序执行

Javascript文件(下面简称脚本文件)需要被HTML文件引用才能在浏览器中运行.在HTML文件中可以通过不同的方式来引用脚本文件,我们需要关注的是,引用的具体实现方式和这些方式可能会带来的性能问题. 首先,引用脚本必须用到<script>标签,所以需要了解<script>标签的特性,引述书中作者原话: 当浏览器遇到(即内嵌)<script>标签时,当前浏览器无从获知Javascript是否会修改页面内容.因此,这时浏览器会停止处理页面,先执行Javascript代