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-11-08 19:20:43

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

requirejs:性能优化-及早并行加载

为了提高页面的性能,通常情况下,我们希望资源尽可能地早地并行加载.这里有两个要点,首先是尽早,其次是并行. 通过data-main方式加载要尽可能地避免,因为它让requirejs.业务代码不必要地串行起来.下面就讲下如何尽可能地利用浏览器并行加载的能力来提高性能. 低效串行:想爱但却无力 最简单的优化,下面的例子中,通过两个并排的script标签加载require.js.main.js,这就达到了require.js.main.js并行加载的目的. 但这会有个问题,假设main.js依赖了jq

js并行加载,顺序执行

<script>运行脚本或加载外部文件时,会阻塞页面渲染,阻塞其他资源的加载.如果页面中需要加载多个js文件,在古老浏览器中性能会比较糟糕. 因此有了最原始的优化原则:把脚本放在底部. 如何实现js非阻塞.并行加载,甚至能保持执行顺序呢?各浏览器表现如何?站在巨人的肩膀上,Kyle Simpson.Nicholas C. Zakas和Steve Souders对此有过总结和方案. 背景 1. Script DOM Element. 动态插入<script>,不会阻塞,但无法保持执行

并行加载实施中遇到的问题

背景   最近着手在一个已有的应用中实施并行加载技术,整理记录一下过程中遇到的问题,方便以后查阅.   关于并行加载可以访问:  (业务层)异步并行加载技术分析和设计   姊妹篇: (业务层)异步并行加载ChangeLog   问题集 1. ThreadLocal不支持  原理分析: 因为并行加载,导致原先的代码快使用了新的独立的线程进行加载.导致原先代码中使用了ThreadLocal失效. 应用分析: 使用的ThreadLocal有几处地方     *  request/response. 需

深入并行:从并行加载到12c Adaptive特性深度理解Oracle并行

陈焕生 Oracle Real-World Performance Group 成员,senior performance engineer,专注于 OLTP.OLAP 系统 在 Exadata 平台和 In-Memory 特性上的最佳实践.个人博客 http://dbsid.com . 编辑手记:感谢陈焕生的精品文章,这篇文章首发在ACOUG,在此转载分享给大家,Sidney撰写这个系列的文章时间跨度也有两年,下篇刚刚出炉. 上篇分为两篇文章: 深入并行:从生产者到消费者模型深度理解Oracl

js加载js文件并行加载与顺序执行

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

OpenJDK类加载实现浅析#1:整体流程

今天来粗略看下Java中的类加载是如何实现的.为啥标题用的是OpenJDK呢?看下官网文档的一段描述, The VM and Java SE class loading libraries share the responsibility for class loading. The VM performs constant pool resolution, linking and initialization for classes and interfaces. The loading ph

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

浅析JS异步加载进度条_javascript技巧

展现效果: 1) 当点击Load的时候,模拟执行异步加载. 浏览器被遮挡. 进度条出现. 实现思路: 1.当用户点击load button执行异步请求. 调用方法 出现加载条 2.怎么实现进度条呢? 1) 在document.body 新增一个div.覆盖浏览器. 设置背景会灰色. z-index = 999. 加载的时候让用户无法修改界面值 2) 在document.body 新增一个动态的div. 代码实现: Main.html: <!DOCTYPE html> <html>

浅析js预加载/延迟加载_javascript技巧

Pre loader 预加载一般有两种常用方式:xhr和动态插入节点的方式.动态插入节点是最为简单也最为广泛的一种异步加载方式,然后使用动态插入节点方法加载的文件都会 在加载后立即执行,javascript的执行一方面会占用浏览器js执行进程,另一方面也可能改变页面结构,而css 的执行更有可能让整个页面变化.xhr方式虽然不会执行脚本,但是由于同域的限制 Lazy loader方式在一些图片非常多的网站中非常有用,在浏览器可视区域外的图片不会被载入,直到用户将页面滚动到它们所在的位置才加载,这