何时加载类
- 遇到new、getstatic、putstatic等指令时
- 对类进行反射调用的时候
- 初始化某个类的子类的时候
- 虚拟机启动时会先加载设置的程序主类
- 使用JDK1.7 的动态语言支持的时候
其实就是,当运行过程中需要这个类的时候。
怎么加载类
利用ClassLoader加载类很简单,直接调用ClassLoder的loadClass()方法即可1
2
3
4
5public class Test {
public static void main(String[] args) throws ClassNotFoundException {
Test.class.getClassLoader().loadClass("com.wangxiandeng.test.Dog");
}
}
JVM 是怎么加载类的
JVM默认用于加载用户程序的ClassLoader为AppClassLoader,不过无论是什么ClassLoader,它的根父类都是java.lang.ClassLoader。最终会调用到ClassLoader.definClass1(),这是一个native方法。
Java_java_lang_ClassLoader_defineClass1()
-> JVM_DefineClassWithSource()
-> jvm_define_class_common() // 利用ClassFileStream将要加载的class文件转成文件流
-> SystemDictionary::resolve_from_stream() // 将Class文件加载成内存中的Klass
resolve_from_stream()便是重中之重!主要逻辑有下面几步:
判断是否允许并行加载类,并根据判断结果进行加锁
1 | bool DoObjectLock = true; |
如果允许并行加载,则不会对ClassLoader进行加锁,只对SystemDictionary加锁。否则,便会利用ObjectLocker对ClassLoader加锁,保证同一个ClassLoader在同一时刻只能加载一个类。ObjectLocker会在其构造函数中获取锁,并在析构函数中释放锁。
允许并行加载的好处便是精细化了锁粒度,这样可以在同一时刻加载多个Class文件。
解析文件流,生成InstanceKlass
1 | InstanceKlass* k = NULL; |
Klass就是JVM用来定义一个Java Class的数据结构。不过Klass只是一个基类,Java Class真正的数据结构定义在InstanceKlass中。InstanceKlass中记录了一个Java类的所有属性,包括注解、方法、字段、内部类、常量池等信息。这些信息本来被记录在Class文件中,所以说,InstanceKlass就是一个Java Class文件被加载到内存后的形式。
生成InstanceKlass调用的是KlassFactory::create_from_stream(),它的主要逻辑如下:1
2
3
4
5
6
7
8
9
10ClassFileParser 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才是真正的主角啊!它才是将Class文件升华成InstanceKlass的幕后大佬!
create_instance_klass()主要就干了两件事:
为 InstanceKlass分配内存
内存分配代码如下:1
2
3
4
5
6
7
8
9const 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
3void* 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
9MetaWord* 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,所以当内存不够时会内存溢出。
分析Class文件,填充InstanceKlass内存区域
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
15void 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);
}
到这儿,Class文件已经完成了华丽的转身,由冷冰冰的二进制文件,变成了内存中充满生命力的InstanceKlass。
利用SystemDictionary注册生成的Klass
先看一下注册的代码:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15// 允许并行加载,那么前面就不会对ClassLoader加锁
// 所以在同一时刻,可能对同一Class文件加载了多次
// 但是同一Class在同一ClassLoader中必须保持唯一性,所以这里会先利用SystemDictionary查询ClassLoader是否已经加载过相同Class
if (is_parallelCapable(class_loader)) {
// 如果没有查询到,那么就将刚刚加载的InstanceKlass注册到ClassLoader的Dictionary中
InstanceKlass* defined_k = find_or_define_instance_class(h_name, class_loader, k, THREAD);
if (!HAS_PENDING_EXCEPTION && defined_k != k) {
// 如果已经加载过,那么就将当前线程刚刚加载的InstanceKlass加入待回收列表,并将InstanceKlass* k重新指向利用SystemDictionary查询到的InstanceKlass
loader_data->add_to_deallocate_list(k);
k = defined_k;
}
} else {
// 如果禁止了并行加载,那么直接利用SystemDictionary将 nstanceKlass注册到ClassLoader的Dictionary中即可
define_instance_class(k, THREAD);
}
SystemDictionary是用来帮助保存ClassLoader加载过的类信息的。准确点说,SystemDictionary并不是一个容器,真正用来保存类信息的容器是Dictionary,每个ClassLoaderData中都保存着一个私有的Dictionary,而SystemDictionary只是一个拥有很多静态方法的工具类而已。
双亲委派模型
我们知道, 双亲委派模型中,ClassLoader在加载类的时候,会先交由它的父ClassLoader加载,只有当父ClassLoader加载失败的情况下,才会尝试自己去加载。这样可以实现部分类的复用,又可以实现部分类的隔离,因为不同ClassLoader加载的类是互相隔离的。
但是在看完上面的分析后,你一定对 “不同ClassLoader加载的类是互相隔离的” 这句话的理解又上了一个台阶。
可以总结一下,即每个ClassLoader都有一个Dictionary用来保存它所加载的InstanceKlass信息。并且,每个ClassLoader通过锁,保证了对于同一个Class,它只会注册一份InstanceKlass到自己的 ictionary。
由于上面这些原因,如果所有的ClassLoader都由自己去加载Class文件,就会导致对于同一个Class文件,存在多份InstanceKlass,所以即使是同一个Class文件,不同InstanceKlasss衍生出来的实例类型也是不一样的。
文章大篇幅摘抄自: