基于SerialGC的java堆研究

堆的继承体系

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堆的初始化

初始化流程如下

graph TD JavaMain--> InitializeJVM --> JNI_CreateJavaVM --> Thread::create_vm --> universe_init --> Universe::initialize_heap

在设置了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老年代的初始化。

相关推荐
paopaokaka_luck4 分钟前
基于Spring Boot+Vue的多媒体素材管理系统的设计与实现
java·数据库·vue.js·spring boot·后端·算法
程序猿麦小七37 分钟前
基于springboot的景区网页设计与实现
java·spring boot·后端·旅游·景区
蓝田~1 小时前
SpringBoot-自定义注解,拦截器
java·spring boot·后端
theLuckyLong1 小时前
SpringBoot后端解决跨域问题
spring boot·后端·python
.生产的驴1 小时前
SpringCloud Gateway网关路由配置 接口统一 登录验证 权限校验 路由属性
java·spring boot·后端·spring·spring cloud·gateway·rabbitmq
小扳1 小时前
Docker 篇-Docker 详细安装、了解和使用 Docker 核心功能(数据卷、自定义镜像 Dockerfile、网络)
运维·spring boot·后端·mysql·spring cloud·docker·容器
v'sir1 小时前
POI word转pdf乱码问题处理
java·spring boot·后端·pdf·word
李少兄1 小时前
解决Spring Boot整合Redis时的连接问题
spring boot·redis·后端
码上一元6 小时前
SpringBoot自动装配原理解析
java·spring boot·后端
枫叶_v8 小时前
【SpringBoot】22 Txt、Csv文件的读取和写入
java·spring boot·后端