内存管理——Hotspot堆创建
堆创建
如何创建堆的过程虽然并不困难,但是涉及到的类比较多所有最好还是记录一下吧,万一忘了就麻烦了,只讨论使用Serial收集器的堆。
首先来列举一下关于堆管理的几个类好了:
- Heap
- Generation
- Space
Heap
Heap是关于堆的抽象,在JVM中只有CollectedHeap
,继承了它的有下面几个类:
重点来看看GenCollectedHeap
即可,SerialHeap
拓展的内容并不多。Heap是堆管理中最为顶层的部分,堆中还会包含Generation,关于Generation会在后面进行介绍。
堆在Universe::initialize_heap()
中被创建,删掉关于指针压缩后的内容非常简单:
jint Universe::initialize_heap() {
_collectedHeap = create_heap();
jint status = _collectedHeap->initialize();
if (status != JNI_OK) {
return status;
}
ThreadLocalAllocBuffer::set_max_size(Universe::heap()->max_tlab_size());
// We will never reach the CATCH below since Exceptions::_throw will cause
// the VM to exit if an exception is thrown during initialization
if (UseTLAB) {
assert(Universe::heap()->supports_tlab_allocation(),
"Should support thread-local allocation buffers");
ThreadLocalAllocBuffer::startup_initialization();
}
return JNI_OK;
}
一共做了2件事:
- 创建和初始化堆。
- 初始化TLAB配置。
我们只来看看关于如何创建和初始化堆就好了。
堆的创建
堆的创建会调用GCArguments::create_heap
,不过这个是个纯虚函数,所以事实上会调用有关的重载,继承了GCArguments
的类有很多,具体是调用了那个类的实现取决于当前的GC配置,如果是使用Serial收集器,那么会使用SerialArguments
的实现:
// src/hotspot/share/gc/serial/serialArguments.cpp
CollectedHeap* SerialArguments::create_heap() {
return create_heap_with_policy<SerialHeap, MarkSweepPolicy>();
}
// src/hotspot/share/gc/shared/gcArguments.inline.hpp
template <class Heap, class Policy>
CollectedHeap* GCArguments::create_heap_with_policy() {
Policy* policy = new Policy();
policy->initialize_all();
return new Heap(policy);
}
可以看到,实际上会创建SerialHeap
,创建了一个堆的配置策略MarkSweepPolicy
,这个类是GenCollectorPolicy
的子类。在对配置进行初始化的过程中会配置堆的最大大小、初始大小、最小大小和存活率等信息,配置的依据一个是虚拟机启动的参数,另外一个则是所谓的ergonomically,也就是自动配置。
再完成了对于配置策略的初始化之后,会使用它来创建堆,实际创建的堆是GenCollectedHeap
:
GenCollectedHeap::GenCollectedHeap(GenCollectorPolicy *policy,
Generation::Name young,
Generation::Name old,
const char* policy_counters_name) :
CollectedHeap(),
_rem_set(NULL),
_young_gen_spec(new GenerationSpec(young,
policy->initial_young_size(),
policy->max_young_size(),
policy->gen_alignment())),
_old_gen_spec(new GenerationSpec(old,
policy->initial_old_size(),
policy->max_old_size(),
policy->gen_alignment())),
_gen_policy(policy),
_soft_ref_gen_policy(),
_gc_policy_counters(new GCPolicyCounters(policy_counters_name, 2, 2)),
_process_strong_tasks(new SubTasksDone(GCH_PS_NumElements)),
_full_collections_completed(0) {
}
在CollectedHeap
中主要初始化了一些统计信息,所以跳过。可以看到这里将policy保存了下来,同时又创建了两个GenerationSpec
对象,在对堆进行初始化的时候会使用到它们。同时,记忆集的字段_rem_set
仍然是空的。
堆的初始化
堆的初始化这一块的逻辑和系统有比较多的关系,这里假设使用linux系统。
jint GenCollectedHeap::initialize() {
// While there are no constraints in the GC code that HeapWordSize
// be any particular value, there are multiple other areas in the
// system which believe this to be true (e.g. oop->object_size in some
// cases incorrectly returns the size in wordSize units rather than
// HeapWordSize).
guarantee(HeapWordSize == wordSize, "HeapWordSize must equal wordSize");
// Allocate space for the heap.
char* heap_address;
ReservedSpace heap_rs;
size_t heap_alignment = collector_policy()->heap_alignment();
heap_address = allocate(heap_alignment, &heap_rs);
if (!heap_rs.is_reserved()) {
vm_shutdown_during_initialization(
"Could not reserve enough space for object heap");
return JNI_ENOMEM;
}
initialize_reserved_region((HeapWord*)heap_rs.base(), (HeapWord*)(heap_rs.base() + heap_rs.size()));
_rem_set = create_rem_set(reserved_region());
_rem_set->initialize();
CardTableBarrierSet *bs = new CardTableBarrierSet(_rem_set);
bs->initialize();
BarrierSet::set_barrier_set(bs);
ReservedSpace young_rs = heap_rs.first_part(_young_gen_spec->max_size(), false, false);
_young_gen = _young_gen_spec->init(young_rs, rem_set());
heap_rs = heap_rs.last_part(_young_gen_spec->max_size());
ReservedSpace old_rs = heap_rs.first_part(_old_gen_spec->max_size(), false, false);
_old_gen = _old_gen_spec->init(old_rs, rem_set());
clear_incremental_collection_failed();
return JNI_OK;
}
第一个调用的函数就是GenCollectedHeap::allocate
,这个逻辑其实比较清晰但是调用链比较长,一直往下走就行了,总之最后会来到os::pd_reserve_memory
:
// src/hotspot/os/linux/os_linux.cpp
char* os::pd_reserve_memory(size_t bytes, char* requested_addr,
size_t alignment_hint) {
return anon_mmap(requested_addr, bytes, (requested_addr != NULL));
}
此时的requested_addr
参数为NULL。
而anon_mmap
则为:
// src/hotspot/os/linux/os_linux.cpp
// If 'fixed' is true, anon_mmap() will attempt to reserve anonymous memory
// at 'requested_addr'. If there are existing memory mappings at the same
// location, however, they will be overwritten. If 'fixed' is false,
// 'requested_addr' is only treated as a hint, the return value may or
// may not start from the requested address. Unlike Linux mmap(), this
// function returns NULL to indicate failure.
static char* anon_mmap(char* requested_addr, size_t bytes, bool fixed) {
char * addr;
int flags;
flags = MAP_PRIVATE | MAP_NORESERVE | MAP_ANONYMOUS;
if (fixed) {
assert((uintptr_t)requested_addr % os::Linux::page_size() == 0, "unaligned address");
flags |= MAP_FIXED;
}
// Map reserved/uncommitted pages PROT_NONE so we fail early if we
// touch an uncommitted page. Otherwise, the read/write might
// succeed if we have enough swap space to back the physical page.
addr = (char*)::mmap(requested_addr, bytes, PROT_NONE,
flags, -1, 0); // 这样创建出来的内存并不能够被访问。
return addr == MAP_FAILED ? NULL : addr;
}
传入的参数中requested_addr
为NULL,bytes
是新生代和老年代的最大大小相加的值,fixed
为false。解释一下几个flag的意思:MAP_NORESERVE
不要提前预留交换区空间、MAP_PRIVATE
和MAP_ANONYMOUS
创建一个匿名的私有的虚拟映射,映射是其他进程不可见的。调用mmap的另外一个参数PROT_NONE
的意思是禁止对映射的区域进行任何操作。
由于是写时复制的所以事实上并不会提前分配内存,但是可能会出现需要分配内存时系统已经没有足够的内存页和交换区了,不过这么做的好处是并不会真正占用内存。
返回到GenCollectedHeap::initialize
:
char* heap_address;
ReservedSpace heap_rs;
size_t heap_alignment = collector_policy()->heap_alignment();
heap_address = allocate(heap_alignment, &heap_rs);
可以发现,事实上allocate对heap_rs进行了初始化,这个对象就是用来表示一段内存的,来看看它的几个字段:
// ReservedSpace is a data structure for reserving a contiguous address range.
class ReservedSpace {
friend class VMStructs;
protected:
char* _base; // 基址。
size_t _size; // 空间的长度。
size_t _noaccess_prefix; // 是否有不可访问的前缀部分,如果开启指针压缩则会有,关于指针压缩机制可以参考[1]。
size_t _alignment; // 对齐。
bool _special; // 是否是特殊的,貌似开启大页同时有一些其他限制就是特殊的。
int _fd_for_heap; // 文件描述符,如果映射到某个虚拟文件则会有(我不清楚是否可以映射其他类型的文件还是什么的),否则为-1。
private:
bool _executable; // 是否可执行,在ReservedCodeSpace的构造函数中会初始化为true。
};
ReservedSpace
的类继承结构如下:
对于ReservedCodeSpace
的介绍会留到关于动态生成代码的内存管理中去。
继续回到GenCollectedHeap::initialize
:
// 这一段并没有做什么,就是判断有没有初始化成功,以及使用`initialize_reserved_region`对几个字段进行了设置。
if (!heap_rs.is_reserved()) {
vm_shutdown_during_initialization(
"Could not reserve enough space for object heap");
return JNI_ENOMEM;
}
initialize_reserved_region((HeapWord*)heap_rs.base(), (HeapWord*)(heap_rs.base() + heap_rs.size()));
// 创建了记忆集,同时创建了读写屏障,这部分的具体内容留到GC中。
_rem_set = create_rem_set(reserved_region());
_rem_set->initialize();
CardTableBarrierSet *bs = new CardTableBarrierSet(_rem_set);
bs->initialize();
BarrierSet::set_barrier_set(bs);
// 比较主要的部分。
ReservedSpace young_rs = heap_rs.first_part(_young_gen_spec->max_size(), false, false);
_young_gen = _young_gen_spec->init(young_rs, rem_set());
heap_rs = heap_rs.last_part(_young_gen_spec->max_size());
ReservedSpace old_rs = heap_rs.first_part(_old_gen_spec->max_size(), false, false);
_old_gen = _old_gen_spec->init(old_rs, rem_set());
clear_incremental_collection_failed(); // 和GC有关。
heap_rs.first_part
的实现非常简单,就只是创建了一个新的Space,用来表示头_young_gen_spec->max_size个字节,然后用这个Space去初始化young_generation。
Generation
堆都是分代的,所以需要用Generation
来表示代,比如在GenCollectedHeap
中有两个字段_young_gen
、_old_gen
分别用于表示young和old代。在更老的版本中(比如说openjdk-8),GenCollectedHeap
允许使用更加多的代,但是到了目前(openjdk-11),只允许了两个代。这里只来看看和Serial收集器有关的代好了:
其中DefNewGeneration
是GenCollectedHeap
中所使用的young代,而TenuredGeneration
则是old代,对于什么样的heap要使用什么样的generation是提前配置好的。进行配置的有关调用在:
如果是的话CardGeneration
还包含了记忆集和偏移表,分别保存在_rs
、_bts
这两个字段,使用这两个数据结构来追踪老年代堆新生代中对象的引用。
在GenCollectedHeap::initialize
的代码中,我们实际上可以看到在_young_gen = _young_gen_spec->init(young_rs, rem_set());
中使用了GenerationSpec
,它是在构造GenCollectedHeap
时创建的,其代码比较短:
// The specification of a generation. This class also encapsulates
// some generation-specific behavior. This is done here rather than as a
// virtual function of Generation because these methods are needed in
// initialization of the Generations.
class GenerationSpec : public CHeapObj<mtGC> {
friend class VMStructs;
private:
Generation::Name _name;
size_t _init_size;
size_t _max_size;
public:
GenerationSpec(Generation::Name name, size_t init_size, size_t max_size, size_t alignment) :
_name(name),
_init_size(align_up(init_size, alignment)),
_max_size(align_up(max_size, alignment))
{ }
Generation* init(ReservedSpace rs, CardTableRS* remset);
// Accessors
Generation::Name name() const { return _name; }
size_t init_size() const { return _init_size; }
void set_init_size(size_t size) { _init_size = size; }
size_t max_size() const { return _max_size; }
void set_max_size(size_t size) { _max_size = size; }
};
Space
Generation的内部还会被进一步划分为Space,比如在DefNewGeneration
中就有_eden_space
等三个Space。