• JVM之线程资源标记ResourceMark


    在前面介绍JVM的类加载中比如Field和Method的解析之前,都时要县创建一个ResourceMark对象,那么我们今天介绍下ResourceMark这个类。

    ResourceMark类的主要成员以及方法

    1. ResourceMark继承StackObj,表示它是分配在栈上的, 它内部持有 ResourceMarkImpl实现类的引用.在构造函数中,会初始化ResourceMarkImpl类的构造方法传入当前线程的ResouceArea对象。
    1. class ResourceMark: public StackObj {
    2. const ResourceMarkImpl _impl;
    3. // 构造方法
    4. ResourceMark(ResourceArea* area, Thread* thread) :
    5. _impl(area),
    6. _thread(thread),
    7. _previous_resource_mark(nullptr)
    8. {
    9. if (_thread != nullptr) {
    10. assert(_thread == Thread::current(), "not the current thread");
    11. _previous_resource_mark = _thread->current_resource_mark();
    12. _thread->set_current_resource_mark(this);
    13. }
    14. }
    15. public:
    16. ResourceMark() : ResourceMark(Thread::current()) {}
    17. explicit ResourceMark(Thread* thread)
    18. : ResourceMark(thread->resource_area(), thread) {}
    19. explicit ResourceMark(ResourceArea* area)
    20. : ResourceMark(area, DEBUG_ONLY(Thread::current_or_null()) NOT_DEBUG(nullptr)) {}
    21. void reset_to_mark() { _impl.reset_to_mark(); }
    22. };
    23. 复制代码

    ResourceMark的实现类ResourceMarkImpl构造函数和方法

    1. 构造函数传入当前线程的ResourceArea对象,保存到_area变量。 并调用SavedState构造函数初始化_saved_state变量。 然后再函数体重调用ResourceArea的activate_state校验当前资源状态,并且将_nesting变量加1。
    1. class ResourceMarkImpl {
    2. ResourceArea* _area;
    3. ResourceArea::SavedState _saved_state; // ResourceArea的快照
    4. public:
    5. explicit ResourceMarkImpl(ResourceArea* area) :
    6. _area(area),
    7. _saved_state(area)
    8. {
    9. _area->activate_state(_saved_state);
    10. }
    11. explicit ResourceMarkImpl(Thread* thread)
    12. : ResourceMarkImpl(thread->resource_area()) {}
    13. };
    14. 复制代码

    SavedState是保存ResourceArea当前使用状态的快照。

    1. class SavedState {
    2. friend class ResourceArea;
    3. Chunk* _chunk;
    4. char* _hwm;
    5. char* _max;
    6. size_t _size_in_bytes;
    7. public:
    8. SavedState(ResourceArea* area) :
    9. _chunk(area->_chunk),
    10. _hwm(area->_hwm),
    11. _max(area->_max),
    12. _size_in_bytes(area->_size_in_bytes)
    13. {}
    14. };
    15. 复制代码

    ResourceMarkImpl的析构函数

    在ResouceMark对象是在栈上分配,所以它在生命周期结束后,也会调用 ResourceMarkImpl的析构函数,从而调用reset_to_mark执行重置ResouceArea到分配内存空间之前的快照状态,然后调用deactivate_state将_nesting将1.

    1. ~ResourceMarkImpl() {
    2. reset_to_mark();
    3. _area->deactivate_state将(_saved_state);
    4. }
    5. void reset_to_mark() const {
    6. _area->rollback_to(_saved_state);
    7. }
    8. 复制代码

    reset_to_mark调用reset_to_mark函数,最终通过调用ResourceArea的 rollback_to函数并传入之前保存的内存资源快照_saved_state。

    • 首先判断UseMallocOnly是否只是用Malloc分配,如果是,则调用free_malloced_objects释放上一次分配的资源,这个默认是false,所以直接跳过。
    • 如果快照状态中当前的Chunk的_next指针不为空指针,执行set_size_in_bytes重置到Area中总的Chunk的字节数,然后调用当前的next_chop函数
    1. void rollback_to(const SavedState& state) {
    2. assert(_nesting > state._nesting, "rollback to inactive mark");
    3. assert((_nesting - state._nesting) == 1, "rollback across another mark");
    4. if (UseMallocOnly) {
    5. free_malloced_objects(state._chunk, state._hwm, state._max, _hwm);
    6. }
    7. if (state._chunk->next() != nullptr) {
    8. assert(size_in_bytes() > state._size_in_bytes,
    9. "size: " SIZE_FORMAT ", saved size: " SIZE_FORMAT, size_in_bytes(), state._size_in_bytes);
    10. set_size_in_bytes(state._size_in_bytes);
    11. state._chunk->next_chop();
    12. } else {
    13. assert(size_in_bytes() == state._size_in_bytes, "Sanity check");
    14. }
    15. _chunk = state._chunk; // Roll back to saved chunk.
    16. _hwm = state._hwm;
    17. _max = state._max;
    18. // Clear out this chunk (to detect allocation bugs)
    19. if (ZapResourceArea) {
    20. memset(state._hwm, badResourceValue, state._max - state._hwm);
    21. }
    22. }
    23. 复制代码

    释放当前Chunk后面的Chunk资源

    next_chop调用chop释放当前Chunk的后面连接的Chunk的内存空间,可以是通过delete去释放空间.

    1. void Chunk::next_chop() {
    2. _next->chop();
    3. _next = NULL;
    4. }
    5. void Chunk::chop() {
    6. Chunk *k = this;
    7. while( k ) {
    8. Chunk *tmp = k->next();
    9. // 清除当前的Chunk的内存
    10. if (ZapResourceArea) memset(k->bottom(), badResourceValue, k->length());
    11. delete k; // Free chunk (was malloc'd)
    12. k = tmp;
    13. }
    14. }
    15. 复制代码

    分配内存资源管理器Arena

    1. ResourceArea是继承Arena的资源管理器,ResourceMark是使用它去做内存资源分配。
    1. class ResourceArea: public Arena {
    2. friend class VMStructs;
    3. public:
    4. ResourceArea(MEMFLAGS flags = mtThread) :
    5. Arena(flags) DEBUG_ONLY(COMMA _nesting(0)) {}
    6. ResourceArea(size_t init_size, MEMFLAGS flags = mtThread) :
    7. Arena(flags, init_size) DEBUG_ONLY(COMMA _nesting(0)) {}
    8. 复制代码

    Arena类的主要Field

    1. class Arena : public CHeapObj<mtNone> {
    2. protected:
    3. MEMFLAGS _flags; // 追踪内存的标识
    4. Chunk *_first; // 第一个分配的chunk
    5. Chunk *_chunk; // 当前 chunk
    6. char *_hwm, *_max; // 高水位 and 当前chunk的最大水位
    7. 复制代码

    MEMFLAGS是内存种类的枚举,主要是以下几个内存存储的类别

    1. define MEMORY_TYPES_DO(f)
    2. f(mtJavaHeap, "Java Heap") // Java 堆
    3. f(mtClass, "Class") // java的class对象
    4. f(mtThread, "Thread") // 线程对象
    5. f(mtThreadStack, "Thread Stack")
    6. f(mtCode, "Code") // 生成的字节码
    7. f(mtGC, "GC")
    8. f(mtGCCardSet, "GCCardSet") // G1使用的卡表
    9. f(mtCompiler, "Compiler")
    10. f(mtJVMCI, "JVMCI")
    11. f(mtInternal, "Internal")
    12. 复制代码

    Chunk是分配内存资源管理者

    Chunk是通过_next指针链接形成单向链表, _len记录当前Chunk的大小

    1. class Chunk: CHeapObj<mtChunk> {
    2. private:
    3. Chunk* _next; // next指针指向下一个,形成链表
    4. const size_t _len; //当前Chunk的大小
    5. }
    6. 复制代码

    Arena构造函数初始化

    • Chunk继承了CHeapObj,它是重载new关键字,所以new Chunk就会再堆区分配对象并设置初始化大小为Chunk::init_size(从下面枚举可知初始大小为: 1k减去Chunk自身占用内存大小)。
    • 刚开始_first和_chunk变量是指向同一个chunk对象,_hwn初始化保存没有分配资源的位置. _max初始化为_hwn加当前chunk的大小len。
    • set_size_in_bytes设置可分配的字节数大小。
    1. Arena::Arena(MEMFLAGS flag) : _flags(flag), _size_in_bytes(0) {
    2. _first = _chunk = new (AllocFailStrategy::EXIT_OOM, Chunk::init_size) Chunk(Chunk::init_size);
    3. _hwm = _chunk->bottom();
    4. _max = _chunk->top();
    5. MemTracker::record_new_arena(flag);
    6. set_size_in_bytes(Chunk::init_size);
    7. }
    8. // 内存大小枚举
    9. enum {
    10. #ifdef _LP64
    11. slack = 40, // [RGV] Not sure if this is right, but make it a multiple of 8.
    12. #else
    13. slack = 24, // suspected sizeof(Chunk) + internal malloc headers
    14. #endif
    15. tiny_size = 256 - slack, // Size of first chunk (tiny)
    16. init_size = 1*K - slack, // Size of first chunk (normal aka small)
    17. medium_size= 10*K - slack, // Size of medium-sized chunk
    18. size = 32*K - slack, // Default size of an Arena chunk (following the first)
    19. non_pool_size = init_size + 32 // An initial size which is not one of above
    20. };
    21. 复制代码

    Arena分配内存资源

    • 首先是64位内存对齐
    • 然后调用internal_amalloc进行分配内存
    1. void* Amalloc(size_t x, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM) {
    2. x = ARENA_ALIGN(x); // 64位内初对齐
    3. // 校验内存对齐
    4. assert(is_aligned(_max, ARENA_AMALLOC_ALIGNMENT), "chunk end unaligned?");
    5. return internal_amalloc(x, alloc_failmode);
    6. }
    7. 复制代码
    • 调用pointer_delta计算当前Chunk是有剩余的空间分配申请的x字节,则直接 _hwm 加上x,返回这次分配的_hwm指针。
    • 如果当前Chunk没有足够的剩余空间,则调用grow进行创建新的Chunk的进行分配内存空间。
    1. void* internal_amalloc(size_t x, AllocFailType alloc_failmode = AllocFailStrategy::EXIT_OOM) {
    2. assert(is_aligned(x, BytesPerWord), "misaligned size");
    3. if (pointer_delta(_max, _hwm, 1) >= x) {
    4. char *old = _hwm;
    5. _hwm += x;
    6. return old;
    7. } else {
    8. return grow(x, alloc_failmode);
    9. }
    10. }
    11. 复制代码
    • 分配新的Chunk分配内存空间,空间大小取申请的x的64位对齐和Chunk::size(实际大小是32k-Chunk对象自身占用大小)
    1. void* Arena::grow(size_t x, AllocFailType alloc_failmode) {
    2. size_t len = MAX2(ARENA_ALIGN(x), (size_t) Chunk::size);
    3. Chunk *k = _chunk; //记录之前的Chunk
    4. _chunk = new (alloc_failmode, len) Chunk(len);
    5. if (_chunk == NULL) {
    6. _chunk = k; // 创建 Chunk失败,则恢复之前的Chunk指针
    7. return NULL;
    8. }
    9. if (k) k->set_next(_chunk); // 将新的chunk设置_next指针
    10. else _first = _chunk;
    11. _hwm = _chunk->bottom(); //保存chunk的最小地址
    12. _max = _chunk->top(); // 保存chunk的最大地址
    13. set_size_in_bytes(size_in_bytes() + len);//设置Arena的所有Chunk的总的字节大小
    14. void* result = _hwm; // 返回Chunk开始分配的内存的void*指针
    15. _hwm += x; // 将_hwm加上x,那么就分配了[_hwn,_hwn+x]内存空间,返回result指针指向_hwn加x之前的地址指针
    16. return result;
    17. }
    18. 复制代码

    Arena的分配内存是Chunk的链表组成

    总结
    本文主要分析栈上分配的ResouceMark,利用线程的ResourceArea进行分配前的快照保存以及内存分配,并利息ResourceMarkImpl的析构函数,释放当前Chunk后面分配的内存空间,并恢复分配前的内存快照的状态,

     

  • 相关阅读:
    算法题位运算-数组中数字出现的次数
    Scratch软件编程等级考试一级——20220619
    (Java)递归,数组
    自动化测试selenium(一)
    LeetCode每日一练 —— 21. 合并两个有序链表
    JavaScript动态设置浏览器可视区域元素的文字颜色、监听滚动条、querySelectorAll、getBoundingClientRect
    Flutter 设计模式|工厂模式家族
    SpringMVC自定义注解---[详细介绍]
    聚观早报 | 长二丁成功发射北京三号B星;​字节推出“悟空搜索”
    职场必看!性能测试响应很慢怎么排查?
  • 原文地址:https://blog.csdn.net/java_beautiful/article/details/126557871