何时加载类

  1. 遇到 new、getstatic、putstatic 等指令时。
  2. 对类进行反射调用的时候。
  3. 初始化某个类的子类的时候。
  4. 虚拟机启动时会先加载设置的程序主类。
  5. 使用 dynamic 动态语言支持等相关特性时。

从 Java 到 cpp 源码分析

JVM
默认用于加载用户程序的ClassLoader为AppClassLoader,不过无论是什么ClassLoader,它的根父类都是java.lang.ClassLoader。在上面那个例子中,loadClass()方法最终会调用到ClassLoader.definClass1()中,这是一个
Native 方法。

1
2
static native Class<?> defineClass1(ClassLoader loader, String name, byte[] b, int off, int len,
ProtectionDomain pd, String source);

definClass1()对应的 JNI 方法为 Java_java_lang_ClassLoader_defineClass1()。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
JNIEXPORT jclass JNICALL
Java_java_lang_ClassLoader_defineClass1(JNIEnv *env,
jclass cls,
jobject loader,
jstring name,
jbyteArray data,
jint offset,
jint length,
jobject pd,
jstring source)
{
......
result = JVM_DefineClassWithSource(env, utfName, loader, body, length, pd, utfSource);
......
return result;
}

Java_java_lang_ClassLoader_defineClass1
主要是调用了JVM_DefineClassWithSource()加载类,跟着源码往下走,会发现最终调用的是 jvm.cpp 中的
jvm_define_class_common()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
static jclass jvm_define_class_common(JNIEnv *env, const char *name,
jobject loader, const jbyte *buf,
jsize len, jobject pd, const char *source,
TRAPS) {
......
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);
......

return (jclass) JNIHandles::make_local(env, k->java_mirror());
}

其上的步骤细分下来共三步:

  1. 将 class 文件转化为字节流。
  2. 求当前线程是否持有锁,并且显式地进入 protection_domain。
  3. 将字节流转化成 Klass 的实例(class 文件在 JVM 中的内存代表),注册进 SystemDictionary 里。

Klass 是 JVM 用来定义 Java Class 的数据结构。不过 Klass 只是一个基类,Java Class 真正的数据结构定义在 InstanceKlass 中。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class InstanceKlass: public Klass {

protected:

Annotations* _annotations;
......
ConstantPool* _constants;
......
Array<jushort>* _inner_classes;
......
Array<Method*>* _methods;
Array<Method*>* _default_methods;
......
Array<u2>* _fields;
}

这个类型定义了 Java 类的所有属性,包括注解、常量、内部类、方法、内部方法、字段等信息。这些信息本来被记录在 Class 文件中,,InstanceKlass 是 它的内存形式。

可以和 Class 文件的结构图对比看。

此处输入图片的描述

可以看到,在 class 文件里的 constant pool,只能映射到 InstanceKlass 里的 constant 上。

resolve_from_stream 详解

判断是否允许并行加载类,并根据判断结果进行加锁

1
2
3
4
5
6
7
8
bool DoObjectLock = true;
if (is_parallelCapable(class_loader)) {
DoObjectLock = false;
}
ClassLoaderData* loader_data = register_loader(class_loader, CHECK_NULL);
Handle lockObject = compute_loader_lock_object(class_loader, THREAD);
check_loader_lock_contention(lockObject, THREAD);
ObjectLocker ol(lockObject, THREAD, DoObjectLock);

如果允许并行加载,则不会对ClassLoader进行加锁,只对SystemDictionary加锁。否则,便会利用 ObjectLocker 对ClassLoader 加锁,保证同一个ClassLoader在同一时刻只能加载一个类。ObjectLocker 会在其构造函数中获取锁,并在析构函数中释放锁。

允许并行加载的好处便是精细化了锁粒度,这样可以在同一时刻加载多个Class文件。

解析文件流,生成 InstanceKlass

1
2
3
4
5
6
7
8
9
InstanceKlass* k = NULL;

k = KlassFactory::create_from_stream(st,
class_name,
loader_data,
protection_domain,
NULL, // host_klass
NULL, // cp_patches
CHECK_NULL);

利用SystemDictionary注册生成的 Klass

SystemDictionary 是用来帮助保存 ClassLoader 加载过的类信息的。准确点说,SystemDiction
并不是一个容器,真正用来保存类信息的容器是 Dictionary,每个 ClassLoaderData 都保存着一个私有的
Dictionary,而 SystemDictionary 只是一个拥有很多静态方法的工具类而已。

注册的代码:

1
2
3
4
5
6
7
8
9
10
11
if (is_parallelCapable(class_loader)) {
InstanceKlass* 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 != 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);
}

如果允许并行加载,那么前面就不会对 ClassLoader 加锁,所以在同一时刻,可能对同一 Class 文件加载多次-但同一个 Class 必须在同一 ClassLoader 里保持唯一,所以先利用 SystemDictionary 查询 ClassLoader 是否已经加载过相同 Class。

  • 如果已经加载过,那么就将刚刚加载的 InstanceKlass 加入待回收列表,并将 InstanceKlass k 重新指向利用 SystemDictionary 查询到的 InstanceKlass。(*允许重复加载,弃新存旧
  • 如果没有查询到,那么就将刚刚加载的 InstanceKlass 注册到 ClassLoader的 Dictionary 中 中。

如果禁止了并行加载,那么直接利用SystemDictionary将 InstanceKlass 注册到 ClassLoader的 Dictionary 中即可。此时由锁保证数据唯一性。

ClassFileParser

resolve_from_stream()最重要的是第二步,从文件流生成InstanceKlass,这依赖于调用 KlassFactory::create_from_stream()方法:

1
2
3
4
5
6
7
8
9
10
ClassFileParser parser(stream,
name,
loader_data,
protection_domain,
host_klass,
cp_patches,
ClassFileParser::BROADCAST, // publicity level
CHECK_NULL);

InstanceKlass* result = parser.create_instance_klass(old_stream != stream, CHECK_NULL);

这又依赖于 ClassFileParser。

ClassFileParser 加载Class文件的入口便是 create_instance_klass()。顾名思义,用来创建InstanceKlass的。

create_instance_klass()主要就干了两件事:

  1. 为 InstanceKlass 分配内存:
1
2
InstanceKlass* const ik =
InstanceKlass::allocate_instance_klass(*this, CHECK_NULL);

内存分配代码如下:

1
2
3
4
5
6
7
8
9
const int size = InstanceKlass::size(parser.vtable_size(),
parser.itable_size(),
nonstatic_oop_map_size(parser.total_oop_map_count()),
parser.is_interface(),
parser.is_anonymous(),
should_store_fingerprint(parser.is_anonymous()));
ClassLoaderData* loader_data = parser.loader_data();
InstanceKlass* ik;
ik = new (loader_data, size, THREAD) InstanceKlass(parser, InstanceKlass::_misc_kind_other);

这里首先计算了InstanceKlass在内存中的大小,要知道,这个大小在Class 文件编译后就被确定了。

然后便 new 了一个新的 InstanceKlass 对象。这里并不是简单的在堆上分配内存,要注意的是Klass 对 new 操作符进行了重载:

1
2
3
void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {
return Metaspace::allocate(loader_data, word_size, MetaspaceObj::ClassType, THREAD);
}

分配 InstanceKlass 的时候调用了 Metaspace::allocate():

1
2
3
4
5
6
7
8
9
MetaWord* Metaspace::allocate(ClassLoaderData* loader_data, size_t word_size,
MetaspaceObj::Type type, TRAPS) {
......
MetadataType mdtype = (type == MetaspaceObj::ClassType) ? ClassType : NonClassType;
......
MetaWord* result = loader_data->metaspace_non_null()->allocate(word_size, mdtype);
......
return result;
}

*由此可见,InstanceKlass 是分配在 ClassLoader的 Metaspace(元空间) 的方法区中。从 JDK8 开始,HotSpot 就没有了永久代,类都分配在 Metaspace 中。Metaspace 和永久代不一样,采用的是 Native Memory,永久代由于受限于 MaxPermSize,所以当内存不够时会内存溢出。

  1. 分析Class文件,填充 InstanceKlass 内存区域:
1
fill_instance_klass(ik, changed_by_loadhook, CHECK_NULL);

ClassFileParser 在构造的时候就会开始分析Class文件,所以fill_instance_klass()中只需要填充即可。填充结束后,还会调用 java_lang_Class::create_mirror()创建 InstanceKlass 在Java 层的 Class 对象。双层对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void ClassFileParser::fill_instance_klass(InstanceKlass* ik, bool changed_by_loadhook, TRAPS) {
.....
ik->set_class_loader_data(_loader_data);
ik->set_nonstatic_field_size(_field_info->nonstatic_field_size);
ik->set_has_nonstatic_fields(_field_info->has_nonstatic_fields);
ik->set_static_oop_field_count(_fac->count[STATIC_OOP]);
ik->set_name(_class_name);
......

java_lang_Class::create_mirror(ik,
Handle(THREAD, _loader_data->class_loader()),
module_handle,
_protection_domain,
CHECK);
}

至此,系统保证了“不同ClassLoader加载的类是互相隔离的”。其基本流程为:每一个 classloader 有一个私有的 Dictionary,在加载类的过程中,Dictionary 有锁机制保证 InstanceKlass 的唯一性。如果存在不同的 ClassLoader 加载同一个 Class 文件,就会在内存里保存多份 InstanceKlass。而不同的 InstanceKlass 之间是不能相互强制转换的。

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {

public static void main(String[] args) throws Exception {
URL url[] = new URL[1];
url[0] = Thread.currentThread().getContextClassLoader().getResource("");

CustomClassloader customClassloader = new CustomClassloader(url);
Class clazz = customClassloader.loadClass("a.b.c");

Student student = (Student) clazz.newInstance();
}
}

上述代码运行一定会出错。

1
2
Exception in thread "main" java.lang.ClassCastException:
a.b.c cannot be cast to a.b.c

那是因为目标 a.b.c 类型存在于当前类加载器中,而 clazz 这个Class实例则存在于 customClassloader 中,它的 clazz.newInstance() 的类型本身不能跨类加载器相互转化。

一种 hack 技巧

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class Test {

public static void main(String[] args) throws Exception {
URL url[] = new URL[1];
url[0] = Thread.currentThread().getContextClassLoader().getResource("");
final CustomClassloader customClassloader = new CustomClassloader(url);
Thread.currentThread().setContextClassLoader(customClassloader);
Class clazz = customClassloader.loadClass("a.b.c");
Object object = clazz.newInstance();
Method method = clazz.getDeclaredMethod("test");
method.invoke(object);
}
}

注意,这里面并不直接调用 c c = clazz.newInstance(),而是调用
Object object = clazz.newInstance(),并且必须使用反射才能调用 test。因为如果显式地使用了 C 这个类型,进行常量解析时,还是会跑到系统默认的 ClassLoader 加载的 C。