堆的继承体系
CollectedHeap ---- SharedHeap ---- GenCollectedHeap
CollectedHeap表示了一个Java堆,其内部说明了整个堆的行为和结构。
c++
openjdk/hotspot/src/share/vm/gc_interface/collectedHeap.hpp
class CollectedHeap : public CHeapObj<mtInternal>{
...
protected:
// 为当前堆分配的内存区域
MemRegion _reserved;
// 屏障,用于标记脏卡
BarrierSet* _barrier_set;
...
}
openjdk/hotspot/src/share/vm/memory/sharedHeap.hpp
class SharedHeap : public CollectedHeap {
...
protected:
// 由于是静态变量,所以在整个应用中只有一个SharedHeap实例
static SharedHeap* _sh;
// 记忆集,用来保存老年代指向年轻代的引用
GenRemSet* _rem_set;
// 保存堆的回收策略
CollectorPolicy *_collector_policy;
...
}
openjdk/hotspot/src/share/vm/memory/genCollectedHeap.hpp
class GenCollectedHeap : public SharedHeap {
...
protected:
static GenCollectedHeap* _gch;
private:
int _n_gens;
// 堆中的分代信息,Serial和Serial Old垃圾收集器会将分代分为年轻代和老年代,因此max_gens=2
Generation* _gens[max_gens];
GenerationSpec** _gen_specs;
// 分代垃圾的回收策略
GenCollectorPolicy* _gen_policy;
...
}
分代的继承体系
Generation ----- DefNewGeneration |---- CardGeneration ---- OneContigSpaceCardGeneration ---- TenuredGeneration
- Generation中主要保存着分代的信息,Serial Old主要负责老年代,对应的类为TenuredGeneration;Serial负责年轻代,对应的类为DefNewGeneration。
- Generation:公有结构,保存上次GC耗时、该代的内存起始地址和GC性能计数。
- DefNewGeneration:一种包含Eden、Fromsurvivor和To survivor的分代。
- CardGeneration:包含卡表(CardTable)的分代,由于年轻代在回收时需要标记出年轻代的存活对象,所以还需要以老年代为根进行标记。为了避免全量扫描,通过卡表来加快标记速度。
- OneContigSpaceCardGeneration:包含卡表的连续内存的分代。
- TenuredGeneration:可Mark-Compact(标记-压缩)的卡表代。
c++
openjdk/hotspot/src/share/vm/memory/generation.hpp
class Generation: public CHeapObj<mtGC> {
...
private:
jlong _time_of_last_gc; // 记录最后一次GC在这个代上发生的时间
// 回收器需要在某些时刻记住当时已经被使用的内存总量
MemRegion _prev_used_region;
protected:
// 保存了内存区域的基址和大小,主要在卡标记的过程中使用
MemRegion _reserved;
// 为代预留的内存区域
VirtualSpace _virtual_space;
// Level in the generation hierarchy.
int _level;
// 对于引用处理的支持
ReferenceProcessor* _ref_processor;
...
}
openjdk/hotspot/src/share/vm/memory/defNewGeneration.hpp
class DefNewGeneration: public Generation {
...
protected:
// 当前代的下一个内存代,对于年轻代来说,下一个内存代就是老年代
Generation* _next_gen;
// 最大的晋升年龄
uint _tenuring_threshold;
ageTable _age_table;
// 当分配对象的内存大于以下值时,会被认为是大对象,直接在老年代中分配
// 可通过-XX:PretenureSizeThreshold选项指定此值
size_t _pretenure_size_threshold_words;
...
size_t _max_eden_size;
size_t _max_survivor_size;
EdenSpace* _eden_space; // eden区空间实例
ContiguousSpace* _from_space; // from区空间实例
ContiguousSpace* _to_space; // to区空间实例
}
openjdk/hotspot/src/share/vm/memory/generation.hpp
class CardGeneration: public Generation {
protected:
// 卡表,加快找到老年代引用的年轻代对象
GenRemSet* _rs;
// 偏移表,与卡表配合加快找到老年代引用的年轻代对象
BlockOffsetSharedArray* _bts;
// 当堆空闲过多时会参考收缩因子进行收缩
size_t _shrink_factor;
// 扩展一次内存堆时的最小内存
size_t _min_heap_delta_bytes;
...
}
openjdk/hotspot/src/share/vm/memory/generation.hpp
class OneContigSpaceCardGeneration: public CardGeneration {
...
protected:
ContiguousSpace* _the_space; // 老年代只定义了一块连续空间
...
}
openjdk/hotspot/src/share/vm/memory/tenuredGeneration.hpp
class TenuredGeneration: public OneContigSpaceCardGeneration {
GenerationCounters* _gen_counters;
CSpaceCounters* _space_counters;
}
Space继承体系
Space ---- CompactibleSpace ---- ContiguousSpace ---- EdenSpace |---- OffsetTableContigSpace ---- TenuredSpace
Space类中保存着实际分配的内存空间信息,是内存区间的基类,支持内存分配、内存空间计算和垃圾回收;CompactibleSpace类增加了压缩操作;ContiguousSpace表示空间是连接的,这样就能支持快速地进行内存分配和压缩操作;EdenSpace用来表示Eden空间。
c++
openjdk/hotspot/src/share/vm/memory/space.hpp
class Space: public CHeapObj<mtGC> {
protected:
HeapWord* _bottom; // 实例内存的首地址
HeapWord* _end; // 实例内存的尾地址
HeapWord* _saved_mark_word;
...
}
openjdk/hotspot/src/share/vm/memory/space.hpp
class CompactibleSpace: public Space {
private:
// 保存需要移动的对象所占用的内存空间,即从_bottom到_compaction_top之间的内存都被分配给
// 那些需要移动的对象
HeapWord* _compaction_top;
// 下一个支持压缩操作的Space实例,如果当前的Space实例没有足够的空间保存需要移动的对象就会
// 切换到_next_compaction_space中保存移动的对象
CompactibleSpace* _next_compaction_space;
// 第一个deadspace的起始地址,没有被标记的对象的内存区域或者非Java对象的内存区域都视为deadspace
HeapWord* _first_dead;
// 最后一个连续的被标记为活跃对象的内存区域的终止地址
HeapWord* _end_of_live;
...
}
openjdk/hotspot/src/share/vm/memory/space.hpp
class ContiguousSpace: public CompactibleSpace {
protected:
HeapWord* _top; //Space实例表示的内存空间的起始地址,分配内存会从此往后分配
...
}
openjdk/hotspot/src/share/vm/memory/space.hpp
class EdenSpace : public ContiguousSpace {
private:
DefNewGeneration* _gen; // 保存了年轻代指针
...
}
CollectorPolicy继承体系
CollectorPolicy ---- GenCollectorPolicy ----TwoGenerationCollectorPolicy ---- MarkSweepPolicy
CollectorPolicy用于根据虚拟机启动的参数分配heap堆的大小,以及将heap堆分成大小不同的区(比如年轻代和老年代),并且对不同的区定义不同的Generation的规范。
Java堆的初始化
初始化流程如下
在设置了SerialGC的情况下,堆初始化设置为以下内容,然后启动
c++
gc_policy = new MarkSweepPolicy();
gc_policy->initialize_all();
Universe::_collectedHeap = new GenCollectedHeap(gc_policy);
jint status = Universe::heap()->initialize();
c++
virtual void initialize_all() {
CollectorPolicy::initialize_all();
initialize_generations();
}
virtual void initialize_all() {
initialize_alignments();
initialize_flags();
initialize_size_info();
}
initialize_alignments()
确定堆的对齐大小,里面的设置如下:
- _space_alignment = 65536
- _gen_alignment = 65536
- _heap_alignment = 512 * 4096 (512为1b卡表对应的堆大小,4096为一页大小)
initialize_flags()
根据vm参数设置堆大小,其逻辑为对设置的-Xmx和-Xms参数做对齐处理。
c++
// 其中InitialHeapSize是vm参数-Xmx设置的值,MaxHeapSize为-Xms设置的值,定义在global.h中
uintx aligned_initial_heap_size = align_size_up(InitialHeapSize, _heap_alignment);
uintx aligned_max_heap_size = align_size_up(MaxHeapSize, _heap_alignment);
// Write back to flags if the values changed
if (aligned_initial_heap_size != InitialHeapSize) {
FLAG_SET_ERGO(uintx, InitialHeapSize, aligned_initial_heap_size);
}
if (aligned_max_heap_size != MaxHeapSize) {
FLAG_SET_ERGO(uintx, MaxHeapSize, aligned_max_heap_size);
}
_initial_heap_byte_size = InitialHeapSize;
_max_heap_byte_size = MaxHeapSize;
initialize_size_info()
调用顺序为TwoGenerationCollectorPolicy::initialize_size_info() 《-- GenCollectorPolicy::initialize_size_info() 《-- CollectorPolicy::initialize_size_info()
GenCollectorPolicy::initialize_size_info()
方法作用为:初始化年轻代的大小,主要逻辑为根据-XX:+NewSize= 和-XX:+MaxNewDSize= vm参数确定_initial_gen0_size和_max_gen0_size两个参数的大小。
TwoGenerationCollectorPolicy::initialize_size_info()
方法作用为:初始化老年代的大小,主要逻辑为根据-XX:OldSize= vm参数确定_initial_gen1_size、_max_gen1_size大小
initialize_generations()
c++
void MarkSweepPolicy::initialize_generations() {
// 在本地方法栈创建,数组大小为2,类型为GenerationSpec
_generations = NEW_C_HEAP_ARRAY3(GenerationSpecPtr, number_of_generations(), mtGC, CURRENT_PC,
AllocFailStrategy::RETURN_NULL);
if (_generations == NULL) {
vm_exit_during_initialization("Unable to allocate gen spec");
}
if (UseParNewGC) {
// CMS逻辑走这里
_generations[0] = new GenerationSpec(Generation::ParNew, _initial_gen0_size, _max_gen0_size);
} else {
// 新生代描述符
_generations[0] = new GenerationSpec(Generation::DefNew, _initial_gen0_size, _max_gen0_size);
}
// 老年代描述符
_generations[1] = new GenerationSpec(Generation::MarkSweepCompact, _initial_gen1_size, _max_gen1_size);
if (_generations[0] == NULL || _generations[1] == NULL) {
vm_exit_during_initialization("Unable to allocate gen spec");
}
}
介绍下GenerationSpec,其为一个generation的描述字,结构如下:
c++
class GenerationSpec : public CHeapObj<mtGC> {
private:
Generation::Name _name;
size_t _init_size;
size_t _max_size;
Universe::heap()->initialize()
此方法为jvm堆空间分配内存的地方,主要分配逻辑如下:
c++
heap_address = allocate(heap_alignment, &total_reserved,
&n_covered_regions, &heap_rs);
if (!heap_rs.is_reserved()) {
vm_shutdown_during_initialization(
"Could not reserve enough space for object heap");
return JNI_ENOMEM;
}
_reserved = MemRegion((HeapWord*)heap_rs.base(),
(HeapWord*)(heap_rs.base() + heap_rs.size()));
// It is important to do this in a way such that concurrent readers can't
// temporarily think somethings in the heap. (Seen this happen in asserts.)
_reserved.set_word_size(0);
_reserved.set_start((HeapWord*)heap_rs.base());
size_t actual_heap_size = heap_rs.size();
_reserved.set_end((HeapWord*)(heap_rs.base() + actual_heap_size));
_rem_set = collector_policy()->create_rem_set(_reserved, n_covered_regions);
set_barrier_set(rem_set()->bs());
_gch = this;
for (i = 0; i < _n_gens; i++) {
ReservedSpace this_rs = heap_rs.first_part(_gen_specs[i]->max_size(), false, false);
_gens[i] = _gen_specs[i]->init(this_rs, i, rem_set());
heap_rs = heap_rs.last_part(_gen_specs[i]->max_size());
}
allocate方法中调用了Universe::reserve_heap方法分配内存
c++
ReservedSpace Universe::reserve_heap(size_t heap_size, size_t alignment) {
// 此方法会根据是否开启压缩指针,是否使用-XX:HeapBaseMinAddress= vm参数来确定堆的起始地址
// 如果关闭了压缩指针,那么addr=Null,表示对堆的基址没有任何要求。否则返回addr=0xff600000,即3GB位置
char* addr = Universe::preferred_heap_base(total_reserved, alignment, Universe::UnscaledNarrowOop);
// 构造方法中会调用ReservedSpace::initialize,完成堆空间的分配
ReservedHeapSpace total_rs(total_reserved, alignment, use_large_pages, addr);
}
void ReservedSpace::initialize(size_t size, size_t alignment, bool large,
char* requested_address,
const size_t noaccess_prefix,
bool executable) {
......
// 如果指定了基址就调用os::attempt_reserve_memory_at,方法里使用了mmap分配内存,关于mmap的知识后面再学习
if (requested_address != 0) {
base = os::attempt_reserve_memory_at(size, requested_address);
if (failed_to_reserve_as_requested(base, requested_address, size, false)) {
// OS ignored requested address. Try different address.
base = NULL;
}
} else {
base = os::reserve_memory(size, NULL, alignment);
}
......
// Done
_base = base;
_size = size;
_alignment = alignment;
_noaccess_prefix = noaccess_prefix;
}
ReservedHeapSpace继承于ReservedSpace,结构如下
c++
class ReservedSpace VALUE_OBJ_CLASS_SPEC {
private:
char* _base;
size_t _size;
size_t _noaccess_prefix;
size_t _alignment;
bool _special;
bool _executable;
}
分配好后,用MemRegion类分割空间,给不同代使用。其中会调用Generation* init(ReservedSpace rs, int level, GenRemSet* remset);
根据不同的设置参数设置不同的代。年轻代生成DefNewGeneration,老年代生成TenuredGeneration,并保存在GenCollectedHeap中的_gens数组中。Generation中会根据ReservedSpace创建MemRegion,创建Space类。最后完成GenCollectedHeap的初始化和DefNewGeneration年轻代、TenuredGeneration老年代的初始化。