JVM은 시작할 때 모듈과 클래스 로더의 바인딩을 미리 설정한다.
아래는 각 클래스로더의 대표 모듈이다.
부트스트랩 클래스로더 모듈
java.base, java.lang, java.util플랫폼 클래스로더 모듈
java.sql, java.xml, java.desktop애플리케이션 클래스로더 모듈
java.sql.Connection이 필요할 때 클래스로더가 어떻게 판단하는지 한 번 살펴보자
애플리케이션 클래스로더는 일단 java.sql.Connection이 이미 로드 되어 있는지 확인한다. 그리고 캐시가 되어 있지 않으면 플랫폼 클래스로더에 위임한다.
플래폼 클래스로더도 애플리케이션 클래스로더랑 똑같이 이미 로드 되어 있는지 확인하고, 로드 되어 있지 않으면 부트스트랩 클래스로더에 위임한다.
부트스트랩 클래스로더도 일단 캐시를 확인한다. 캐시를 확인했는데 없으면 자신에게 바인딩된 모듈 레이어에서 java.sql.Connection을 찾는다.
부트스트랩 클래스로더에서는 당연히 java.sql.Connection을 찾지 못했을 거니까 다시 플랫폼 클래스로더로 온다.
다시 왔을 때는 캐시를 확인하지 않고 바로 자신에게 바인딩된 모듈 레이어에서 java.sql.Connection을 찾는다.
플랫폼 클래스로더에는 java.sql이 바인딩 되어 있을거니까 로드를 하게 된다.
findLoadedClass
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
if문 부분에서 checkName을 호출해서 JVM이 관리하는 클래스 이름인지를 확인한다. (캐시를 조회할지 말지를 결정하는 부분)
아래 코드는 java.lang.ClassLoader와 JVM 사이를 잇는 JNI 브릿지 코드다.
JNI(Java Native Interface)는 자바 코드가 JVM 밖의 네이티브 코드(C/C++)를 호출하고, 반대로 네이티브 코드가 자바 객체를 다룰 수 있게 해주는 표준 인터페이스다.
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findLoadedClass0(JNIEnv *env, jobject loader,
jstring name)
{
if (name == NULL) {
return NULL;
} else {
return JVM_FindLoadedClass(env, loader, name);
}
}
null이면 조회를 할 이유가 없으니까 null인지 판단하고, null이면 null을 반환하고 아니면 JVM_FindLoadedClass를 호출한다.
JVM_FindLoadedClass
JVM_ENTRY(jclass, JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name))
ResourceMark rm(THREAD);
Handle h_name (THREAD, JNIHandles::resolve_non_null(name));
char* str = java_lang_String::as_utf8_string(h_name());
// Sanity check, don't expect null
if (str == nullptr) return nullptr;
// Internalize the string, converting '.' to '/' in string.
char* p = (char*)str;
while (*p != '\0') {
if (*p == '.') {
*p = '/';
}
p++;
}
const int str_len = (int)(p - str);
if (str_len > Symbol::max_length()) {
// It's impossible to create this class; the name cannot fit
// into the constant pool.
return nullptr;
}
TempNewSymbol klass_name = SymbolTable::new_symbol(str, str_len);
// Security Note:
// The Java level wrapper will perform the necessary security check allowing
// us to pass the null as the initiating class loader.
Handle h_loader(THREAD, JNIHandles::resolve(loader));
Klass* k = SystemDictionary::find_instance_or_array_klass(THREAD, klass_name, h_loader);
#if INCLUDE_CDS
if (k == nullptr) {
// If the class is not already loaded, try to see if it's in the shared
// archive for the current classloader (h_loader).
k = SystemDictionaryShared::find_or_load_shared_class(klass_name, h_loader, CHECK_NULL);
}
#endif
return (k == nullptr) ? nullptr :
(jclass) JNIHandles::make_local(THREAD, k->java_mirror());
JVM_END
이 코드의 핵심만 설명하면 Java 문자열을 JVM 내부 형식(java/lang/String)으로 변환하고 클래스 이름을 Symbol로 만들어 (Symbol + ClassLoader) 조합을 키로 해서SystemDictionary(로딩한 클래스를 관리하는 저장소)에 질의한다.
if 이미 로딩되어 있으면
SystemDictionary에서 Klass*(JVM이 실제로 사용하는 클래스 자체를 표현하는 내부 메타데이터 포인터)를 찾고 그에 맞는 java.lang.Class 객체를 반환한다.
else 로딩이 안 되어 있으면
null을 반환한다.
클래스로더는 해당 클래스가 자신에게 바인딩된 모듈에 속하는지를 기준으로 로드한다.