今天来看下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);