JVM类加载-小探
类加载
- jvm把描述类的数据从class文件加载到内存,并对数据进行校验、转换解析及初始化,最终形成可以被jvm直接使用的java类型,这就是jvm的类加载机制。
- 类加载的生命周期
- Loading,加载
- Linking,连接
- Verification,验证
- Preparation,准备
- Resolution,解析
- Initialization,初始化
- Using,使用
- Unloading,卸载
如下图所示:
WechatIMG294.png其中,1-加载、2.1-验证、2.2-准备、3-初始化、5-卸载 这五个阶段是顺序确定的。类加载的过程必须按照这种顺序按部就班的开始(注意,是开始,而非进行或完成,因为这些阶段通常是互相交叉、混合式进行的,通常会在一个阶段的执行过程中调用、激活另一个阶段)。
JVM只对类进行初始化的情况进行了严格规定(当然,加载、验证、准备阶段必须在此之前开始),有且仅有这五个场景才会主动初始化,称为主动引用:
- 遇到new、get_static、put_static、invoke_static 4条字节码指令时
- 使用java.lang.reflect包的方法对类进行反射调用,如果类还没进行初始化,需要先触发其初始化动作
- 当初始化一个类时,如果发现其父类还没进行过初始化,则需要先触发其父类的初始化
- JVM启动时,需要指定一个主类(含main的类),jvm会先初始化主类
- 使用JDK1.7及以上的动态语言支持时,如果一个java.lang.invoke.MethodHandle实例最后的解析结果是REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,且这个方法句柄所对应的类没有进行过初始化,则需要先触发其初始化
HotSpot加载并解析class文件
先看个例子:
public class MyClassLoaderCase {
public static void main(String[] args) throws ClassNotFoundException {
Class clazz = MyClassLoaderCase.class.getClassLoader().loadClass("Test");
System.out.println(clazz);
}
}
这里使用当前类的同一个ClassLoader加载了一个同目录的Test类,打印出来的结果是:class Test
如果没有找到对应的类,就会报ClassNotFoundException
下面详细看下loadClass方法的实现:
/**
* Loads the class with the specified <a href="#name">binary name</a>. The
* default implementation of this method searches for classes in the
* following order:
*
* <ol>
*
* <li><p> Invoke {@link #findLoadedClass(String)} to check if the class
* has already been loaded. </p></li>
*
* <li><p> Invoke the {@link #loadClass(String) <tt>loadClass</tt>} method
* on the parent class loader. If the parent is <tt>null</tt> the class
* loader built-in to the virtual machine is used, instead. </p></li>
*
* <li><p> Invoke the {@link #findClass(String)} method to find the
* class. </p></li>
*
* </ol>
*
* <p> If the class was found using the above steps, and the
* <tt>resolve</tt> flag is true, this method will then invoke the {@link
* #resolveClass(Class)} method on the resulting <tt>Class</tt> object.
*
* <p> Subclasses of <tt>ClassLoader</tt> are encouraged to override {@link
* #findClass(String)}, rather than this method. </p>
*
* <p> Unless overridden, this method synchronizes on the result of
* {@link #getClassLoadingLock <tt>getClassLoadingLock</tt>} method
* during the entire class loading process.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @param resolve
* If <tt>true</tt> then resolve the class
*
* @return The resulting <tt>Class</tt> object
*
* @throws ClassNotFoundException
* If the class could not be found
*/
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;
}
}
关注注释,类加载的顺序如下:
- 先通过findLoadedClass判断是否已经加载,当然这个是在同一个ClassLoader中(或父ClassLoader)
- 调用父ClassLoader的loadClass方法来加载对应的类
- 如果没有父ClassLoader,则使用BootStrapClassLoader来加载
- 调用findClass来寻找对应的类(如果重写ClassLoader,建议只重写findClass方法,而不是整个loadClass方法)
经过以上步骤,类的resolve标识会变为true
开始研究代码,首先看到一个同步synchronized,以及其中的getClassLoadingLock:
// Maps class name to the corresponding lock object when the current
// class loader is parallel capable.
// Note: VM also uses this field to decide if the current class loader
// is parallel capable and the appropriate lock object for class loading.
private final ConcurrentHashMap<String, Object> parallelLockMap;
/**
* 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.
*
* @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;
}
这个同步器用于防止多个子类同时需要加载一个类,同时调用父类加载时产生冲突。
在这块互斥代码之中,开始进行类的加载操作,如下:
第一步,findLoadedClass
/**
* Returns the class with the given <a href="#name">binary name</a> if this
* loader has been recorded by the Java virtual machine as an initiating
* loader of a class with that <a href="#name">binary name</a>. Otherwise
* <tt>null</tt> is returned.
*
* @param name
* The <a href="#name">binary name</a> of the class
*
* @return The <tt>Class</tt> object, or <tt>null</tt> if the class has
* not been loaded
*
* @since 1.1
*/
protected final Class<?> findLoadedClass(String name) {
if (!checkName(name))
return null;
return findLoadedClass0(name);
}
private native final Class<?> findLoadedClass0(String name);
可以看到,这里最终还是调用到native方法,在ClassLoader.c文件中,实现如下:
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findLoadedClass0(JNIEnv *env, jobject loader,
jstring name)
{
if (name == NULL) {
return 0;
} else {
return JVM_FindLoadedClass(env, loader, name);
}
}
/* Find a loaded class cached by the VM */
JNIEXPORT jclass JNICALL
JVM_FindLoadedClass(JNIEnv *env, jobject loader, jstring name);
在jvm.h中,可以看到VM cache了已load的class,而findLoadedClass就是从jvm缓存的class中寻找。
第二步,parent.loadClass
如果存在parentClassLoader,则直接调用对应的loadClass方法(双亲委派)
如果不存在,则用BootStrapClassLoader执行load动作,如下:
/**
* Returns a class loaded by the bootstrap class loader;
* or return null if not found.
*/
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
// return null if not found
private native Class<?> findBootstrapClass(String name);
一样走到了native方法,如下:
/*
* Returns NULL if class not found.
*/
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_findBootstrapClass(JNIEnv *env, jobject loader,
jstring classname)
{
char *clname;
jclass cls = 0;
char buf[128];
if (classname == NULL) {
return 0;
}
clname = getUTF(env, classname, buf, sizeof(buf));
if (clname == NULL) {
JNU_ThrowOutOfMemoryError(env, NULL);
return NULL;
}
VerifyFixClassname(clname);
if (!VerifyClassname(clname, JNI_TRUE)) { /* expects slashed name */
goto done;
}
cls = JVM_FindClassFromBootLoader(env, clname);
done:
if (clname != buf) {
free(clname);
}
return cls;
}
第三步,调用findClass加载-关键
这个方法在ClassLoader.java中并未实现,而是在各个子类中实现。以URLClassLoader.java为例
/**
* Finds and loads the class with the specified name from the URL search
* path. Any URLs referring to JAR files are loaded and opened as needed
* until the class is found.
*
* @param name the name of the class
* @return the resulting class
* @exception ClassNotFoundException if the class could not be found,
* or if the loader is closed.
* @exception NullPointerException if {@code name} is {@code null}.
*/
protected Class<?> findClass(final String name)
throws ClassNotFoundException
{
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
可以看到,最终是走到了defineClass方法中,而URLClassLoader中的defineClass最终只不过是封装了ClassLoader.java中的defineClass:
/**
* Converts a {@link java.nio.ByteBuffer <tt>ByteBuffer</tt>}
* into an instance of class <tt>Class</tt>,
* with an optional <tt>ProtectionDomain</tt>. If the domain is
* <tt>null</tt>, then a default domain will be assigned to the class as
* specified in the documentation for {@link #defineClass(String, byte[],
* int, int)}. Before the class can be used it must be resolved.
*
* <p>The rules about the first class defined in a package determining the
* set of certificates for the package, and the restrictions on class names
* are identical to those specified in the documentation for {@link
* #defineClass(String, byte[], int, int, ProtectionDomain)}.
*
* <p> An invocation of this method of the form
* <i>cl</i><tt>.defineClass(</tt><i>name</i><tt>,</tt>
* <i>bBuffer</i><tt>,</tt> <i>pd</i><tt>)</tt> yields exactly the same
* result as the statements
*
*<p> <tt>
* ...<br>
* byte[] temp = new byte[bBuffer.{@link
* java.nio.ByteBuffer#remaining remaining}()];<br>
* bBuffer.{@link java.nio.ByteBuffer#get(byte[])
* get}(temp);<br>
* return {@link #defineClass(String, byte[], int, int, ProtectionDomain)
* cl.defineClass}(name, temp, 0,
* temp.length, pd);<br>
* </tt></p>
*
* @param name
* The expected <a href="#name">binary name</a>. of the class, or
* <tt>null</tt> if not known
*
* @param b
* The bytes that make up the class data. The bytes from positions
* <tt>b.position()</tt> through <tt>b.position() + b.limit() -1
* </tt> should have the format of a valid class file as defined by
* <cite>The Java™ Virtual Machine Specification</cite>.
*
* @param protectionDomain
* The ProtectionDomain of the class, or <tt>null</tt>.
*
* @return The <tt>Class</tt> object created from the data,
* and optional <tt>ProtectionDomain</tt>.
*
* @throws ClassFormatError
* If the data did not contain a valid class.
*
* @throws NoClassDefFoundError
* If <tt>name</tt> is not equal to the <a href="#name">binary
* name</a> of the class specified by <tt>b</tt>
*
* @throws SecurityException
* If an attempt is made to add this class to a package that
* contains classes that were signed by a different set of
* certificates than this class, or if <tt>name</tt> begins with
* "<tt>java.</tt>".
*
* @see #defineClass(String, byte[], int, int, ProtectionDomain)
*
* @since 1.5
*/
protected final Class<?> defineClass(String name, java.nio.ByteBuffer b,
ProtectionDomain protectionDomain)
throws ClassFormatError
{
int len = b.remaining();
// Use byte[] if not a direct ByteBufer:
if (!b.isDirect()) {
if (b.hasArray()) {
return defineClass(name, b.array(),
b.position() + b.arrayOffset(), len,
protectionDomain);
} else {
// no array, or read-only array
byte[] tb = new byte[len];
b.get(tb); // get bytes out of byte buffer.
return defineClass(name, tb, 0, len, protectionDomain);
}
}
protectionDomain = preDefineClass(name, protectionDomain);
String source = defineClassSourceLocation(protectionDomain);
Class<?> c = defineClass2(name, b, b.position(), len, protectionDomain, source);
postDefineClass(c, protectionDomain);
return c;
}
private native Class<?> defineClass0(String name, byte[] b, int off, int len,
ProtectionDomain pd);
private native Class<?> defineClass1(String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);
private native Class<?> defineClass2(String name, java.nio.ByteBuffer b,
int off, int len, ProtectionDomain pd,
String source);
可以看到,这个方法才是真正将字节码内容转换为Class对象的地方。
一步步来:
先看java中代码:preDefineClass & defineClassSourceLocation:
/* Determine protection domain, and check that:
- not define java.* class,
- signer of this class matches signers for the rest of the classes in
package.
*/
private ProtectionDomain preDefineClass(String name,
ProtectionDomain pd)
{
if (!checkName(name))
throw new NoClassDefFoundError("IllegalName: " + name);
// Note: Checking logic in java.lang.invoke.MemberName.checkForTypeAlias
// relies on the fact that spoofing is impossible if a class has a name
// of the form "java.*"
if ((name != null) && name.startsWith("java.")) {
throw new SecurityException
("Prohibited package name: " +
name.substring(0, name.lastIndexOf('.')));
}
if (pd == null) {
pd = defaultDomain;
}
if (name != null) checkCerts(name, pd.getCodeSource());
return pd;
}
private String defineClassSourceLocation(ProtectionDomain pd)
{
CodeSource cs = pd.getCodeSource();
String source = null;
if (cs != null && cs.getLocation() != null) {
source = cs.getLocation().toString();
}
return source;
}
// true if the name is null or has the potential to be a valid binary name
private boolean checkName(String name) {
if ((name == null) || (name.length() == 0))
return true;
if ((name.indexOf('/') != -1)
|| (!VM.allowArraySyntax() && (name.charAt(0) == '[')))
return false;
return true;
}
private void checkCerts(String name, CodeSource cs) {
int i = name.lastIndexOf('.');
String pname = (i == -1) ? "" : name.substring(0, i);
Certificate[] certs = null;
if (cs != null) {
certs = cs.getCertificates();
}
Certificate[] pcerts = null;
if (parallelLockMap == null) {
synchronized (this) {
pcerts = package2certs.get(pname);
if (pcerts == null) {
package2certs.put(pname, (certs == null? nocerts:certs));
}
}
} else {
pcerts = ((ConcurrentHashMap<String, Certificate[]>)package2certs).
putIfAbsent(pname, (certs == null? nocerts:certs));
}
if (pcerts != null && !compareCerts(pcerts, certs)) {
throw new SecurityException("class \""+ name +
"\"'s signer information does not match signer information of other classes in the same package");
}
}
......
private void postDefineClass(Class<?> c, ProtectionDomain pd)
{
if (pd.getCodeSource() != null) {
Certificate certs[] = pd.getCodeSource().getCertificates();
if (certs != null)
setSigners(c, certs);
}
}
可以看到,在调用native方法之前,这里做了一系列的校验,比如name、cert等。
而在调用native方法之后,又通过postDefineClass设置了class中的signers,也就是签名者,当然这里的setSigners底层也是走到了native方法,这里就不继续深挖了。
主要看defineClass2的实现:
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass2(JNIEnv *env,
jclass cls,
jobject loader,
jstring name,
jobject data,
jint offset,
jint length,
jobject pd,
jstring source)
{
jbyte *body;
char *utfName;
jclass result = 0;
char buf[128];
char* utfSource;
char sourceBuf[1024];
assert(data != NULL); // caller fails if data is null.
assert(length >= 0); // caller passes ByteBuffer.remaining() for length, so never neg.
// caller passes ByteBuffer.position() for offset, and capacity() >= position() + remaining()
assert((*env)->GetDirectBufferCapacity(env, data) >= (offset + length));
body = (*env)->GetDirectBufferAddress(env, data);
if (body == 0) {
JNU_ThrowNullPointerException(env, 0);
return 0;
}
body += offset;
if (name != NULL) {
utfName = getUTF(env, name, buf, sizeof(buf));
if (utfName == NULL) {
JNU_ThrowOutOfMemoryError(env, NULL);
return result;
}
VerifyFixClassname(utfName);
} else {
utfName = NULL;
}
if (source != NULL) {
utfSource = getUTF(env, source, sourceBuf, sizeof(sourceBuf));
if (utfSource == NULL) {
JNU_ThrowOutOfMemoryError(env, NULL);
goto free_utfName;
}
} else {
utfSource = NULL;
}
result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
if (utfSource && utfSource != sourceBuf)
free(utfSource);
free_utfName:
if (utfName && utfName != buf)
free(utfName);
return result;
}
可以看到,这里最终走到了JVM_DefineClassWithSource,而最终实现则在这里jvm_define_class_common:
// common code for JVM_DefineClass() and JVM_DefineClassWithSource()
static jclass jvm_define_class_common(JNIEnv *env, const char *name,
jobject loader, const jbyte *buf,
jsize len, jobject pd, const char *source,
TRAPS) {
if (source == NULL) source = "__JVM_DefineClass__";
assert(THREAD->is_Java_thread(), "must be a JavaThread");
JavaThread* jt = (JavaThread*) THREAD;
PerfClassTraceTime vmtimer(ClassLoader::perf_define_appclass_time(),
ClassLoader::perf_define_appclass_selftime(),
ClassLoader::perf_define_appclasses(),
jt->get_thread_stat()->perf_recursion_counts_addr(),
jt->get_thread_stat()->perf_timers_addr(),
PerfClassTraceTime::DEFINE_CLASS);
if (UsePerfData) {
ClassLoader::perf_app_classfile_bytes_read()->inc(len);
}
// Since exceptions can be thrown, class initialization can take place
// if name is NULL no check for class name in .class stream has to be made.
TempNewSymbol class_name = NULL;
if (name != NULL) {
const int str_len = (int)strlen(name);
if (str_len > Symbol::max_length()) {
// It's impossible to create this class; the name cannot fit
// into the constant pool.
THROW_MSG_0(vmSymbols::java_lang_NoClassDefFoundError(), name);
}
class_name = SymbolTable::new_symbol(name, str_len, CHECK_NULL);
}
ResourceMark rm(THREAD);
ClassFileStream st((u1*)buf, len, source, ClassFileStream::verify);
Handle class_loader (THREAD, JNIHandles::resolve(loader));
if (UsePerfData) {
is_lock_held_by_thread(class_loader,
ClassLoader::sync_JVMDefineClassLockFreeCounter(),
THREAD);
}
Handle protection_domain (THREAD, JNIHandles::resolve(pd));
Klass* k = SystemDictionary::resolve_from_stream(class_name,
class_loader,
protection_domain,
&st,
CHECK_NULL);
if (log_is_enabled(Debug, class, resolve) && k != NULL) {
trace_class_resolution(k);
}
return (jclass) JNIHandles::make_local(env, k->java_mirror());
}
这里会判断类名的长度(包含路径),如果大于系统最大值max_symbol_length = (1 << 16) -1,会直接报错。
当然,最核心的处理在resolve_from_stream中,具体实现如下
// Add a klass to the system from a stream (called by jni_DefineClass and
// JVM_DefineClass).
// Note: class_name can be NULL. In that case we do not know the name of
// the class until we have parsed the stream.
Klass* SystemDictionary::resolve_from_stream(Symbol* class_name,
Handle class_loader,
Handle protection_domain,
ClassFileStream* st,
TRAPS) {
// 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;
}
ClassLoaderData* loader_data = register_loader(class_loader, CHECK_NULL);
// 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);
assert(st != NULL, "invariant");
// Parse the stream and create a klass.
// Note that we do this even though this klass might
// already be present in the SystemDictionary, otherwise we would not
// throw potential ClassFormatErrors.
//
instanceKlassHandle k;
#if INCLUDE_CDS
k = SystemDictionaryShared::lookup_from_stream(class_name,
class_loader,
protection_domain,
st,
CHECK_NULL);
#endif
if (k.is_null()) {
if (st->buffer() == NULL) {
return NULL;
}
k = KlassFactory::create_from_stream(st,
class_name,
loader_data,
protection_domain,
NULL, // host_klass
NULL, // cp_patches
CHECK_NULL);
}
assert(k.not_null(), "no klass created");
Symbol* h_name = k->name();
assert(class_name == NULL || class_name == h_name, "name mismatch");
// 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)) {
instanceKlassHandle defined_k = find_or_define_instance_class(h_name, class_loader, k, THREAD);
if (!HAS_PENDING_EXCEPTION && defined_k() != k()) {
// If a parallel capable class loader already defined this class, register 'k' for cleanup.
assert(defined_k.not_null(), "Should have a klass if there's no exception");
loader_data->add_to_deallocate_list(k());
k = defined_k;
}
} else {
define_instance_class(k, THREAD);
}
// If defining the class throws an exception register 'k' for cleanup.
if (HAS_PENDING_EXCEPTION) {
assert(k.not_null(), "Must have an instance klass here!");
loader_data->add_to_deallocate_list(k());
return NULL;
}
// Make sure we have an entry in the SystemDictionary on success
debug_only( {
MutexLocker mu(SystemDictionary_lock, THREAD);
Klass* check = find_class(h_name, k->class_loader_data());
assert(check == k(), "should be present in the dictionary");
} );
return k();
}
尼玛。。。今天实在看不下去了,再深挖会死,回头待续,都在create_from_stream里面
// TODO
第四步,resolveClass-可选操作
如果reslove是true,那么会走到resloveClass方法中。不过看代码中在调用的时候大都是false。
......
if (resolve) {
resolveClass(c);
}
......
/**
* Links the specified class. This (misleadingly named) method may be
* used by a class loader to link a class. If the class <tt>c</tt> has
* already been linked, then this method simply returns. Otherwise, the
* class is linked as described in the "Execution" chapter of
* <cite>The Java™ Language Specification</cite>.
*
* @param c
* The class to link
*
* @throws NullPointerException
* If <tt>c</tt> is <tt>null</tt>.
*
* @see #defineClass(String, byte[], int, int)
*/
protected final void resolveClass(Class<?> c) {
resolveClass0(c);
}
private native void resolveClass0(Class<?> c);