JVM内存池
根据jconsole工具提供,内存池大致可分为
- 堆
年轻代(New area)
survivor space
eden space
老年代(old Gen 或者 tenuered area) - 非堆
元数据(Metaspace)
compressed Class space
其它元数据
代码缓存块(codeCache)
结合《内存概述与JAVA进程内存》,进一步得出
注:其它内存都可以算到c heap中
元空间和类指针压缩空间的区别
- 类指针压缩空间只包含类的元数据,比如InstanceKlass, ArrayKlass
仅当打开了UseCompressedClassPointers选项才生效
为了提高性能,Java中的虚方法表也存放到这里
这里到底存放哪些元数据的类型,目前仍在减少 - 元空间包含类的其它比较大的元数据,比如方法,字节码,常量池等。
InstanceKlass, ArrayKlass只是一个框架对象,保存关系,大多都通过指针向堆或元空间关联
初始化
ContinueInNewThread0()《openJdk的启动流程》中的方法->
pthread_attr_setstacksize() #设置线程栈大小
Threads::create_vm()《openJdk的启动流程》中的方法->
init_globals()–>
CodeCache::codeCache_init() #初始化code cache
Universe::universe_init() #初始化堆 -->
Metaspace::global_initialize() #初始化元数据空间
CodeCache
用于缓存不同类型的生成的汇编代码,如热点方法编译后的代码,各种运行时的调用入口Stub等,所有的汇编代码在CodeCache中都是以CodeBlob及其子类的形式存在的。通常CodeBlob会对应一个CodeBuffer,负责生成汇编代码的生成器会通过CodeBuffer将汇编代码写入到CodeBlob中。位于hotspot/src/share/vm/code/codeCache.hpp。
class CodeCache : AllStatic {
//CodeHeap就是实际管理汇编代码内存分配的实现
//CodeCache只是CodeHeap的一层包装而已,核心实现都在CodeHeap中
//位于hotspot/src/share/vm/memory/heap.hpp。
static CodeHeap * _heap;
void codeCache_init() {
CodeCache::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());
//CodeHeap根据参数申请内存空间
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());
}
}
Universe
用来保存JVM中重要的系统类及其实例,如基础类型数组的Klass,用来提供对分配Java对象的CollectedHeap的访问入口,提供多种对象分配的方法.位于hotspot src/shared/vm/memory/universe.hpp。
class Universe: AllStatic {
//负责Java对象分配的CollectedHeap引用
//不堆的垃圾收集器对应其不同的子类
static CollectedHeap* _collectedHeap;
jint universe_init() {
assert(!Universe::_fully_initialized, "called after initialize_vtables");
//校验参数的合法
guarantee(1 << LogHeapWordSize == sizeof(HeapWord),
"LogHeapWordSize is incorrect.");
//sizeof(oop)和sizeof(HeapWord)实际都是一个指针的大小
guarantee(sizeof(oop) >= sizeof(HeapWord), "HeapWord larger than oop?");
guarantee(sizeof(oop) % sizeof(HeapWord) == 0,
"oop size is not not a multiple of HeapWord size");
TraceTime timer("Genesis", TraceStartupTime);
//计算部分重要的系统类的关键属性在oop中的偏移量,方便快速根据内存偏移读取属性值
JavaClasses::compute_hard_coded_offsets();
//初始化collectedHeap和TLABA
//1.通过选用不同的垃圾收集器创建不同的_collectedHeap子类,并保存引用
//2.在子类中,通过读取Arguments及globals解析出的vm参数,反调Universe::reserve_heap
//3.根据不同的垃圾收集器,将堆分配用不同的类型,年轻代,老年代等
jint status = Universe::initialize_heap();
if (status != JNI_OK) {
return status;
}
//初始化负责元空间内存管理的Metaspace
Metaspace::global_initialize();
// 初始化ClassLoaderData的_the_null_class_loader_data属性
ClassLoaderData::init_null_class_loader_data();
// 初始化属性
Universe::_finalizer_register_cache = new LatestMethodCache();
Universe::_loader_addClass_cache = new LatestMethodCache();
Universe::_pd_implies_cache = new LatestMethodCache();
Universe::_throw_illegal_access_error_cache = new LatestMethodCache();
//UseSharedSpaces默认为true,表示为元数据使用共享空间
if (UseSharedSpaces) {
//初始化共享空间
MetaspaceShared::initialize_shared_spaces();
StringTable::create_table();
} else {
//不使用共享空间时,分别初始化各个组件,SymbolTable表示符号表,StringTable表示字符串表
SymbolTable::create_table();
StringTable::create_table();
ClassLoader::create_package_info_table();
if (DumpSharedSpaces) {
MetaspaceShared::prepare_for_dumping();
}
}
if (strlen(VerifySubSet) > 0) {
Universe::initialize_verify_flags();
}
return JNI_OK;
}
ReservedSpace Universe::reserve_heap(size_t heap_size, size_t alignment) {
//校验参数
assert(alignment <= Arguments::conservative_max_heap_alignment(),
err_msg("actual alignment " SIZE_FORMAT " must be within maximum heap alignment " SIZE_FORMAT,
alignment, Arguments::conservative_max_heap_alignment()));
//heap_size取整
size_t total_reserved = align_size_up(heap_size, alignment);
//使用指针压缩时堆空间不能超过OopEncodingHeapMax,是计算出来的,32G
assert(!UseCompressedOops || (total_reserved <= (OopEncodingHeapMax - os::vm_page_size())),
"heap size is too big for compressed oops");
//是否使用大内存页,UseLargePages默认是false
bool use_large_pages = UseLargePages && is_size_aligned(alignment, os::large_page_size());
assert(!UseLargePages
|| UseParallelGC
|| use_large_pages, "Wrong alignment to use large pages");
//计算Java堆的基地址,堆内存起始地址
char* addr = Universe::preferred_heap_base(total_reserved, alignment, Universe::UnscaledNarrowOop);
//在执行构造方法的时候会向操作系统申请一段连续的内存空间
ReservedHeapSpace total_rs(total_reserved, alignment, use_large_pages, addr);
if (UseCompressedOops) {
//如果申请失败,即该地址已经被分配了,则重试重新申请,每次重试时使用的NARROW_OOP_MODE不同
if (addr != NULL && !total_rs.is_reserved()) {
addr = Universe::preferred_heap_base(total_reserved, alignment, Universe::ZeroBasedNarrowOop);
ReservedHeapSpace total_rs0(total_reserved, alignment,
use_large_pages, addr);
if (addr != NULL && !total_rs0.is_reserved()) {
//继续重试
addr = Universe::preferred_heap_base(total_reserved, alignment, Universe::HeapBasedNarrowOop);
assert(addr == NULL, "");
ReservedHeapSpace total_rs1(total_reserved, alignment,
use_large_pages, addr);
total_rs = total_rs1;
} else {
total_rs = total_rs0;
}
}
}
//重试依然失败,抛出异常
if (!total_rs.is_reserved()) {
vm_exit_during_initialization(err_msg("Could not reserve enough space for " SIZE_FORMAT "KB object heap", total_reserved/K));
return total_rs;
}
if (UseCompressedOops) {
//设置压缩指针的基地址
address base = (address)(total_rs.base() - os::vm_page_size());
Universe::set_narrow_oop_base(base);
}
return total_rs;
}
}
Metaspace
Metaspace表示用来给Klass等元数据分配内存的一个内存空间,称为元空间,每个ClassLoader实例包括启动类加载器都会创建一个对应的Metaspace实例,每个Metaspace实例都有一个SpaceManager实例,通过SpaceManager完成内存分配与管理,位于hotspot/src/share/vm/memory/metaspace.hpp
class Metaspace : public CHeapObj<mtClass> {
void Metaspace::global_initialize() {
MetaspaceGC::initialize();
// Initialize the alignment for shared spaces.
int max_alignment = os::vm_allocation_granularity();
size_t cds_total = 0;
MetaspaceShared::set_max_alignment(max_alignment);
//DumpSharedSpaces默认为false
if (DumpSharedSpaces) {
#if INCLUDE_CDS
MetaspaceShared::estimate_regions_size();
SharedReadOnlySize = align_size_up(SharedReadOnlySize, max_alignment);
SharedReadWriteSize = align_size_up(SharedReadWriteSize, max_alignment);
SharedMiscDataSize = align_size_up(SharedMiscDataSize, max_alignment);
SharedMiscCodeSize = align_size_up(SharedMiscCodeSize, max_alignment);
// the min_misc_code_size estimate is based on MetaspaceShared::generate_vtable_methods()
uintx min_misc_code_size = align_size_up(
(MetaspaceShared::num_virtuals * MetaspaceShared::vtbl_list_size) *
(sizeof(void*) + MetaspaceShared::vtbl_method_size) + MetaspaceShared::vtbl_common_code_size,
max_alignment);
if (SharedMiscCodeSize < min_misc_code_size) {
report_out_of_shared_space(SharedMiscCode);
}
cds_total = FileMapInfo::shared_spaces_size();
cds_total = align_size_up(cds_total, _reserve_alignment);
_space_list = new VirtualSpaceList(cds_total/wordSize);
_chunk_manager_metadata = new ChunkManager(SpecializedChunk, SmallChunk, MediumChunk);
if (!_space_list->initialization_succeeded()) {
vm_exit_during_initialization("Unable to dump shared archive.", NULL);
}
#ifdef _LP64
if (cds_total + compressed_class_space_size() > UnscaledClassSpaceMax) {
vm_exit_during_initialization("Unable to dump shared archive.",
err_msg("Size of archive (" SIZE_FORMAT ") + compressed class space ("
SIZE_FORMAT ") == total (" SIZE_FORMAT ") is larger than compressed "
"klass limit: " SIZE_FORMAT, cds_total, compressed_class_space_size(),
cds_total + compressed_class_space_size(), UnscaledClassSpaceMax));
}
assert(UseCompressedOops && UseCompressedClassPointers,
"UseCompressedOops and UseCompressedClassPointers must be set");
Universe::set_narrow_klass_base((address)_space_list->current_virtual_space()->bottom());
if (TraceMetavirtualspaceAllocation && Verbose) {
gclog_or_tty->print_cr("Setting_narrow_klass_base to Address: " PTR_FORMAT,
_space_list->current_virtual_space()->bottom());
}
Universe::set_narrow_klass_shift(0);
#endif // _LP64
#endif // INCLUDE_CDS
} else {
#if INCLUDE_CDS
// If using shared space, open the file that contains the shared space
// and map in the memory before initializing the rest of metaspace (so
// the addresses don't conflict)
address cds_address = NULL;
//UseSharedSpaces默认是false
if (UseSharedSpaces) {
FileMapInfo* mapinfo = new FileMapInfo();
if (mapinfo->initialize() && MetaspaceShared::map_shared_spaces(mapinfo)) {
cds_total = FileMapInfo::shared_spaces_size();
cds_address = (address)mapinfo->region_base(0);
} else {
assert(!mapinfo->is_open() && !UseSharedSpaces,
"archive file not closed or shared spaces not disabled.");
}
}
#endif // INCLUDE_CDS
#ifdef _LP64
//以堆内存的终止地址作为起始地址申请内存,避免堆内存与Metaspace的内存地址冲突
char* base = (char*)align_ptr_up(Universe::heap()->reserved_region().end(), _reserve_alignment);
//申请元数据空间
allocate_metaspace_compressed_klass_ptrs(base, 0);
#endif // _LP64
//计算_first_chunk_word_size和_first_class_chunk_word_size
_first_chunk_word_size = InitialBootClassLoaderMetaspaceSize / BytesPerWord;
_first_chunk_word_size = align_word_size_up(_first_chunk_word_size);
_first_class_chunk_word_size = MIN2((size_t)MediumChunk*6,
(CompressedClassSpaceSize/BytesPerWord)*2);
_first_class_chunk_word_size = align_word_size_up(_first_class_chunk_word_size);
//VIRTUALSPACEMULTIPLIER的取值是2,初始化_space_list和_chunk_manager_metadata
size_t word_size = VIRTUALSPACEMULTIPLIER * _first_chunk_word_size;
word_size = align_size_up(word_size, Metaspace::reserve_alignment_words());
_space_list = new VirtualSpaceList(word_size);
_chunk_manager_metadata = new ChunkManager(SpecializedChunk, SmallChunk, MediumChunk);
//_space_list初始化失败
if (!_space_list->initialization_succeeded()) {
vm_exit_during_initialization("Unable to setup metadata virtual space list.", NULL);
}
}
_tracer = new MetaspaceTracer();
}
void Metaspace::allocate_metaspace_compressed_klass_ptrs(char* requested_addr, address cds_base) {
//校验参数
assert(using_class_space(), "called improperly");
assert(UseCompressedClassPointers, "Only use with CompressedKlassPtrs");
assert(compressed_class_space_size() < KlassEncodingMetaspaceMax,
"Metaspace size is too big");
assert_is_ptr_aligned(requested_addr, _reserve_alignment);
assert_is_ptr_aligned(cds_base, _reserve_alignment);
assert_is_size_aligned(compressed_class_space_size(), _reserve_alignment);
//尝试在指定起始地址处申请一段连续的内存空间
bool large_pages = false;
//如果开启了指针压缩,则CompressedClassSpace分配在MaxMetaspaceSize里头,即MaxMetaspaceSize=Compressed Class Space Size + Metaspace area (excluding the Compressed Class Space) Size
ReservedSpace metaspace_rs = ReservedSpace(compressed_class_space_size(),
_reserve_alignment,
large_pages,
requested_addr, 0);
if (!metaspace_rs.is_reserved()) {
#if INCLUDE_CDS
if (UseSharedSpaces) {
size_t increment = align_size_up(1*G, _reserve_alignment);
char *addr = requested_addr;
while (!metaspace_rs.is_reserved() && (addr + increment > addr) &&
can_use_cds_with_metaspace_addr(addr + increment, cds_base)) {
addr = addr + increment;
metaspace_rs = ReservedSpace(compressed_class_space_size(),
_reserve_alignment, large_pages, addr, 0);
}
}
#endif
// 忽略起始地址,尝试重新申请,分配失败抛出异常
if (!metaspace_rs.is_reserved()) {
metaspace_rs = ReservedSpace(compressed_class_space_size(),
_reserve_alignment, large_pages);
if (!metaspace_rs.is_reserved()) {
vm_exit_during_initialization(err_msg("Could not allocate metaspace: %d bytes",
compressed_class_space_size()));
}
}
}
// If we got here then the metaspace got allocated.
MemTracker::record_virtual_memory_type((address)metaspace_rs.base(), mtClass);
#if INCLUDE_CDS
if (UseSharedSpaces && !can_use_cds_with_metaspace_addr(metaspace_rs.base(), cds_base)) {
FileMapInfo::stop_sharing_and_unmap(
"Could not allocate metaspace at a compatible address");
}
#endif
//重置narrow_klass_base和narrow_klass_shift
set_narrow_klass_base_and_shift((address)metaspace_rs.base(),
UseSharedSpaces ? (address)cds_base : 0);
initialize_class_space(metaspace_rs);
//打印日志
if (PrintCompressedOopsMode || (PrintMiscellaneous && Verbose)) {
print_compressed_class_space(gclog_or_tty, requested_addr);
}
}
MetaWord* Metaspace::allocate(ClassLoaderData* loader_data, size_t word_size,
bool read_only, MetaspaceObj::Type type, TRAPS) {
if (HAS_PENDING_EXCEPTION) {
//不能有未处理异常
assert(false, "Should not allocate with exception pending");
return NULL; // caller does a CHECK_NULL too
}
//loader_data不能为空
assert(loader_data != NULL, "Should never pass around a NULL loader_data. "
"ClassLoaderData::the_null_class_loader_data() should have been used.");
// DumpSharedSpaces默认为false
if (DumpSharedSpaces) {
assert(type > MetaspaceObj::UnknownType && type < MetaspaceObj::_number_of_types, "sanity");
Metaspace* space = read_only ? loader_data->ro_metaspace() : loader_data->rw_metaspace();
MetaWord* result = space->allocate(word_size, NonClassType);
if (result == NULL) {
report_out_of_shared_space(read_only ? SharedReadOnly : SharedReadWrite);
}
if (PrintSharedSpaces) {
space->record_allocation(result, type, space->vsm()->get_raw_word_size(word_size));
}
// Zero initialize.
Copy::fill_to_aligned_words((HeapWord*)result, word_size, 0);
return result;
}
MetadataType mdtype = (type == MetaspaceObj::ClassType) ? ClassType : NonClassType;
//获取ClassLoaderData的_metaspace,然后分配内存
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);
if (is_init_completed()) {
//启动完成通过GC释放部分内存,然后尝试重新分配
result = Universe::heap()->collector_policy()->satisfy_failed_metadata_allocation(
loader_data, word_size, mdtype);
}
}
if (result == NULL) {
//报告分配失败,会对外抛出异常
report_metadata_oome(loader_data, word_size, type, mdtype, CHECK_NULL);
}
//将分配的内存初始化成0
Copy::fill_to_aligned_words((HeapWord*)result, word_size, 0);
return result;
}
}
对象创建
在《Hotspot 类文件加载、链接和初始化》,我们把类模板实例好了,只剩用new关键字创建对象了。关于new指令《hotspot解释器和JIT》,他最终会触发
//hotspot/src/share/vm/interpreter/bytecodeInterpretor.cpp
CASE(_new): {
//获取操作数栈中目标类的符号引用在常量池的索引
u2 index = Bytes::get_Java_u2(pc+1);
//获取当前执行的方法的类的常量池,istate是当前字节码解释器BytecodeInterpreter实例的指针
ConstantPool* constants = istate->method()->constants();
//如果目标类已经解析
if (!constants->tag_at(index).is_unresolved_klass()) {
//校验从常量池获取的解析结果Klass指针是否是InstanceKlass指针,
Klass* entry = constants->slot_at(index).get_klass();
assert(entry->is_klass(), "Should be resolved klass");
Klass* k_entry = (Klass*) entry;
assert(k_entry->oop_is_instance(), "Should be InstanceKlass");
InstanceKlass* ik = (InstanceKlass*) k_entry;
//如果目标类已经完成初始化,并且可以使用快速分配的方式创建
//如果满足以下条件则不能使用快速分配的方式创建:
//1.目标类是抽象类
//2.目标类覆写了Object的finalizer方法
//3.目标类大于FastAllocateSizeLimit参数的值,该参数默认是128k
//4.目标类是java/lang/Class,不能直接分配
if ( ik->is_initialized() && ik->can_be_fastpath_allocated() ) {
//获取目标类的对象大小
size_t obj_size = ik->size_helper();
oop result = NULL;
// If the TLAB isn't pre-zeroed then we'll have to do it
//如果TLAB没有预先初始化则必须在这里完成初始化,need_zero表示是否需要初始化
bool need_zero = !ZeroTLAB;
//如果UseTLAB参数为true,在TLAB中分配对象内存
if (UseTLAB) {
result = (oop) THREAD->tlab().allocate(obj_size);
}
//如果使用Profile则当TLAB分配失败必须使用InterpreterRuntime::_new()方法分配内存,此时不能走
//ifndef圈起来的一段逻辑
#ifndef CC_INTERP_PROFILE
if (result == NULL) {
need_zero = true;
//尝试在共享的eden区分配
retry:
//获取当前未分配内存空间的起始地址
HeapWord* compare_to = *Universe::heap()->top_addr();
//起始地址加上目标类对象大小后,判断是否超过eden区的终止地址
HeapWord* new_top = compare_to + obj_size;
if (new_top <= *Universe::heap()->end_addr()) {
//如果没有超过则通过原子CAS的方式尝试分配,分配失败就一直尝试直到不能分配为止
//cmpxchg_ptr函数是比较top_addr的地址和compare_to的地址是否一样,如果一样则将new_top的地址写入top_addr中并返回compare_to
//如果不相等,即此时eden区分配了新对象,则返回top_addr新的地址,即返回结果不等于compare_to
if (Atomic::cmpxchg_ptr(new_top, Universe::heap()->top_addr(), compare_to) != compare_to) {
goto retry;
}
result = (oop) compare_to;
}
}
#endif
//如果分配成功
if (result != NULL) {
//如果需要完成对象初始化
if (need_zero ) {
HeapWord* to_zero = (HeapWord*) result + sizeof(oopDesc) / oopSize;
obj_size -= sizeof(oopDesc) / oopSize;
if (obj_size > 0 ) {
//将目标对象的内存置0
memset(to_zero, 0, obj_size * HeapWordSize);
}
}
//设置对象头
if (UseBiasedLocking) {
//如果使用偏向锁
result->set_mark(ik->prototype_header());
} else {
result->set_mark(markOopDesc::prototype());
}
//设置oop的相关属性
result->set_klass_gap(0);
result->set_klass(k_entry);
//执行内存屏障指令
OrderAccess::storestore();
//将目标对象放到操作数栈的顶部
SET_STACK_OBJECT(result, 0);
//更新PC指令计数器,即告诉解释器此条new指令执行完毕,new指令总共3个字节,计数器加3
UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
}
}
}
//调用InterpreterRuntime::_new执行慢速分配
CALL_VM(InterpreterRuntime::_new(THREAD, METHOD->constants(), index),
handle_exception);
OrderAccess::storestore();
//分配的对象保存在vm_result中,将对象放到操作数栈的顶部
SET_STACK_OBJECT(THREAD->vm_result(), 0);
//vm_result置空
THREAD->set_vm_result(NULL);
//更新PC指令计数器
UPDATE_PC_AND_TOS_AND_CONTINUE(3, 1);
}
//运行时指令行接口
//hotspot/src/share/vm/interpreter/interpretorRuntime.cpp
IRT_ENTRY(void, InterpreterRuntime::_new(JavaThread* thread, ConstantPool* pool, int index))
//获取常量池中索引为index的Klass指针
Klass* k_oop = pool->klass_at(index, CHECK);
instanceKlassHandle klass (THREAD, k_oop);
//校验Klass是否是抽象类,接口或者java.lang.Class,如果是则抛出异常
klass->check_valid_for_instantiation(true, CHECK);
//检查Klass是否已经完成初始化,如果未完成则执行初始化
//会对klass进行加载,链接初始化,
klass->initialize(CHECK);
//创建对象
oop obj = klass->allocate_instance(CHECK);
//将结果放到当前线程的_vm_result属性中,该属性专门用来传递解释器执行方法调用的结果
thread->set_vm_result(obj);
IRT_END
快速分配
如果在实例分配之前已经完成了类型的解析,那么分配操作仅仅是在内存空间中划分可用内存,因此能以较高效率实现内存分配,这就是快速分配。
HotSpot通过线程局部分配缓存技术(Thread-Local AllocationBuffers,即TLABs)可以在线程私有区域实现空间的分配。
可以通过VM选项UseTLAB来开启或关闭TLAB功能
根据分配空间是来自于线程私有区域还是共享的堆空间,快速分配可以分为两种空间选择策略:
选择TLAB:首先尝试在TLAB中分配,因为TLAB是线程私有区域,故不需要加锁便能够确保线程安全。在分配一个新的对象空间时,将首先尝试在TLAB空间中分配对象空间,若分配空间的请求失败,则再尝试使用加锁机制在Eden区分配新的TLAB,重新计算内存分部。如果还失败。。
选择Eden空间:则尝试在共享的Eden区分配,Eden区是所有线程共享区域,需要保证线程安全,故采用原子操作进行分配。如果失败,则一直重试
慢速分配
之所以成为慢速分配,正是因为在分配实例前需要对类进行解析,确保类及依赖类已得到正确的解析和初始化。
碰撞指针算法(bump-the-pointer)
通常情况下,系统中有大量连续的内存块可来分配对象。如果提高效率
- 记录上一次分配对象的位置
- 当有新对象要分配时,若检查剩余空间能够满足容纳这个对象,就只需要一次移支指针的操作
线和局部分配缓存技术(TLAB)
为了解决在Eden区分配内存时锁竞争导致性能下降的问题。在Eden区域为每个线程分配了私有的内存区域(Thread-local Allocation Buffers),简称TLABS。这里的私有具体是指只能由该线程在这块内存区域中分配对象,但是已经分配的对象其他线程也可以正常访问.
//hotspot/src/share/vm/memory/threadLocalAllocBuffer.hpp
//每个线程中都会有这样一个私有对象
class ThreadLocalAllocBuffer: public CHeapObj<mtThread> {
//TLAB内存区域的起始地址
HeapWord* _start;
//最后一次分配后的地址,即该地址之前的内存区域都已经被分配了
HeapWord* _top;
//预分配的top
HeapWord* _pf_top;
//TLAB内存区域的结束地址
HeapWord* _end;
//期望的内存大小,包含保留区域,以字宽为单位,即8字节
size_t _desired_size;
//一个阈值,free()的返回值大于此值,则保留此TLAB,否则丢弃创建一个新的TLAB
size_t _refill_waste_limit;
//最近一次gc前已分配的内存大小
size_t _allocated_before_last_gc;
//TLAB的最大大小
static size_t _max_size;
//在GC前的目标refill的次数
static unsigned _target_refills;
//执行refill的次数,即重新分配TLAB的次数
unsigned _number_of_refills;
//走快速分配refill浪费的内存大小
unsigned _fast_refill_waste;
//走慢速分配refill浪费的内存大小
unsigned _slow_refill_waste;
//因为gc导致refill浪费的内存大小
unsigned _gc_waste;
//走慢速分配的次数,通过TLAB分配是快速分配,走堆内存分配因为必须加锁是慢速分配
unsigned _slow_allocations;
//用于自适应调整待分配的TLAB大小
AdaptiveWeightedAverage _allocation_fraction;
//线程Thread初始化的时候会调用些方法
void ThreadLocalAllocBuffer::initialize() {
//属性初始化,为空,全是null
initialize(NULL, // start
NULL, // top
NULL); // end
//计算TLAB期望的大小
set_desired_size(initial_desired_size());
if (Universe::heap() != NULL) {
size_t capacity = Universe::heap()->tlab_capacity(myThread()) / HeapWordSize;
double alloc_frac = desired_size() * target_refills() / (double) capacity;
//将当前准备分配的TLAB的大小作为样本采集
_allocation_fraction.sample(alloc_frac);
}
//设置初始的refill_waste_limit
set_refill_waste_limit(initial_refill_waste_limit());
//初始化统计数据
initialize_statistics();
}
inline HeapWord* ThreadLocalAllocBuffer::allocate(size_t size) {
invariants();
HeapWord* obj = top();
//通过指针地址计算剩余的空间是否大于待分配的内存大小
//注意pointer_delta返回的内存大小以及这里待分配的内存大小size的单位都是字宽,8字节
if (pointer_delta(end(), obj) >= size) {
//将obj的指针往前移动指定大小,将原top的地址返回
set_top(obj + size);
invariants();
return obj;
}
return NULL;
}
inline size_t pointer_delta(const HeapWord* left, const HeapWord* right) {
return pointer_delta(left, right, sizeof(HeapWord));
}
inline size_t pointer_delta(const void* left,
const void* right,
size_t element_size) {
return (((uintptr_t) left) - ((uintptr_t) right)) / element_size;
}
void ThreadLocalAllocBuffer::initialize(HeapWord* start,
HeapWord* top,
HeapWord* end) {
set_start(start);
set_top(top);
set_pf_top(top);
set_end(end);
//参数校验
invariants();
}
//快速使用tlab分配失败
//计算,重新在eden里面申请个tlab。
HeapWord* CollectedHeap::allocate_from_tlab_slow(KlassHandle klass, Thread* thread, size_t size) {
// Retain tlab and allocate object in shared space if
// the amount free in the tlab is too large to discard.
//如果剩余的空间大于允许refill浪费的空间则继续使用该TLAB,返回NULL后就将在Eden区分配内存,因为必须加锁,所以相对于
//走TLAB是慢速分配
if (thread->tlab().free() > thread->tlab().refill_waste_limit()) {
thread->tlab().record_slow_allocation(size);
return NULL;
}
//如果剩余的空闲小于refill浪费的空间则丢弃当前线程的TLAB,重新分配一个新的
//为了避免内存碎片化,新分配的TLAB会比之前分配的更小,compute_size就是计算待分配的TLAB的大小,如果返回0说明Eden区内存不足
size_t new_tlab_size = thread->tlab().compute_size(size);
//这里的clear并不是释放当前TALB占用的内存,而是将剩余的允许浪费的空间用无意义的对象填充,让Eden区的内存是连续的
//同时将top,end指针等置位NULL
thread->tlab().clear_before_allocation();
if (new_tlab_size == 0) {
//Eden区堆内存不足了,返回NULL,可能会触发Eden区的垃圾回收
return NULL;
}
//分配一个新的TLAB,有可能分配失败
//会在年轻代里面重新开一个空间
HeapWord* obj = Universe::heap()->allocate_new_tlab(new_tlab_size);
if (obj == NULL) {
return NULL;
}
//发布事件通知
AllocTracer::send_allocation_in_new_tlab_event(klass, new_tlab_size * HeapWordSize, size * HeapWordSize);
if (ZeroTLAB) {
//初始化
Copy::zero_to_words(obj, new_tlab_size);
}
//新分配的TLAB的属性初始化
thread->tlab().fill(obj, obj + size, new_tlab_size);
return obj;
}
}
- 被始化ThreadLocalAllocBuffer
Threads::create_vm()《openJdk的启动流程》中的方法->
java_start
JavaThread::run()
ThreadLocalAllocBuffer::initialize()
设置的相关属性都为null
initialize(NULL, NULL, NULL);
- 当调用ThreadLocalAllocBuffer::allocate_from_tlab时
HeapWord* CollectedHeap::allocate_from_tlab(KlassHandle klass, Thread* thread, size_t size) {
assert(UseTLAB, "should use UseTLAB");
//从tlab中分配指定大小的内存
HeapWord* obj = thread->tlab().allocate(size);
if (obj != NULL) {
return obj;
}
//走慢速模式从tlab中分配内存
return allocate_from_tlab_slow(klass, thread, size);
}
因为初始值都为null,所以直接allocate失败,转而走allocate_from_tlab_slow
- allocate_from_tlab_slow中如果发现tlab不够,则申请
Universe::heap()->allocate_new_tlab(new_tlab_size);
ParallelScavengeHeap模式下最终调用年轻代申请
young_gen()->allocate(size)
- 再次申请对象就会进行快速分配allocate
//直接在已申请的tlab上划走空间
//将obj的指针往前移动指定大小,将原top的地址返回
set_top(obj + size);
栈上分配
在栈中分配的基本思路是这样的:分析局部变量的作用域仅限于方法内部,则JVM直接在栈帧内分配对象空间,避免在堆中分配。
- 逃逸分析escape analysis。
分析程序中指针的动态作用域,看某个指针是否指向某个固定的对象并且没有“逃逸”出某个函数/方法或者线程的范围。如果没有逃逸则可知该指针只在某个局部范围内可见,外部(别的函数/方法或线程)无法看到它
- 标量替换,scalar replacement
Java中的原始类型无法再分解,可以看作标量(scalar);指向对象的引用也是标量;而对象本身则是聚合量(aggregate),可以包含任意个数的标量。如果把一个Java对象拆散,将其成员变量恢复为分散的变量,这就叫做标量替换。拆散后的变量便可以被单独分析与优化,可以各自分别在活动记录(栈帧或寄存器)上分配空间;原本的对象就无需整体分配空间了。
这样子做的目的是减少新生代的收集次数,间接的提高JVM性能。
注:
- Oracle/Sun的HotSpot VM从来没在产品里实现过栈上分配,而只实现过它的一种特殊形式——标量替换,这俩是不一样的喔。栈上分配还是要分配完整的对象结构,只不过是在栈帧里而不在GC堆里分配;标量替换则不分配完整的对象,直接把对象的字段打散看作方法的局部变量,也就是说标量替换后就没有对象头了,也不需要把该对象的字段打包为一个整体。
- 逃逸分析及标量替换的栈上分配,是在JIT编译期优化的,解释不存在
对象创建各组件空间的申请
对象数据实例申请
InstanceKlass::allocate_instance
CollectedHeap::obj_allocate
CollectedHeap::common_mem_allocate_init
CollectedHeap->mem_allocate
根据不同的垃圾收集器,调用的类不一样比如ParallelScavengeHeap
直接在年轻代里面申请 young_gen()->allocate(size);
类信息数据内存申请
ClassFileParser::parseClassFile
InstanceKlass::allocate_instance_klass
new (loader_data, size, THREAD) InstanceKlass
Klass::operator new()
Metaspace::allocate
klass通过重写operator new方式指定metaspace进行分配内存
在metaspace找到此类loader_data位置然后进行分配
类常量池内存申请
ClassFileParser::parseClassFile
ConstantPool::allocate
new (loader_data, size, false, ConstantPoolType, THREAD) ConstantPool
MetaspaceObj::operator new()
Metaspace::allocate
MetaspaceObj通过重写operator new方式指定metaspace进行分配内存
在metaspace找到此类loader_data位置然后进行分配
另外描述符Symbol也是继承MetaspaceObj,具体参考SymbolTable
字符串常量池内存申请,<<JVM常量池>>
ClassFileParser::parseClassFile
java_lang_Class::create_mirror
对应class加载过程的准备阶段
设置类的静态 static声明的,对象为null,int 为0
对于 final static String 去字符串常量池StringTable中去寻找
如果有则和 mirror关联起来
如果没有则 调用klass:allocate_instance申请空间
编译方法的空间申请
<<hotspot解释器和JIT>>中,c1,c2编译生成nmethod
nmethod通过重写operator new方式指定codeCache进行分配内存
c heap空间
比如StringTable和SymbolTable的框架结构都是直接new出来的
StringTable entry 是堆中申请
SymbolTable entry 是元数据块申请
//hotspot/src/share/vm/oops/instanceKlass.cpp
InstanceKlass : Klass{
instanceOop InstanceKlass::allocate_instance(TRAPS) {
//判断目标类是否覆写了Object类的finalize方法
bool has_finalizer_flag = has_finalizer();
//获取目标类的对象大小
int size = size_helper();
KlassHandle h_k(THREAD, this);
instanceOop i;
//创建对象
i = (instanceOop)CollectedHeap::obj_allocate(h_k, size, CHECK_NULL);
//如果覆写了finalize方法并且RegisterFinalizersAtInit为false,即不在JVM启动时完成finalize方法的注册
if (has_finalizer_flag && !RegisterFinalizersAtInit) {
//注册finalize方法
i = register_finalizer(i, CHECK_NULL);
}
return i;
}
InstanceKlass* InstanceKlass::allocate_instance_klass(
ClassLoaderData* loader_data,
int vtable_len,
int itable_len,
int static_field_size,
int nonstatic_oop_map_size,
ReferenceType rt,
AccessFlags access_flags,
Symbol* name,
Klass* super_klass,
bool is_anonymous,
TRAPS) {
//计算出InstanceKlass对象要占用的空间
int size = InstanceKlass::size(vtable_len, itable_len, nonstatic_oop_map_size,
access_flags.is_interface(), is_anonymous);
// Allocation
InstanceKlass* ik;
if (rt == REF_NONE) {
if (name == vmSymbols::java_lang_Class()) {
ik = new (loader_data, size, THREAD) InstanceMirrorKlass(
vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
access_flags, is_anonymous);
} else if (name == vmSymbols::java_lang_ClassLoader() ||
(SystemDictionary::ClassLoader_klass_loaded() &&
super_klass != NULL &&
super_klass->is_subtype_of(SystemDictionary::ClassLoader_klass()))) {
ik = new (loader_data, size, THREAD) InstanceClassLoaderKlass(
vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
access_flags, is_anonymous);
} else {
// 普通类选项
//使用new生成对象实际上执行了三个操作:
//1. 调用operator new分配内存 调用重写Klass::operator new
//2. 调用类的构造函数
//3. 返回指针
ik = new (loader_data, size, THREAD) InstanceKlass(
vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
access_flags, is_anonymous);
}
} else {
// reference klass
ik = new (loader_data, size, THREAD) InstanceRefKlass(
vtable_len, itable_len, static_field_size, nonstatic_oop_map_size, rt,
access_flags, is_anonymous);
}
// Check for pending exception before adding to the loader data and incrementing
// class count. Can get OOM here.
if (HAS_PENDING_EXCEPTION) {
return NULL;
}
// Add all classes to our internal class loader list here,
// including classes in the bootstrap (NULL) class loader.
loader_data->add_class(ik);
Atomic::inc(&_total_instanceKlass_count);
return ik;
}
void* Klass::operator new(size_t size, ClassLoaderData* loader_data, size_t word_size, TRAPS) throw() {
//元数据分配空间
return Metaspace::allocate(loader_data, word_size, /*read_only*/false,
MetaspaceObj::ClassType, THREAD);
}
}
//hotspot/src/share/vm/gc_interface/collectedHeap.hpp
CollectedHeap{
oop CollectedHeap::obj_allocate(KlassHandle klass, int size, TRAPS) {
debug_only(check_for_valid_allocation_state());
//检查Java堆是否正在gc
assert(!Universe::heap()->is_gc_active(), "Allocation during gc not allowed");
assert(size >= 0, "int won't convert to size_t");
//分配对象内存并完成初始化
HeapWord* obj = common_mem_allocate_init(klass, size, CHECK_NULL);
//设置对象头和klass属性
post_allocation_setup_obj(klass, obj, size);
//检查分配的内存是否正常,生产环境不执行
NOT_PRODUCT(Universe::heap()->check_for_bad_heap_word_value(obj, size));
return (oop)obj;
}
HeapWord* CollectedHeap::common_mem_allocate_init(KlassHandle klass, size_t size, TRAPS) {
HeapWord* obj = common_mem_allocate_noinit(klass, size, CHECK_NULL);
init_obj(obj, size);
return obj;
}
HeapWord* CollectedHeap::common_mem_allocate_noinit(KlassHandle klass, size_t size, TRAPS) {
//清理当前线程TLAB中未使用的opp
CHECK_UNHANDLED_OOPS_ONLY(THREAD->clear_unhandled_oops();)
//判断是否发生异常
if (HAS_PENDING_EXCEPTION) {
NOT_PRODUCT(guarantee(false, "Should not allocate with exception pending"));
return NULL; // caller does a CHECK_0 too
}
HeapWord* result = NULL;
//如果使用TLAB
if (UseTLAB) {
//从TLAB分配对象
result = allocate_from_tlab(klass, THREAD, size);
if (result != NULL) {
assert(!HAS_PENDING_EXCEPTION,
"Unexpected exception, will result in uninitialized storage");
return result;
}
}
bool gc_overhead_limit_was_exceeded = false;
//从Java堆中分配对象内存
//根据不同的垃圾收集器,调用的类不一样比如ParallelScavengeHeap
//HeapWord* result = young_gen()->allocate(size);
//直接在年轻带里面申请
result = Universe::heap()->mem_allocate(size,
&gc_overhead_limit_was_exceeded);
//分配成功
if (result != NULL) {
NOT_PRODUCT(Universe::heap()->
check_for_non_bad_heap_word_value(result, size));
assert(!HAS_PENDING_EXCEPTION,
"Unexpected exception, will result in uninitialized storage");
//增加当前线程记录已分配的内存大小的属性
THREAD->incr_allocated_bytes(size * HeapWordSize);
//发布堆内存对象分配事件
AllocTracer::send_allocation_outside_tlab_event(klass, size * HeapWordSize);
return result;
}
//分配失败
if (!gc_overhead_limit_was_exceeded) {
//异常处理,Java heap space表示当前堆内存严重不足
report_java_out_of_memory("Java heap space");
//通知JVMTI
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP,
"Java heap space");
}
//抛出异常
THROW_OOP_0(Universe::out_of_memory_error_java_heap());
} else {
//同上,异常处理,GC overhead limit exceeded表示执行GC后仍不能有效回收内存导致内存不足
report_java_out_of_memory("GC overhead limit exceeded");
if (JvmtiExport::should_post_resource_exhausted()) {
JvmtiExport::post_resource_exhausted(
JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_JAVA_HEAP,
"GC overhead limit exceeded");
}
THROW_OOP_0(Universe::out_of_memory_error_gc_overhead_limit());
}
}
}
//hotspot/src/share/vm/classfile/collectedHeap.hpp
class java_lang_Class : AllStatic {
void java_lang_Class::create_mirror(KlassHandle k, Handle class_loader,
Handle protection_domain, TRAPS) {
assert(k->java_mirror() == NULL, "should only assign mirror once");
int computed_modifiers = k->compute_modifier_flags(CHECK);
k->set_modifier_flags(computed_modifiers);
// Class_klass是否已加载
if (SystemDictionary::Class_klass_loaded()) {
//根据Class_klass中的常量申请一个class对象
Handle mirror = InstanceMirrorKlass::cast(SystemDictionary::Class_klass())->allocate_instance(k, CHECK);
// 把申请的class对象和Class_klass关联起来
if (!k.is_null()) {
java_lang_Class::set_klass(mirror(), k());
}
InstanceMirrorKlass* mk = InstanceMirrorKlass::cast(mirror->klass());
assert(oop_size(mirror()) == mk->instance_size(k), "should have been set");
//设置Class_klass的静态字段数
java_lang_Class::set_static_oop_field_count(mirror(), mk->compute_static_oop_field_count(mirror()));
// 如果是数组对象
if (k->oop_is_array()) {
//省略
} else {//普通对象
assert(k->oop_is_instance(), "Must be");
//设置类的静态 static声明的,对象为null,int 为0
//对于 final static String 去字符串常量池StringTable中去寻找
//如果有则和 mirror关联起来
//如果没有则 调用klass:allocate_instance申请空间
initialize_mirror_fields(k, mirror, protection_domain, THREAD);
if (HAS_PENDING_EXCEPTION) {
//如果有异常设null
java_lang_Class::set_klass(mirror(), NULL);
return;
}
}
// set the classLoader field in the java_lang_Class instance
assert(class_loader() == k->class_loader(), "should be same");
set_class_loader(mirror(), class_loader());
// Setup indirection from klass->mirror last
// after any exceptions can happen during allocations.
if (!k.is_null()) {
k->set_java_mirror(mirror());
}
} else {
if (fixup_mirror_list() == NULL) {
GrowableArray<Klass*>* list =
new (ResourceObj::C_HEAP, mtClass) GrowableArray<Klass*>(40, true);
set_fixup_mirror_list(list);
}
fixup_mirror_list()->push(k());
}
}
}
主要参考
《hotspot实战》
《Java虚拟机规范8版》
《openjdk hotspot源码分析之string.intern方法原理》
《Hotspot 内存管理之Metaspace(三) 源码解析》
《Hotspot 内存管理之CodeCache 源码解析》
《Hotspot 内存管理之Universe 源码解析》
《Hotspot Java对象创建和TLAB源码解析》
《逃逸分析/标量替换的一个演示》