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源碼解析》
《逃逸分析/標量替換的一個演示》