目录
在《Hotspot 方法调用之StubRoutines 源码解析》中讲解BufferBlob的初始化时,BufferBlob是通过CodeCache::allocate方法分配内存的,如下图所示:
本篇博客就顺着这个方法的实现来研究负责管理生成的汇编代码缓存的CodeCache的相关实现类。
一、CodeCache
1、定义
CodeCache就是用于缓存不同类型的生成的汇编代码,如热点方法编译后的代码,各种运行时的调用入口Stub等,所有的汇编代码在CodeCache中都是以CodeBlob及其子类的形式存在的。通常CodeBlob会对应一个CodeBuffer,负责生成汇编代码的生成器会通过CodeBuffer将汇编代码写入到CodeBlob中,参考《Hotspot 方法调用之StubGenerator 源码解析》中MacroAssembler类的说明。CodeCache的定义位于hotspot src/share/vm/code/codeCache.hpp中,包含的属性和方法都是静态的,属性不多,如下图:
其中_heap属性是实际负责内存管理的, _number_of开头的几个属性是统计计数的,_scavenge_root_nmethods是为了GC时遍历nmethod使用。CodeCache定义的关键方法主要有以下几种:
- 内存分配和释放的,如allocate,commit,free等
- 查找或者遍历已经存在的Blob和nmethod,如find_blob,find_nmethod,blobs_do,nmethods_do,first,next等
- GC支持的,如gc_epilogue,gc_prologue,do_unloading,scavenge_root_nmethods_do等
- 获取CodeCache对应内存块属性的,如low_bound,high_bound,capacity等
- 逆优化相关的,如mark_for_deoptimization,mark_all_nmethods_for_deoptimization,make_marked_nmethods_not_entrant等
我们重点关注其初始化和内存分配相关方法的实现。
2、initialize
initialize方法用来初始化CodeCache的,在JVM进程启动时执行,初始化结束后整个运行期通过_heap属性管理的内存都不会被释放,从而保证已生成的被频繁访问的汇编代码一直常驻内存。该方法的调用链如下:
codeCache_init方法的实现如下:
initialize方法实现的源码说明如下:
void CodeCache::initialize() {
//校验参数配置是否正确,CodeCacheSegmentSize表示CodeCache对应内存的最小值,CodeEntryAlignment表示分配给一段汇编代码的最小内存空间
assert(CodeCacheSegmentSize >= (uintx)CodeEntryAlignment, "CodeCacheSegmentSize must be large enough to align entry points");
#ifdef COMPILER2
//OptoLoopAlignment表示给内部循环代码分配的最低内存大小
assert(CodeCacheSegmentSize >= (uintx)OptoLoopAlignment, "CodeCacheSegmentSize must be large enough to align inner loops");
#endif
assert(CodeCacheSegmentSize >= sizeof(jdouble), "CodeCacheSegmentSize must be large enough to align constants");
//按照系统的内存页大小对CodeCache的参数取整
//CodeCacheExpansionSize表示CodeCache扩展一次内存空间对应的内存大小,x86下默认值是2304k
CodeCacheExpansionSize = round_to(CodeCacheExpansionSize, os::vm_page_size());
//InitialCodeCacheSize表示CodeCache的初始大小,x86 启用C2编译下,默认值是2304k
InitialCodeCacheSize = round_to(InitialCodeCacheSize, os::vm_page_size());
//ReservedCodeCacheSize表示CodeCache的最大内存大小,x86 启用C2编译下,默认值是48M
ReservedCodeCacheSize = round_to(ReservedCodeCacheSize, os::vm_page_size());
//完成heap属性的初始化
if (!_heap->reserve(ReservedCodeCacheSize, InitialCodeCacheSize, CodeCacheSegmentSize)) {
vm_exit_during_initialization("Could not reserve enough space for code cache");
}
//将CodeHeap放入一个MemoryPool中管理起来
MemoryService::add_code_heap_memory_pool(_heap);
//初始化用于刷新CPU指令缓存的Icache,即生成一段用于刷新指令缓存的汇编代码,此时因为heap属性已初始化完成,所以可以从CodeCache中分配Blob了
icache_init();
//通知操作系统我们的CodeCache的内存区域,主要是win64使用
os::register_code_area(_heap->low_boundary(), _heap->high_boundary());
}
3、allocate / commit / free
allocate方法用于分配CodeBlob的,当CodeBlob分配成功并且初始化完成时调用commit方法来增加CodeCache的相关统计计数;free方法用于释放一个已分配的CodeBlob并减少CodeCache的统计计数。
allocate方法的调用链如下:
其中BufferBlob,nmethod,RuntimeStub和SingletonBlob就是CodeBlob的四个直接子类。
commit方法的调用链如下:
其中AdapterBlob是BufferBlob的子类,之所以只有这两种Blob调用commit方法是因为CodeCache只单独统计了这两类Blob的数量。
free方法的调用链如下:
这三个方法的源码说明如下:
CodeBlob* CodeCache::allocate(int size, bool is_critical) {
guarantee(size >= 0, "allocation request must be reasonable");
//校验已经获取锁了,由此方法的调用方负责获取锁
assert_locked_or_safepoint(CodeCache_lock);
CodeBlob* cb = NULL;
//增加计数器
_number_of_blobs++;
//不断循环
while (true) {
//分配CodeBlob
cb = (CodeBlob*)_heap->allocate(size, is_critical);
//分配成功
if (cb != NULL) break;
//分配失败,尝试扩展CodeCache,如果失败返回NULL
if (!_heap->expand_by(CodeCacheExpansionSize)) {
// Expansion failed
return NULL;
}
//扩展CodeCache成功后继续尝试分配一个Blob
//打印CodeCache扩展日志
if (PrintCodeCacheExtension) {
ResourceMark rm;
tty->print_cr("code cache extended to [" INTPTR_FORMAT ", " INTPTR_FORMAT "] (" SSIZE_FORMAT " bytes)",
(intptr_t)_heap->low_boundary(), (intptr_t)_heap->high(),
(address)_heap->high() - (address)_heap->low_boundary());
}
}
//更新已使用的CodeCache内存大小,maxCodeCacheUsed是一个静态变量
maxCodeCacheUsed = MAX2(maxCodeCacheUsed, ((address)_heap->high_boundary() -
(address)_heap->low_boundary()) - unallocated_capacity());
//校验CodeHeap
verify_if_often();
print_trace("allocation", cb, size);
return cb;
}
void CodeCache::verify_if_often() {
if (VerifyCodeCacheOften) {
_heap->verify();
}
}
void CodeCache::free(CodeBlob* cb) {
assert_locked_or_safepoint(CodeCache_lock);
verify_if_often();
print_trace("free", cb);
//根据CodeBlob的类型减少对应的计数器
if (cb->is_nmethod()) {
_number_of_nmethods--;
if (((nmethod *)cb)->has_dependencies()) {
_number_of_nmethods_with_dependencies--;
}
}
if (cb->is_adapter_blob()) {
_number_of_adapters--;
}
_number_of_blobs--;
//释放CodeBlob
_heap->deallocate(cb);
verify_if_often();
assert(_number_of_blobs >= 0, "sanity check");
}
void CodeCache::commit(CodeBlob* cb) {
//校验已获取锁CodeCache_lock
assert_locked_or_safepoint(CodeCache_lock);
//根据CodeBlob的类型增加计数器
if (cb->is_nmethod()) {
_number_of_nmethods++;
if (((nmethod *)cb)->has_dependencies()) {
_number_of_nmethods_with_dependencies++;
}
}
if (cb->is_adapter_blob()) {
_number_of_adapters++;
}
//刷新CPU的汇编指令缓存
ICache::invalidate_range(cb->content_begin(), cb->content_size());
}
4、blobs_do / nmethods_do
blobs_do用于遍历所有的CodeBlob执行指定的函数,nmethods_do用于遍历所有的nmethod执行指定的函数,这两个方法实现的源码说明如下:
void CodeCache::blobs_do(void f(CodeBlob* nm)) {
assert_locked_or_safepoint(CodeCache_lock);
//FOR_ALL_BLOBS是一个遍历所有Blob的宏
FOR_ALL_BLOBS(p) {
f(p);
}
}
void CodeCache::nmethods_do(void f(nmethod* nm)) {
assert_locked_or_safepoint(CodeCache_lock);
FOR_ALL_BLOBS(nm) {
//判断Blob是否是nmethod
if (nm->is_nmethod()) f((nmethod*)nm);
}
}
//最终通过heap属性的方法完成遍历
#define FOR_ALL_BLOBS(var) for (CodeBlob *var = first() ; var != NULL; var = next(var) )
CodeBlob* CodeCache::first() {
assert_locked_or_safepoint(CodeCache_lock);
return (CodeBlob*)_heap->first();
}
CodeBlob* CodeCache::next(CodeBlob* cb) {
assert_locked_or_safepoint(CodeCache_lock);
return (CodeBlob*)_heap->next(cb);
}