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

UnsyncloadClass AlwaysLockClassLoader 这些 Flag globals.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);

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值