热部署实现
此段代码使用循环的方式,定时创建自己实现的类加载器,并用类加载器加载自己的class文件,并调用需要实现的方法,由此来实现热部署。
为什么能实现热部署功能
主要是因为每个新建的ClassLoader享有一份独有的元空间内存,被称为Metachunk。当一个类加载器加载类时,会在元空间分配一个chunk块,通常一个普通类加载器会分配一个4KB的chunck。因此每个类加载器都能加载一份属于自己的klass,这样就能保证每次读入的klass是最新的klass。
如果只用一个类加载器不停的加载同一个类是不行的。因为加载器有多次加载判断,会调用findclass0()
方法判断是否已经被本类加载器加载了,如果是就不会再次加载。
这些创建的类加载器会不会被gc回收?
实际上,对于每个ClassLoader,都会调用方法SystemDictionary::register_loader
将一个ClassLoader实例与一个ClassLoaderData关联,而ClassLoaderData创建在元空间中,因此会形成元空间的ClassLoaderData引用堆区中的ClassLoader实例,ygc是不会回收这些ClassLoader的。
ClassLoaderData* SystemDictionary::register_loader(Handle class_loader, TRAPS) {
if (class_loader() == NULL) return ClassLoaderData::the_null_class_loader_data();
return ClassLoaderDataGraph::find_or_create(class_loader, CHECK_NULL);
}
ClassLoaderData保存在ClassLoaderDataGraph结构中(可以认为是一个链表)。只有在执行fgc的时候才会对元空间进行gc,只有在这时候才会清理ClassLoaderData。此时会在调用的
SystemDictionary::do_unloading()
函数中调用ClassLoaderDataGraph::do_unloading()
函数找出失效的类加载器卸载。
什么情况下才会回收这些创建的类加载器
首先,在元空间无法分配内存的情况下,会触发metaspace的gc。
MetaWord* Metaspace::allocate(ClassLoaderData* loader_data, size_t word_size,
bool read_only, MetaspaceObj::Type type, TRAPS) {
......
// Try to allocate metadata.
MetaWord* result = loader_data->metaspace_non_null()->allocate(word_size, mdtype);
if (result == NULL) {
// 为元数据信息分配空间失败
tracer()->report_metaspace_allocation_failure(loader_data, word_size, type, mdtype);
// Allocation failed.
if (is_init_completed()) {
// 触发GC进行内存回收后再分配
// Only start a GC if the bootstrapping has completed.
// Try to clean out some memory and retry.
result = Universe::heap()->collector_policy()->satisfy_failed_metadata_allocation(
loader_data, word_size, mdtype);
}
}
......
}
触发gc逻辑很简单,往任务队列中丢一个gc任务,由gc线程负责执行其中的doit()
函数
// 触发Full GC回收那些已经无效的类加载器所占用的内存块
MetaWord* CollectorPolicy::satisfy_failed_metadata_allocation(
ClassLoaderData* loader_data,
size_t word_size,
Metaspace::MetadataType mdtype) {
do {
......
// Generate a VM operation
VM_CollectForMetadataAllocation op(loader_data,
word_size,
mdtype,
gc_count,
full_gc_count,
GCCause::_metadata_GC_threshold);
VMThread::execute(&op);
if (op.gc_locked()) {
continue;
}
if (op.prologue_succeeded()) {
return op.result();
}
......
} while (true);//直到gc被完成才会结束循环
}
可以看到,针对metaspace的垃圾回收都是fullgc
void CollectedHeap::collect_as_vm_thread(GCCause::Cause cause) {
assert(Thread::current()->is_VM_thread(), "Precondition#1");
assert(Heap_lock->is_locked(), "Precondition#2");
GCCauseSetter gcs(this, cause);
switch (cause) {
case GCCause::_heap_inspection:
case GCCause::_heap_dump:
case GCCause::_metadata_GC_threshold : {
HandleMark hm;
do_full_collection(false); // don't clear all soft refs
break;
}
case GCCause::_last_ditch_collection: {
HandleMark hm;
do_full_collection(true); // do clear all soft refs
break;
}
default:
ShouldNotReachHere(); // Unexpected use of this function
}
}
要卸载一个类加载器,要卸载很多相关的信息,例如:
// 卸载所有被标记为不再使用的Klass对象
bool purged_class = SystemDictionary::do_unloading(&is_alive);
// 卸载类中的nmethod
CodeCache::do_unloading(&is_alive, purged_class);
// Prune dead klasses from subklass/sibling/implementor lists.
Klass::clean_weak_klass_links(&is_alive);
// 卸载用不到的string对象
StringTable::unlink(&is_alive);
// Clean up unreferenced symbols in symbol table.
SymbolTable::unlink();
因此大概率可以认为,代码中循环创建的类加载器是很难被卸载的,因此最好考虑热部署关键类。