1. Epsilon GC简介

Epsilon GC源于RedHat开发者Aleksey Shipilëv提交的一份JEP 318: Epsilon: A No-Op Garbage Collector (Experimental)草案,该GC只做内存分配而不做内存回收(reclaim),当堆空间耗尽关闭JVM即可,因为它不做任何垃圾回收工作,所以又叫No-op GC。也因为它简单,很适合用来入门OpenJDK GC源码,看看一个最小化可行的垃圾回收器应该具备哪些功能。

Epsilon GC源码位于gc/epsilon:

  1. hotspot/share/gc/epsilon:
  2. epsilon_globals.hpp # GC提供的一些JVM参数,如-XX:+UseEpsilonGC
  3. epsilonArguments.cpp
  4. epsilonArguments.hpp # GC参数在JVM中的表示,是否使用TLAB等,是否开启EpsilonGC等
  5. epsilonBarrierSet.cpp
  6. epsilonBarrierSet.hpp # GC barrier,用于线程创建的时候初始化TLAB
  7. epsilonCollectorPolicy.hpp # 垃圾回收策略,堆初始化大小,最小,最大值,对齐等信息
  8. epsilonHeap.cpp # 包含堆初始化,内存分配,垃圾回收接口
  9. epsilonHeap.hpp # 真正的堆表示,EpsilonGC独有
  10. epsilonMemoryPool.cpp
  11. epsilonMemoryPool.hpp # 感知该堆内存的使用情况,gc次数,gc线程数,上次gc时间等
  12. epsilonMonitoringSupport.cpp
  13. epsilonMonitoringSupport.hpp # perfdata支持
  14. epsilonThreadLocalData.hpp # TLAB内存分配
  15. vmStructs_epsilon.hpp # serviceability agent支持

另外为了启动EpsilonGC需要添加JVM参数-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC,为了输出GC日志查看详细过程添加JVM参数-Xlog:gc*=trace(仅限fastdebug版JVM)

2. EpsilonGC创建

虚拟机在创建早期会调用GCArguments::initialize()初始化GC参数,然后创建中期会配置好堆空间并调用GCArguments::create_heap()创建堆:

  1. // hotspot\share\runtime\thread.cpp
  2. // 创建早期
  3. jint Threads::create_vm(JavaVMInitArgs* args, bool* canTryAgain) {
  4. ...
  5. jint ergo_result = Arguments::apply_ergo(); // GCArguments::initialize()
  6. }
  7. // hotspot\share\memory\universe.cpp
  8. // 创建中期
  9. jint Universe::initialize_heap() {
  10. _collectedHeap = create_heap(); // GCArguments::create_heap()
  11. jint status = _collectedHeap->initialize();
  12. ...
  13. }

首先我们创建GCArguments的子类用来表示EpsilonGC的GC参数:

  1. class EpsilonArguments : public GCArguments {
  2. public:
  3. virtual void initialize();
  4. virtual size_t conservative_max_heap_alignment();
  5. virtual CollectedHeap* create_heap();
  6. };

GCArguments::initialize()可以初始化GC参数,不过并没有什么可以初始化的,Epsilon GC只是简单检查了一下是否有-XX:+UseEpsilonGC

  1. CollectedHeap* EpsilonArguments::create_heap() {
  2. return create_heap_with_policy<EpsilonHeap, EpsilonCollectorPolicy>();
  3. }

GCArguments::create_heap()更简单,直接根据堆的类型(EpsilonHeap,真正的Java堆空间表示)和垃圾回收器策略(EpsilonCollectorPolicy,堆初始化大小,最小,最大值,对齐等信息)创建堆。这个新创建的堆是EpsilonHeap,关于Java堆体系可以参见Java分代堆,简单来说,每种垃圾回收器都有一个独属于自己的可垃圾回收堆,它需要继承自CollectedHeap:

  1. // hotspot\share\gc\epsilon\epsilonHeap.hpp
  2. class EpsilonHeap : public CollectedHeap {
  3. friend class VMStructs;
  4. private:
  5. // 回收器策略
  6. EpsilonCollectorPolicy* _policy;
  7. // 软引用清除策略
  8. SoftRefPolicy _soft_ref_policy;
  9. // perfdata支持
  10. EpsilonMonitoringSupport* _monitoring_support;
  11. // 感知内存池使用情况
  12. MemoryPool* _pool;
  13. GCMemoryManager _memory_manager;
  14. // 实际堆空间
  15. ContiguousSpace* _space;
  16. // 虚拟内存(及其物理后备)
  17. VirtualSpace _virtual_space;
  18. // 最大TLAB
  19. size_t _max_tlab_size;
  20. // 间隔多少次内存分配再进行perdata数据更新
  21. size_t _step_counter_update;
  22. // 间隔多少次内存分配再进行堆用量输出
  23. size_t _step_heap_print;
  24. // TLAB大小衰减时间
  25. int64_t _decay_time_ns;
  26. // 最后一次perdata数据更新计数
  27. volatile size_t _last_counter_update;
  28. // 最后一次输出堆用量计数
  29. volatile size_t _last_heap_print;
  30. public:
  31. ...
  32. };

CollectedHeap是一个抽象基类,里面有很多纯虚函数需要子类重写,创建完堆之后JVM会初始化堆,初始化分两步走:EpsilonHeap::initialize和EpsilonHeap::post_initialize。initialize是重头戏,它做了最重要的工作,包括堆内存的申请,gc barrier的设置:

  1. // hotspot\share\gc\epsilon\epsilonHeap.hpp
  2. jint EpsilonHeap::initialize() {
  3. size_t align = _policy->heap_alignment();
  4. size_t init_byte_size = align_up(_policy->initial_heap_byte_size(), align);
  5. size_t max_byte_size = align_up(_policy->max_heap_byte_size(), align);
  6. // 申请虚拟内存空间,然后commit一部分
  7. // [------------------------------|------------------]
  8. // [ committed | reserved ]
  9. // 0 init_byte_size max_byte_size
  10. // low/low_boundary high high_boundary
  11. ReservedSpace heap_rs = Universe::reserve_heap(max_byte_size, align);
  12. _virtual_space.initialize(heap_rs, init_byte_size);
  13. MemRegion committed_region((HeapWord*)_virtual_space.low(), (HeapWord*)_virtual_space.high());
  14. MemRegion reserved_region((HeapWord*)_virtual_space.low_boundary(), (HeapWord*)_virtual_space.high_boundary());
  15. initialize_reserved_region(reserved_region.start(), reserved_region.end());
  16. // 用ContiguousSpace表示这片(连续)堆内存
  17. _space = new ContiguousSpace();
  18. _space->initialize(committed_region, /* clear_space = */ true, /* mangle_space = */ true);
  19. // 计算最大tlab大小
  20. _max_tlab_size = MIN2(CollectedHeap::max_tlab_size(), align_object_size(EpsilonMaxTLABSize / HeapWordSize));
  21. _step_counter_update = MIN2<size_t>(max_byte_size / 16, EpsilonUpdateCountersStep);
  22. _step_heap_print = (EpsilonPrintHeapSteps == 0) ? SIZE_MAX : (max_byte_size / EpsilonPrintHeapSteps);
  23. _decay_time_ns = (int64_t) EpsilonTLABDecayTime * NANOSECS_PER_MILLISEC;
  24. // perfdata支持,外部可以访问共享内存感知堆信息
  25. _monitoring_support = new EpsilonMonitoringSupport(this);
  26. _last_counter_update = 0;
  27. _last_heap_print = 0;
  28. // 创建gc barrier,比如CMS修改老年代指向新生代的指针就有一个write barrier
  29. // Epsilon GC只是用它在线程创建的时候初始化TLAB
  30. BarrierSet::set_barrier_set(new EpsilonBarrierSet());
  31. // 完成初始化,输出配置信息
  32. ...
  33. return JNI_OK;
  34. }
  35. void EpsilonHeap::post_initialize() {
  36. CollectedHeap::post_initialize();
  37. }
  38. void EpsilonHeap::initialize_serviceability() {
  39. // post_initialize会调用该方法,将堆空间加入内存池管理
  40. _pool = new EpsilonMemoryPool(this);
  41. _memory_manager.add_pool(_pool);
  42. }

3. 内存分配

EpsilonGC支持普通内存分配和TLAB内存分配,前者接口是mem_allocate(),后者是allocate_new_tlab()。

3.1 普通内存分配

  1. // hotspot\share\gc\epsilon\epsilonHeap.hpp
  2. HeapWord* EpsilonHeap::mem_allocate(size_t size, bool *gc_overhead_limit_was_exceeded) {
  3. *gc_overhead_limit_was_exceeded = false;
  4. return allocate_work(size);
  5. }
  6. HeapWord* EpsilonHeap::allocate_work(size_t size) {
  7. // 无锁并发分配
  8. HeapWord* res = _space->par_allocate(size);
  9. // 如果分配失败,循环扩容
  10. while (res == NULL) {
  11. MutexLockerEx ml(Heap_lock);
  12. // 先扩容(之前virtual space有一部分是reserved但是没有committed)
  13. // 剩余可用空间大小
  14. size_t space_left = max_capacity() - capacity();
  15. // 需要空间大小
  16. size_t want_space = MAX2(size, EpsilonMinHeapExpand);
  17. if (want_space < space_left) {
  18. bool expand = _virtual_space.expand_by(want_space);
  19. } else if (size < space_left) {
  20. bool expand = _virtual_space.expand_by(space_left);
  21. } else {
  22. // 如果扩容失败则分配失败,返回null
  23. return NULL;
  24. }
  25. // 扩容成功,设置virtual_space的committed尾为新大小
  26. _space->set_end((HeapWord *) _virtual_space.high());
  27. // 再次尝试分配内存
  28. res = _space->par_allocate(size);
  29. }
  30. size_t used = _space->used();
  31. // 分配成功,更新perdata信息
  32. {
  33. size_t last = _last_counter_update;
  34. if ((used - last >= _step_counter_update) && Atomic::cmpxchg(used, &_last_counter_update, last) == last) {
  35. _monitoring_support->update_counters();
  36. }
  37. }
  38. // 输出堆占用情况
  39. {
  40. size_t last = _last_heap_print;
  41. if ((used - last >= _step_heap_print) && Atomic::cmpxchg(used, &_last_heap_print, last) == last) {
  42. log_info(gc)("Heap: " SIZE_FORMAT "M reserved, " SIZE_FORMAT "M (%.2f%%) committed, " SIZE_FORMAT "M (%.2f%%) used",
  43. max_capacity() / M,
  44. capacity() / M,
  45. capacity() * 100.0 / max_capacity(),
  46. used / M,
  47. used * 100.0 / max_capacity());
  48. }
  49. }
  50. return res;
  51. }

为了看到扩容的发生,我们可以修改一下代码,在循环扩容处添加日志记录:

  1. log_info(gc)("Heap expansion: committed %lluM, needs %lluM, reserved %lluM",
  2. capacity() / M,
  3. want_space/M,
  4. max_capacity() / M);

编译得到JVM,然后准备一段Java代码:

  1. package com.github.kelthuzadx;
  2. class Foo{
  3. private static int _1MB = 1024*1024;
  4. private byte[] b = new byte[_1MB*200];
  5. }
  6. public class GCBaby {
  7. public static void main(String[] args) {
  8. new Foo();new Foo();
  9. }
  10. }

启动JVM时添加参数-XX:+UnlockExperimentalVMOptions -Xms128m -Xmx512m -XX:+UseEpsilonGC -Xlog:gc*=info ,最终我们可以看到为了分配400M的对象,初始大小128M的堆进行了3次扩容:

  1. [8.904s][info][gc] Heap expansion: committed 128M, needs 128M, reserved 512M
  2. [8.904s][info][gc] Heap: 512M reserved, 256M (50.00%) committed, 207M (40.51%) used
  3. [9.010s][info][gc] Heap expansion: committed 256M, needs 128M, reserved 512M
  4. [9.010s][info][gc] Heap expansion: committed 384M, needs 128M, reserved 512M
  5. [9.011s][info][gc] Heap: 512M reserved, 512M (100.00%) committed, 407M (79.57%) used

3.2 TLAB内存分配

  1. // hotspot\share\gc\epsilon\epsilonHeap.hpp
  2. HeapWord* EpsilonHeap::allocate_new_tlab(size_t min_size,
  3. size_t requested_size,
  4. size_t* actual_size) {
  5. Thread* thread = Thread::current();
  6. bool fits = true;
  7. size_t size = requested_size;
  8. size_t ergo_tlab = requested_size;
  9. int64_t time = 0;
  10. // 如果启用TLAB
  11. if (EpsilonElasticTLAB) {
  12. // 为线程设置TLAB
  13. ergo_tlab = EpsilonThreadLocalData::ergo_tlab_size(thread);
  14. // 如果启用TLAB衰减,则默认1s后TLAB大小重置为0
  15. if (EpsilonElasticTLABDecay) {
  16. int64_t last_time = EpsilonThreadLocalData::last_tlab_time(thread);
  17. time = (int64_t) os::javaTimeNanos();
  18. if (last_time != 0 && (time - last_time > _decay_time_ns)) {
  19. ergo_tlab = 0;
  20. EpsilonThreadLocalData::set_ergo_tlab_size(thread, 0);
  21. }
  22. }
  23. // 如果TLAB大小能容纳下本次分配,就在TLAB上分配
  24. // 否则弹性的增大TLAB大小,所谓的弹性增大默认是1.1倍扩大
  25. fits = (requested_size <= ergo_tlab);
  26. if (!fits) {
  27. size = (size_t) (ergo_tlab * EpsilonTLABElasticity);
  28. }
  29. }
  30. size = MAX2(min_size, MIN2(_max_tlab_size, size));
  31. size = align_up(size, MinObjAlignment);
  32. if (log_is_enabled(Trace, gc)) {
  33. ResourceMark rm;
  34. log_trace(gc)("TLAB size for \"%s\" (Requested: " SIZE_FORMAT "K, Min: " SIZE_FORMAT
  35. "K, Max: " SIZE_FORMAT "K, Ergo: " SIZE_FORMAT "K) -> " SIZE_FORMAT "K",
  36. thread->name(),
  37. requested_size * HeapWordSize / K,
  38. min_size * HeapWordSize / K,
  39. _max_tlab_size * HeapWordSize / K,
  40. ergo_tlab * HeapWordSize / K,
  41. size * HeapWordSize / K);
  42. }
  43. // 准备就绪,分配内存
  44. HeapWord* res = allocate_work(size);
  45. if (res != NULL) {
  46. // 分配成功
  47. *actual_size = size;
  48. if (EpsilonElasticTLABDecay) {
  49. EpsilonThreadLocalData::set_last_tlab_time(thread, time);
  50. }
  51. if (EpsilonElasticTLAB && !fits) {
  52. EpsilonThreadLocalData::set_ergo_tlab_size(thread, size);
  53. }
  54. } else {
  55. // 分配失败
  56. if (EpsilonElasticTLAB) {
  57. EpsilonThreadLocalData::set_ergo_tlab_size(thread, 0);
  58. }
  59. }
  60. return res;
  61. }

4. 垃圾回收

前面已经提到EpsilonGC只管分配不管释放,所以垃圾回收接口极其简单,就只需要记录一下GC计数信息即可:

  1. // hotspot\share\gc\epsilon\epsilonHeap.cpp
  2. void EpsilonHeap::collect(GCCause::Cause cause) {
  3. log_info(gc)("GC request for \"%s\" is ignored", GCCause::to_string(cause));
  4. _monitoring_support->update_counters();
  5. }
  6. void EpsilonHeap::do_full_collection(bool clear_all_soft_refs) {
  7. log_info(gc)("Full GC request for \"%s\" is ignored", GCCause::to_string(gc_cause()));
  8. _monitoring_support->update_counters();
  9. }
  10. // hotspot\share\gc\epsilon\epsilonMonitoringSupport.cpp
  11. void EpsilonMonitoringSupport::update_counters() {
  12. MemoryService::track_memory_usage();
  13. // 如果启用perfdata
  14. if (UsePerfData) {
  15. EpsilonHeap* heap = EpsilonHeap::heap();
  16. size_t used = heap->used();
  17. size_t capacity = heap->capacity();
  18. _heap_counters->update_all();
  19. _space_counters->update_all(capacity, used);
  20. MetaspaceCounters::update_performance_counters();
  21. CompressedClassSpaceCounters::update_performance_counters();
  22. }
  23. }

然后?就没啦!大功告成。如果想做一个有实际垃圾回收效果的GC可以继续阅读Do It Yourself (OpenJDK) Garbage Collector,这篇文章在Epsilon GC上增加了一个基于标记-压缩(Mark-Compact)算法的垃圾回收机制。

引用

[1] Build Your Own GC in 20 Minutes

[2] Do It Yourself (OpenJDK) Garbage Collector

[3] JEP 318: Epsilon: A No-Op Garbage Collector (Experimental)

[Inside HotSpot] Epsilon GC的更多相关文章

  1. [Inside HotSpot] Serial垃圾回收器 (二) Minor GC

    Serial垃圾回收器Minor GC 1. DefNewGeneration垃圾回收 新生代使用复制算法做垃圾回收,比老年代的标记-压缩简单很多,所有回收代码都位于DefNewGeneration: ...

  2. [Inside HotSpot] Serial垃圾回收器 (一) Full GC

    Serial垃圾回收器Full GC Serial垃圾回收器的Full GC使用标记-压缩(Mark-Compact)进行垃圾回收,该算法基于Donald E. Knuth提出的Lisp2算法,它会把 ...

  3. [Inside HotSpot] Java分代堆

    [Inside HotSpot] Java分代堆 1. 宇宙初始化 JVM在启动的时候会初始化各种结构,比如模板解释器,类加载器,当然也包括这篇文章的主题,Java堆.在hotspot源码结构中gc/ ...

  4. [inside hotspot] 汇编模板解释器(Template Interpreter)和字节码执行

    [inside hotspot] 汇编模板解释器(Template Interpreter)和字节码执行 1.模板解释器 hotspot解释器模块(hotspot\src\share\vm\inter ...

  5. [Inside HotSpot] UseParallelGC和UseParallelOldGC的区别

    JVM的很多参数命名很有迷惑性,-XX:+UseParallel,-XX:+UseParallelOldGC,-XX:+UseParNewGC,-XX:+UseConcMarkSweepGC咋一看容易 ...

  6. [Inside HotSpot] C1编译器HIR的构造

    1. 简介 这篇文章可以说是Christian Wimmer硕士论文Linear Scan Register Allocation for the Java HotSpot™ Client Compi ...

  7. [Inside HotSpot] C1编译器优化:全局值编号(GVN)

    1. 值编号 我们知道C1内部使用的是一种图结构的HIR,它由基本块构成一个图,然后每个基本块里面是SSA形式的指令,关于这点如可以参考[Inside HotSpot] C1编译器工作流程及中间表示. ...

  8. [Inside HotSpot] C1编译器优化:条件表达式消除

    1. 条件传送指令 日常编程中有很多根据某个条件对变量赋不同值这样的模式,比如: int cmov(int num) { int result = 10; if(num<10){ result ...

  9. HotSpot Stop-and-Copy GC

    rednaxelafx的Cheney算法的伪代码.如果不用forwarding的话,维护一个旧地址到新地址的映射也可以. 其中重点部分: void Heap::collect() { // The f ...

随机推荐

  1. 前向纠错码(FEC)的RTP荷载格式

    http://www.rosoo.net/a/201110/15146.html 本文档规定了一般性的前向纠错的媒体数据流的RTP打包格式.这种格式针对基于异或操作的FEC算法进行了特殊设计,它允许终 ...

  2. px-rem自适应转换(进阶@rem:40rem; )

    接力之前的文章 https://www.cnblogs.com/leshao/p/5674710.html 这篇文章讲解的是px -rem 单位换算 除100的  写法 比如实际测量PSD宽度是500 ...

  3. bzoj 1242 弦图判定 MCS

    题目大意: 给定一张无向图,判断是不是弦图. 题解: 今天刚学了<弦图与区间图> 本来写了一个60行+的学习笔记 结果因为忘了保存重启电脑后被还原了... 那就算了吧. MCS最大势算法, ...

  4. Centos6.5 安装pip

    1.下载 sudo wget https://bootstrap.pypa.io/get-pip.py --no-check-certificate 2.安装  python get-pip.py 参 ...

  5. win7下vs2010开发atl服务

    问题一: 编译服务后,提示出下错误 Microsoft.CppCommon.targets(113,5): error MSB3073: 命令 " ***.exe "  /RegS ...

  6. python并发编程之多进程2数据共享及进程池和回调函数

    一.数据共享 尽量避免共享数据的方式 可以借助队列或管道实现通信,二者都是基于消息传递的. 虽然进程间数据独立,但可以用过Manager实现数据共享,事实上Manager的功能远不止于此. 命令就是一 ...

  7. shell入门-grep过滤-1

    正则表达式,就是一个字符串.有一定的规律.我们用指定的字符串匹配一个指定的行.指定的字符串就是正则表达式. 正则表达式有这几个工具:grep egrep sed awk 命令:gerep 说明:过滤出 ...

  8. Java泛型通配符以及限定

    摘抄笔记 A:泛型的限定 /* * 将的酒店员工,厨师,服务员,经理,分别存储到3个集合中 * 定义方法,可以同时遍历3集合,遍历三个集合的同时,可以调用工作方法 */ import java.uti ...

  9. nodejs 操作文件系统读取写入文件

    我们通过fs这个模块来对文件系统进行操作,对于文件系统操作一般都有同步.异步方法,两者区别,同步等有返回结果时候,在继续执行后面的代码,异步是不等返回结果,直接执行后面的代码,待有返回结果时候,通过回 ...

  10. 报错:defined for 'courierAction_pageQuery' in namespace '/'Error creating bean with name 'cn.itcast.bos.web.action.base.CourierAction': Injection of autowired dependencies failed; nested exception is or

    No qualifying bean of type [cn.itcast.bos.web.service.base.CourierService] found for dependency: exp ...