第一次认识Class.forName是在大学时学习使用jdbc连接数据库,当时不知道这个句话什么意思,只是简单的给背下来。
Java层面
先对Class.forName有一个大概的认识:
方法签名:
public static Class<?> forName(String className){...}
入参为String类型,返回值为一个Class对象。
/**
* Returns the {@code Class} object associated with the class or
* interface with the given string name. Invoking this method is
* equivalent to:
*
* <blockquote>
* {@code Class.forName(className, true, currentLoader)}
* </blockquote>
*
* where {@code currentLoader} denotes the defining class loader of
* the current class.
*/
和我们猜的一样,该方法返回的为给定className所关联的Class对象。
那么...
什么是Class对象?
由于Class只有一个私有的构造方法,很明显,我们无法直接创建其实例对象。
java.lang.Class类的注释很长,我们这里只看其中的一段话:
/**
* <p> {@code Class} has no public constructor. Instead a {@code Class}
* object is constructed automatically by the Java Virtual Machine
* when a class loader invokes one of the
* {@link ClassLoader#defineClass(String,byte[], int,int) defineClass} methods
* and passes the bytes of a {@code class} file.
*/
当类加载器中的ClassLoader#defineClass方法被调用时,虚拟机会自动构造对应的Class对象。
类加载过程
简单复习一下一个类的加载过程
根据java虚拟机规范(docs.oracle.com/javase/spec…),一个类加载到内存并供我们使用分为以下三个阶段:
- Loading 根据一个类的全限定名获取其二进制二进制字节流,并创建java.lang.Class对象。
- Linking 将类组合为运行时状态,包括为变量赋默认值,解析符号引用,和验证二进制格式等。
- Initializing 调用初始化方法<clinit>
而对应的ClassLoader#defineClass正式位于类加载的第一个过程,Loading中。
类与对象关系
让我们想象这样一个极简化的Java虚拟机运行时状态:方法区中只加载了两个类,java.lang.Object和java.lang.Class;堆中只通过new指令分配了一个对象Object。
如图所示,在方法区中,class1和class2分别是java.lang.Object和java.lang.Class类的数据。而堆中object1和object2分别代表java.lang.Object和java.lang.Class的类对象,Object3是单独的Object实例。
其中,每一个实例对象都位于有一个指向方法区中自己class类型的指针。同时,因为反射的需要,每一个方法区中的类数据也维护了一个指向堆中对应Class实例的指针(图中的JClass字段)。
Native层面
Class.forName中的主要逻辑都位于native方法forName0
中,我们结合openJdk的源码,看一下其中都做了什么。
以openJdk12为例,对应jni实现位于src/java.base/share/native/libjava/Class.c
中。
JNIEXPORT jclass JNICALL
Java_java_lang_Class_forName0(JNIEnv *env, jclass this, jstring classname,
jboolean initialize, jobject loader, jclass caller)
可以看出,forName
的主要实现逻辑位于JVM_FindClassFromCaller
方法中。
/*
* Find a class from a given class loader. Throws ClassNotFoundException.
* name: name of class
* init: whether initialization is done
* loader: class loader to look up the class. This may not be the same as the caller's
* class loader.
* caller: initiating class. The initiating class may be null when a security
* manager is not installed.
*/
JNIEXPORT jclass JNICALL
JVM_FindClassFromCaller(JNIEnv *env, const char *name, jboolean init,
jobject loader, jclass caller);
其功能由函数名就可以看出:在指定的classLoader中查找对应的class。该方法的实现位于src/hotpot/share/prims/jvm.cpp
中。
// Find a class with this name in this loader, using the caller's protection domain.
JVM_ENTRY(jclass, JVM_FindClassFromCaller(JNIEnv* env, const char* name,
jboolean init, jobject loader,
jclass caller))
JVMWrapper("JVM_FindClassFromCaller throws ClassNotFoundException");
// Java libraries should ensure that name is never null...
if (name == NULL || (int)strlen(name) > Symbol::max_length()) {
// It's impossible to create this class; the name cannot fit
// into the constant pool.
THROW_MSG_0(vmSymbols::java_lang_ClassNotFoundException(), name);
}
TempNewSymbol h_name = SymbolTable::new_symbol(name, CHECK_NULL);
oop loader_oop = JNIHandles::resolve(loader);
oop from_class = JNIHandles::resolve(caller);
oop protection_domain = NULL;
// If loader is null, shouldn't call ClassLoader.checkPackageAccess; otherwise get
// NPE. Put it in another way, the bootstrap class loader has all permission and
// thus no checkPackageAccess equivalence in the VM class loader.
// The caller is also passed as NULL by the java code if there is no security
// manager to avoid the performance cost of getting the calling class.
if (from_class != NULL && loader_oop != NULL) {
protection_domain = java_lang_Class::as_Klass(from_class)->protection_domain();
}
Handle h_loader(THREAD, loader_oop);
Handle h_prot(THREAD, protection_domain);
jclass result = find_class_from_class_loader(env, h_name, init, h_loader,
h_prot, false, THREAD);
if (log_is_enabled(Debug, class, resolve) && result != NULL) {
trace_class_resolution(java_lang_Class::as_Klass(JNIHandles::resolve_non_null(result)));
}
return result;
JVM_END
这个函数代码量不算多,我们将按该函数的调用顺序分析其过程:
SymbolTable
该类位于src/hotspot/share/classfile/symbolTable.cpp
中,他其实是对哈希表的一个实现。
symbolTable
中没有对应的数据,则调用do_add_if_needed
函数将其加入哈希表。
find_class_from_class_loader
在将当前类加入到SymbolTable中后,调用find_class_from_class_loader
函数,在指定的classLoader中查找该类。
jclass find_class_from_class_loader(JNIEnv* env, Symbol* name, jboolean init,
Handle loader, Handle protection_domain,
jboolean throwError, TRAPS) {
// Security Note:
// The Java level wrapper will perform the necessary security check allowing
// us to pass the NULL as the initiating class loader. The VM is responsible for
// the checkPackageAccess relative to the initiating class loader via the
// protection_domain. The protection_domain is passed as NULL by the java code
// if there is no security manager in 3-arg Class.forName().
Klass* klass = SystemDictionary::resolve_or_fail(name, loader, protection_domain, throwError != 0, CHECK_NULL);
// Check if we should initialize the class
if (init && klass->is_instance_klass()) {
klass->initialize(CHECK_NULL);
}
return (jclass) JNIHandles::make_local(env, klass->java_mirror());
}
SystemDictionary::resolve_or_fail
根据类名&类加载器去查找class对象。
在该方法中,直接封装了SystemDictionary::resolve_or_null
方法。
SystemDictionary::resolve_or_null
负责转发,分别将数组对象和普通对象转至resolve_array_class_or_null
方法和resolve_instance_class_or_null_helper
方法。
// Forwards to resolve_array_class_or_null or resolve_instance_class_or_null
Klass* SystemDictionary::resolve_or_null(Symbol* class_name, Handle class_loader, Handle protection_domain, TRAPS) {
if (FieldType::is_array(class_name)) {
return resolve_array_class_or_null(class_name, class_loader, protection_domain, THREAD);
} else {
return resolve_instance_class_or_null_helper(class_name, class_loader, protection_domain, THREAD);
}
}
SystemDictionary::resolve_instance_class_or_null_helper
如果*class_name是L
开头,;
结尾的字符串的话,需要将其开头,结尾截取掉。
// name may be in the form of "java/lang/Object" or "Ljava/lang/Object;"
InstanceKlass* SystemDictionary::resolve_instance_class_or_null_helper(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
TRAPS) {
assert(class_name != NULL && !FieldType::is_array(class_name), "must be");
if (FieldType::is_obj(class_name)) {
ResourceMark rm(THREAD);
// Ignore wrapping L and ;.
TempNewSymbol name = SymbolTable::new_symbol(class_name->as_C_string() + 1,
class_name->utf8_length() - 2, CHECK_NULL);
return resolve_instance_class_or_null(name, class_loader, protection_domain, THREAD);
} else {
return resolve_instance_class_or_null(class_name, class_loader, protection_domain, THREAD);
}
}
SystemDictionary::resolve_instance_class_or_null
这个方法略长...完整代码我就不在文章里面贴了,这里只分析下关键过程,感兴趣的可以自己看下src/hotspot/share/classfile/systemDictionary.cpp
- 首先,判断一下该类是否已经加载过(是否已存在对应的class实例对象)。
// Do lookup to see if class already exist and the protection domain
// has the right access
// This call uses find which checks protection domain already matches
// All subsequent calls use find_class, and set has_loaded_class so that
// before we return a result we call out to java to check for valid protection domain
// to allow returning the Klass* and add it to the pd_set if it is valid
{
InstanceKlass* probe = dictionary->find(d_hash, name, protection_domain);
if (probe != NULL) return probe;
}
- 将要加载的类名hash加入placeholders,防止重复加载。
unsigned int p_hash = placeholders()->compute_hash(name);
int p_index = placeholders()->hash_to_index(p_hash);
- 获取对象锁。
// Class is not in SystemDictionary so we have to do loading.
// 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);
- 再次确认类是否已加载,或正在被加载中(通过placeholder)。
{
MutexLocker mu(SystemDictionary_lock, THREAD);
InstanceKlass* check = find_class(d_hash, name, dictionary);
if (check != NULL) {
// InstanceKlass is already loaded, so just return it
class_has_been_loaded = true;
k = check;
} else {
placeholder = placeholders()->get_entry(p_index, p_hash, name, loader_data);
if (placeholder && placeholder->super_load_in_progress()) {
super_load_in_progress = true;
if (placeholder->havesupername() == true) {
superclassname = placeholder->supername();
havesupername = true;
}
}
}
}
- 真正的加载类。
k = load_instance_class(name, class_loader, THREAD);
- 加载完成后移出placeholder
if (load_instance_added == true) {
// clean up placeholder entries for LOAD_INSTANCE success or error
// This brackets the SystemDictionary updates for both defining
// and initiating loaders
MutexLocker mu(SystemDictionary_lock, THREAD);
placeholders()->find_and_remove(p_index, p_hash, name, loader_data, PlaceholderTable::LOAD_INSTANCE, THREAD);
SystemDictionary_lock->notify_all();
}
- 发布event通知
if (class_load_start_event.should_commit()) {
post_class_load_event(&class_load_start_event, k, loader_data);
}
SystemDictionary::load_instance_class
该方法内有两个逻辑分支,如下:
if (class_loader.is_null()) {
//...
}else {
// Use user specified class loader to load class. Call loadClass operation on class_loader.
//...
}
-
传入的class_loader为空
被加载的类为系统类,使用启动类加载器去加载:
// Called by the boot classloader to load classes
InstanceKlass* ClassLoader::load_class(Symbol* name, bool search_append_only, TRAPS) {
}
最后,通过读取到的字节流去创建InstanceKlass
。
InstanceKlass* KlassFactory::create_from_stream(ClassFileStream* stream,
Symbol* name,
ClassLoaderData* loader_data,
Handle protection_domain,
const InstanceKlass* unsafe_anonymous_host,
GrowableArray<Handle>* cp_patches,
TRAPS) {
- 传入的class_loader不为空 使用用户定义的类加载器去加载。(在jvm层面,类加载器只分为boot classLoader和user defined classLoader。平常说的extClassLoader也归到了user defined classLoader里面)。
// Call public unsynchronized loadClass(String) directly for all class loaders.
// For parallelCapable class loaders, JDK >=7, loadClass(String, boolean) will
// acquire a class-name based lock rather than the class loader object lock.
// JDK < 7 already acquire the class loader lock in loadClass(String, boolean).
JavaCalls::call_virtual(&result,
class_loader,
spec_klass,
vmSymbols::loadClass_name(),
vmSymbols::string_class_signature(),
string,
CHECK_NULL);
这里,其实就是调用的java中的ClassLoader.loadClass()
方法。
总结
至此,我们已经看完了Class.forName中的主要流程。
这里只是很初略的跟了一下调用过程,其中涉及的权限检查,数组类加载,异常处理等都直接略过了。
我们先回顾一下最后的调用栈:
其实,我们可以认为,当jvm中尚未加载一个类的时候,Class.forName
可以促使JVM去去装载指定类。这也就是为什么在jdbc连接数据库的时候,有时不使用Class.forName
也一样可以成功。
参考资料
openJdk12源码
《自己动手写java虚拟机》
《深入理解java虚拟机》