Epsilon GC实际上是一款no-op GC,即只进行内存分配,不进行内存回收。本文用于介绍Epsilon GC的内存分配方式,以及GC是如何与HotSpot JVM的其他部分打交道的。
通过 JEP 304: Garbage Collector Interface 提案,HotSpot为各个GC抽象出了一套统一的GC接口。这使得新GC可以像插件一样很轻松地添加到JVM中。添加新GC时,除了自身的代码,对JVM中其他代码的改动非常小且很容易修改(改动其他地方的主要目的是为了让JVM能识别出新GC)。而后面添加到JVM中的Epsilon GC(JEP 318 )就是这个 JEP 304 提案的受益者,反过来也说明 JEP 304 提案实现的效果不错。这其实也方便了像我这种JVM小白阅读JVM的源代码。
每个GC都需要实现GCArguments
、CollectedHeap
等约定的接口。当运行JVM时,会通过参数和默认值选择本次要使用的GC,其实就是确定初始化哪个GC的GCArguments
子类。比如通过参数-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC
,就可以激活Epsilon GC,初始化GCArguments
的子类EpsilonArguments
。其中有一个create_heap
方法必须实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class GCArguments {public : virtual CollectedHeap* create_heap () = 0 ; } class EpsilonArguments : public GCArguments {private : virtual CollectedHeap* create_heap () ; } CollectedHeap* EpsilonArguments::create_heap () { return new EpsilonHeap(); }
create_heap
方法返回CollectedHeap
子类实例,这个类是内存管理的核心类,要求子类必须实现统一的内存分配和回收接口:
1 2 3 4 5 6 7 8 9 10 11 12 class CollectedHeap : public CHeapObj<mtInternal> {public : virtual HeapWord* mem_allocate (size_t size, bool * gc_overhead_limit_was_exceeded) = 0 ; virtual HeapWord* allocate_new_tlab (size_t min_size, size_t requested_size, size_t * actual_size) ; virtual void collect (GCCause::Cause cause) = 0 ; virtual void do_full_collection (bool clear_all_soft_refs) = 0 ; }
下面来看下Epsilon GC中的这几个接口的实现。
mem_allocate
功能:直接从堆内存分配,分配时需要加锁。这里有做优化,首先通过_space->par_allocate(size)
分配,使用到了CAS原子操作 解决多线程分配问题。如果空间不足,再加锁扩充内存,然后再重新进行分配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 HeapWord* EpsilonHeap::mem_allocate (size_t size, bool *gc_overhead_limit_was_exceeded) { *gc_overhead_limit_was_exceeded = false ; return allocate_work(size); } HeapWord* EpsilonHeap::allocate_work (size_t size) { HeapWord* res = _space->par_allocate(size); while (res == NULL ) { MutexLocker ml (Heap_lock) ; size_t space_left = max_capacity() - capacity(); size_t want_space = MAX2(size, EpsilonMinHeapExpand); if (want_space < space_left) { bool expand = _virtual_space.expand_by(want_space); } else if (size < space_left) { bool expand = _virtual_space.expand_by(space_left); } else { return NULL ; } _space->set_end((HeapWord *) _virtual_space.high()); res = _space->par_allocate(size); } return res; }
这里的分配我觉得可以再优化下:
进入锁的区域后,再尝试分配一次,因为有可能已经有空间了,可以减缓堆空间的分配
将res = _space->par_allocate(size)
移到锁的区域外面,可以减少锁的阻塞时间
优化后的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 HeapWord* EpsilonHeap::allocate_work (size_t size) { HeapWord* res = _space->par_allocate(size); while (res == NULL ) { { MutexLocker ml (Heap_lock) ; res = _space->par_allocate(size); if (res != NULL ) { break ; } size_t space_left = max_capacity() - capacity(); size_t want_space = MAX2(size, EpsilonMinHeapExpand); if (want_space < space_left) { bool expand = _virtual_space.expand_by(want_space); } else if (size < space_left) { bool expand = _virtual_space.expand_by(space_left); } else { return NULL ; } _space->set_end((HeapWord *) _virtual_space.high()); } res = _space->par_allocate(size); } return res; }
allocate_new_tlab
功能:分配一个线程独享的内存块(TLAB,Thread Local Allocation Buffer),从这块内存块中分配内存时不需要加锁。主要的逻辑是先确认需要分配的TLAB内存的大小,然后调用前面的allocate_work
方法进行分配。如果开启了-XX:EpsilonElasticTLAB
等参数,TLAB的大小会动态进行调整。比如会根据设置的比例进行上调,如果超过某个时间,又会从min_size
开始递增。这样做的目的是为了平衡TLAB块分配的次数和内存消耗。
JVM会调用GC提供的该方法获得TLAB块,然后在分配内存时,通过简单的指针移动进行快速的内存分配。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 HeapWord* EpsilonHeap::allocate_new_tlab (size_t min_size, size_t requested_size, size_t * actual_size) { Thread* thread = Thread::current(); bool fits = true ; size_t size = requested_size; size_t ergo_tlab = requested_size; int64_t time = 0 ; if (EpsilonElasticTLAB) { ergo_tlab = EpsilonThreadLocalData::ergo_tlab_size(thread); if (EpsilonElasticTLABDecay) { int64_t last_time = EpsilonThreadLocalData::last_tlab_time(thread); time = (int64_t ) os::javaTimeNanos(); if (last_time != 0 && (time - last_time > _decay_time_ns)) { ergo_tlab = 0 ; EpsilonThreadLocalData::set_ergo_tlab_size(thread, 0 ); } } fits = (requested_size <= ergo_tlab); if (!fits) { size = (size_t ) (ergo_tlab * EpsilonTLABElasticity); } } size = clamp(size, min_size, _max_tlab_size); size = align_up(size, MinObjAlignment); HeapWord* res = allocate_work(size); if (res != NULL ) { *actual_size = size; if (EpsilonElasticTLABDecay) { EpsilonThreadLocalData::set_last_tlab_time(thread, time); } if (EpsilonElasticTLAB && !fits) { EpsilonThreadLocalData::set_ergo_tlab_size(thread, size); } } else { if (EpsilonElasticTLAB) { EpsilonThreadLocalData::set_ergo_tlab_size(thread, 0 ); } } return res; }
其中,存储跟线程绑定的数据(EpsilonThreadLocalData
)是在EpsilonBarrierSet
类中初始化的。利用Runtime提供的on_thread_create
和on_thread_destroy
方法进行创建和销毁:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 class EpsilonBarrierSet : public BarrierSet {public : virtual void on_thread_create (Thread* thread) ; virtual void on_thread_destroy (Thread* thread) ; }; void EpsilonBarrierSet::on_thread_create (Thread *thread) { EpsilonThreadLocalData::create(thread); } void EpsilonBarrierSet::on_thread_destroy (Thread *thread) { EpsilonThreadLocalData::destroy(thread); } class EpsilonThreadLocalData {private : size_t _ergo_tlab_size; int64_t _last_tlab_time; static EpsilonThreadLocalData* data (Thread* thread) { return thread->gc_data<EpsilonThreadLocalData>(); } public : static void create (Thread* thread) { new (data(thread)) EpsilonThreadLocalData(); } static void destroy (Thread* thread) { data(thread)->~EpsilonThreadLocalData(); } static size_t ergo_tlab_size (Thread *thread) { return data(thread)->_ergo_tlab_size; } static int64_t last_tlab_time (Thread *thread) { return data(thread)->_last_tlab_time; } static void set_ergo_tlab_size (Thread *thread, size_t val) { data(thread)->_ergo_tlab_size = val; } static void set_last_tlab_time (Thread *thread, int64_t time) { data(thread)->_last_tlab_time = time; }
上面EpsilonThreadLocalData::create
静态方法中的 C++ new 语法我之前没有见过,查资料知道它叫 Placement new 语法。作用是将创建的对象存储在指定的分配好的内存中,不需要先调用内存接口分配所需内存。这里指定的内存就是thread->gc_data<EpsilonThreadLocalData>()
了,实际上是thread
对象的一个字段GCThreadLocalData _gc_data
,大小为152字节,只要自定义的GCThreadLocalData
不超过这个大小就可以,内容完全由GC自己决定。
collect
、do_full_collection
功能:这两个方法用于进行垃圾回收。因为Epsilon GC的定位就是只分配内存,不回收,所以并不会做啥具体的事情。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 void EpsilonHeap::collect (GCCause::Cause cause) { switch (cause) { case GCCause::_metadata_GC_threshold: case GCCause::_metadata_GC_clear_soft_refs: log_info(gc)("GC request for \"%s\" is handled" , GCCause::to_string(cause)); MetaspaceGC::compute_new_size(); print_metaspace_info(); break ; default : log_info(gc)("GC request for \"%s\" is ignored" , GCCause::to_string(cause)); } _monitoring_support->update_counters(); } void EpsilonHeap::do_full_collection (bool clear_all_soft_refs) { collect(gc_cause()); }
总结 Epsilon GC涉及的所有类如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 EpsilonArguments - create_heap 创建EspilonHeap对象 EpsilonHeap - allocate_new_tlab 分配新TLAB - mem_allocate 直接从堆上分配内存(大对象) - allocate_work 实际堆内存分配实现,供上面两个方法使用 - collect 进行垃圾回收(实际上不会做任何事情,只是实现下接口) - do_full_collection 进行垃圾回收(实际上不会做任何事情,只是实现下接口) EpsilonBarrierSet - on_thread_create 用户线程创建时的回调 - on_thread_destroy 用户线程销毁时的回调 EpsilonThreadLocalData 存储动态TLAB块大小的信息 EpsilonMemoryPool 提供内存使用情况的接口 - committed_in_bytes 申请了的内存 - used_in_bytes 当前使用了的内存 - max_size 最大内存 EpsilonInitLogger 日志输出相关 EpsilonMonitoringSupport 监控相关
参考