Hotspot 內存管理之CodeCache 源碼解析

  目錄

一、CodeCache

1、定義

2、initialize

3、allocate / commit / free

4、blobs_do / nmethods_do

二、CodeHeap

1、定義

2、HeapBlock /FreeBlock 

3、reserve /expand_by

4、allocate

5、deallocate

6、 first_block / next_block

三、ReservedSpace

四、VirtualSpace


《Hotspot 方法調用之StubRoutines 源碼解析》中講解BufferBlob的初始化時,BufferBlob是通過CodeCache::allocate方法分配內存的,如下圖所示:

本篇博客就順着這個方法的實現來研究負責管理生成的彙編代碼緩存的CodeCache的相關實現類。 

一、CodeCache

1、定義

      CodeCache就是用於緩存不同類型的生成的彙編代碼,如熱點方法編譯後的代碼,各種運行時的調用入口Stub等,所有的彙編代碼在CodeCache中都是以CodeBlob及其子類的形式存在的。通常CodeBlob會對應一個CodeBuffer,負責生成彙編代碼的生成器會通過CodeBuffer將彙編代碼寫入到CodeBlob中,參考《Hotspot 方法調用之StubGenerator 源碼解析》中MacroAssembler類的說明。CodeCache的定義位於hotspot src/share/vm/code/codeCache.hpp中,包含的屬性和方法都是靜態的,屬性不多,如下圖:

其中_heap屬性是實際負責內存管理的, _number_of開頭的幾個屬性是統計計數的,_scavenge_root_nmethods是爲了GC時遍歷nmethod使用。CodeCache定義的關鍵方法主要有以下幾種:

  • 內存分配和釋放的,如allocate,commit,free等
  • 查找或者遍歷已經存在的Blob和nmethod,如find_blob,find_nmethod,blobs_do,nmethods_do,first,next等
  • GC支持的,如gc_epilogue,gc_prologue,do_unloading,scavenge_root_nmethods_do等
  • 獲取CodeCache對應內存塊屬性的,如low_bound,high_bound,capacity等
  • 逆優化相關的,如mark_for_deoptimization,mark_all_nmethods_for_deoptimization,make_marked_nmethods_not_entrant等

我們重點關注其初始化和內存分配相關方法的實現。

2、initialize

      initialize方法用來初始化CodeCache的,在JVM進程啓動時執行,初始化結束後整個運行期通過_heap屬性管理的內存都不會被釋放,從而保證已生成的被頻繁訪問的彙編代碼一直常駐內存。該方法的調用鏈如下:

codeCache_init方法的實現如下:

initialize方法實現的源碼說明如下:

void CodeCache::initialize() {
//校驗參數配置是否正確,CodeCacheSegmentSize表示CodeCache對應內存的最小值,CodeEntryAlignment表示分配給一段彙編代碼的最小內存空間
  assert(CodeCacheSegmentSize >= (uintx)CodeEntryAlignment, "CodeCacheSegmentSize must be large enough to align entry points");
#ifdef COMPILER2
//OptoLoopAlignment表示給內部循環代碼分配的最低內存大小
  assert(CodeCacheSegmentSize >= (uintx)OptoLoopAlignment,  "CodeCacheSegmentSize must be large enough to align inner loops");
#endif
  assert(CodeCacheSegmentSize >= sizeof(jdouble),    "CodeCacheSegmentSize must be large enough to align constants");
  
  //按照系統的內存頁大小對CodeCache的參數取整
  //CodeCacheExpansionSize表示CodeCache擴展一次內存空間對應的內存大小,x86下默認值是2304k
  CodeCacheExpansionSize = round_to(CodeCacheExpansionSize, os::vm_page_size());
  //InitialCodeCacheSize表示CodeCache的初始大小,x86 啓用C2編譯下,默認值是2304k
  InitialCodeCacheSize = round_to(InitialCodeCacheSize, os::vm_page_size());
  //ReservedCodeCacheSize表示CodeCache的最大內存大小,x86 啓用C2編譯下,默認值是48M
  ReservedCodeCacheSize = round_to(ReservedCodeCacheSize, os::vm_page_size());
  //完成heap屬性的初始化
  if (!_heap->reserve(ReservedCodeCacheSize, InitialCodeCacheSize, CodeCacheSegmentSize)) {
    vm_exit_during_initialization("Could not reserve enough space for code cache");
  }
  //將CodeHeap放入一個MemoryPool中管理起來
  MemoryService::add_code_heap_memory_pool(_heap);

  //初始化用於刷新CPU指令緩存的Icache,即生成一段用於刷新指令緩存的彙編代碼,此時因爲heap屬性已初始化完成,所以可以從CodeCache中分配Blob了
  icache_init();

  //通知操作系統我們的CodeCache的內存區域,主要是win64使用
  os::register_code_area(_heap->low_boundary(), _heap->high_boundary());
}

3、allocate / commit / free

     allocate方法用於分配CodeBlob的,當CodeBlob分配成功並且初始化完成時調用commit方法來增加CodeCache的相關統計計數;free方法用於釋放一個已分配的CodeBlob並減少CodeCache的統計計數。

allocate方法的調用鏈如下:

其中BufferBlob,nmethod,RuntimeStub和SingletonBlob就是CodeBlob的四個直接子類。

commit方法的調用鏈如下:

其中AdapterBlob是BufferBlob的子類,之所以只有這兩種Blob調用commit方法是因爲CodeCache只單獨統計了這兩類Blob的數量。

free方法的調用鏈如下:

這三個方法的源碼說明如下:

CodeBlob* CodeCache::allocate(int size, bool is_critical) {
  guarantee(size >= 0, "allocation request must be reasonable");
  //校驗已經獲取鎖了,由此方法的調用方負責獲取鎖
  assert_locked_or_safepoint(CodeCache_lock);
  CodeBlob* cb = NULL;
  //增加計數器
  _number_of_blobs++;
  //不斷循環
  while (true) {
    //分配CodeBlob
    cb = (CodeBlob*)_heap->allocate(size, is_critical);
    //分配成功
    if (cb != NULL) break;
    //分配失敗,嘗試擴展CodeCache,如果失敗返回NULL
    if (!_heap->expand_by(CodeCacheExpansionSize)) {
      // Expansion failed
      return NULL;
    }
    //擴展CodeCache成功後繼續嘗試分配一個Blob
    //打印CodeCache擴展日誌
    if (PrintCodeCacheExtension) {
      ResourceMark rm;
      tty->print_cr("code cache extended to [" INTPTR_FORMAT ", " INTPTR_FORMAT "] (" SSIZE_FORMAT " bytes)",
                    (intptr_t)_heap->low_boundary(), (intptr_t)_heap->high(),
                    (address)_heap->high() - (address)_heap->low_boundary());
    }
  }
  //更新已使用的CodeCache內存大小,maxCodeCacheUsed是一個靜態變量
  maxCodeCacheUsed = MAX2(maxCodeCacheUsed, ((address)_heap->high_boundary() -
                          (address)_heap->low_boundary()) - unallocated_capacity());
  //校驗CodeHeap
  verify_if_often();
  print_trace("allocation", cb, size);
  return cb;
}

void CodeCache::verify_if_often() {
  if (VerifyCodeCacheOften) {
    _heap->verify();
  }
}


void CodeCache::free(CodeBlob* cb) {
  assert_locked_or_safepoint(CodeCache_lock);
  verify_if_often();

  print_trace("free", cb);
  //根據CodeBlob的類型減少對應的計數器
  if (cb->is_nmethod()) {
    _number_of_nmethods--;
    if (((nmethod *)cb)->has_dependencies()) {
      _number_of_nmethods_with_dependencies--;
    }
  }
  if (cb->is_adapter_blob()) {
    _number_of_adapters--;
  }
  _number_of_blobs--;
  //釋放CodeBlob
  _heap->deallocate(cb);

  verify_if_often();
  assert(_number_of_blobs >= 0, "sanity check");
}


void CodeCache::commit(CodeBlob* cb) {
  //校驗已獲取鎖CodeCache_lock
  assert_locked_or_safepoint(CodeCache_lock);
  //根據CodeBlob的類型增加計數器
  if (cb->is_nmethod()) {
    _number_of_nmethods++;
    if (((nmethod *)cb)->has_dependencies()) {
      _number_of_nmethods_with_dependencies++;
    }
  }
  if (cb->is_adapter_blob()) {
    _number_of_adapters++;
  }

  //刷新CPU的彙編指令緩存
  ICache::invalidate_range(cb->content_begin(), cb->content_size());
}

4、blobs_do / nmethods_do

     blobs_do用於遍歷所有的CodeBlob執行指定的函數,nmethods_do用於遍歷所有的nmethod執行指定的函數,這兩個方法實現的源碼說明如下:

void CodeCache::blobs_do(void f(CodeBlob* nm)) {
  assert_locked_or_safepoint(CodeCache_lock);
  //FOR_ALL_BLOBS是一個遍歷所有Blob的宏
  FOR_ALL_BLOBS(p) {
    f(p);
  }
}

void CodeCache::nmethods_do(void f(nmethod* nm)) {
  assert_locked_or_safepoint(CodeCache_lock);
  FOR_ALL_BLOBS(nm) {
    //判斷Blob是否是nmethod
    if (nm->is_nmethod()) f((nmethod*)nm);
  }
}

//最終通過heap屬性的方法完成遍歷
#define FOR_ALL_BLOBS(var)       for (CodeBlob *var =       first() ; var != NULL; var =       next(var) )

CodeBlob* CodeCache::first() {
  assert_locked_or_safepoint(CodeCache_lock);
  return (CodeBlob*)_heap->first();
}


CodeBlob* CodeCache::next(CodeBlob* cb) {
  assert_locked_or_safepoint(CodeCache_lock);
  return (CodeBlob*)_heap->next(cb);
}

綜合上述源碼分析可知,CodeCache只是CodeHeap的一層包裝而已,核心實現都在CodeHeap中。

二、CodeHeap

1、定義

      CodeHeap就是實際管理彙編代碼內存分配的實現,其定義在hotspot src/share/vm/memory/heap.hpp中。CodeHeap定義的屬性如下:

  • _memory:VirtualSpace,用於描述CodeHeap對應的一段連續的內存空間
  • _segmap:VirtualSpace,用於保存所有的segment的起始地址,記錄這些segment的使用情況,通過mark_segmap_as_free方法標記爲未分配給Block,通過mark_segmap_as_used方法標記爲已分配給Block
  • _number_of_committed_segments:size_t,已分配內存的segments的數量
  • _number_of_reserved_segments:size_t,剩餘的未分配內存的保留的segments的數量
  • _segment_size:size_t,一個segment的大小
  • _log2_segment_size:int,segment的大小取log2,用於計算根據內存地址計算所屬的segment的序號
  • _next_segment:size_t,下一待分配給Block的segment的序號
  • _freelist:FreeBlock*,可用的HeapBlock 鏈表,所有的Block按照地址依次增加的順序排序,即_freelist是內存地址最小的一個Block
  • _freelist_segments:size_t,可用的segments的個數

一個segment可以理解爲一個內存頁,是操作系統分配內存的最小粒度,爲了避免內存碎片,任意一個Block的大小都必須是segment的整數倍,即任意一個Block會對應N個segment。

CodeHeap定義的public方法主要有以下幾類:

  • 內存初始化,擴展,縮小和釋放的,如reserve,release,expand_by,shrink_by等
  • CodeBlob內存分配和銷燬的,如allocate,deallocate
  • 獲取CodeHeap的內存使用情況的,如low_boundary,high,capacity,allocated_capacity等
  • CodeBlob遍歷相關的,如first,next

重點關注內存管理和遍歷相關的方法實現,可以通過方法的實現反過來理解定義的屬性的具體含義和用途。

2、HeapBlock /FreeBlock 

      HeapBlock表示一個內存塊,HeapBlock只有一個union聯合屬性,如下圖:

通過padding屬性來保證_header屬性對應的內存大小必須是sizeof(int64_t)的整數倍,即按照sizeof(int64_t)對齊,64位下sizeof(int64_t)等於8。Header是一個結構體,定義如下:

其中_length屬性表示這個內存塊的大小,_used屬性表示這個內存塊是否被佔用了。 

HeapBlock定義的方法都是操作Header的兩個屬性的,如下圖:

其中allocated_space方法即返回這個內存塊可用於分配其他對象的內存地址,this+1即表示HeapBlock本身的內存區域的下一個字節的地址。

FreeBlock繼承自HeapBlock,添加了一個_link屬性,如下圖:

FreeBlock添加的方法都是讀寫_link屬性的,可以通過此屬性將所有空閒的HeapBlock組成一個鏈表,便於管理。

3、reserve /expand_by

    reserve方法用於CodeHeap的初始化,CodeCache初始化時調用此方法;expand_by方法用於擴展CodeHeap的內存,CodeHeap的內存不足即分配CodeBlob失敗時調用。

reserve方法的調用鏈如下:

 expand_by方法的調用鏈如下:

這兩者實現的源碼說明如下:

bool CodeHeap::reserve(size_t reserved_size, size_t committed_size,
                       size_t segment_size) {
  //校驗參數                     
  assert(reserved_size >= committed_size, "reserved < committed");
  assert(segment_size >= sizeof(FreeBlock), "segment size is too small");
  assert(is_power_of_2(segment_size), "segment_size must be a power of 2");
  
  //屬性初始化
  _segment_size      = segment_size;
  _log2_segment_size = exact_log2(segment_size);

  // Reserve and initialize space for _memory.
  //獲取內存頁大小
  size_t page_size = os::vm_page_size();
  if (os::can_execute_large_page_memory()) {
    page_size = os::page_size_for_region_unaligned(reserved_size, 8);
  }
  const size_t granularity = os::vm_allocation_granularity();
  const size_t r_align = MAX2(page_size, granularity);
  //內存取整
  const size_t r_size = align_size_up(reserved_size, r_align);
  const size_t c_size = align_size_up(committed_size, page_size);

  const size_t rs_align = page_size == (size_t) os::vm_page_size() ? 0 :
    MAX2(page_size, granularity);

  //ReservedCodeSpace繼承自ReservedSpace,用來描述並分配一段連續的內存空間
  ReservedCodeSpace rs(r_size, rs_align, rs_align > 0);
  os::trace_page_sizes("code heap", committed_size, reserved_size, page_size,
                       rs.base(), rs.size());
  //初始化_memory屬性,如果rs分配內存失敗,此處會返回false
  if (!_memory.initialize(rs, c_size)) {
    return false;
  }
  //Linux的內存映射相關操作
  on_code_mapping(_memory.low(), _memory.committed_size());
  //初始化屬性,size_to_segments返回committed_size是_segment_size的多少倍,即有多少個commited的segment
  _number_of_committed_segments = size_to_segments(_memory.committed_size());
  _number_of_reserved_segments  = size_to_segments(_memory.reserved_size());
  assert(_number_of_reserved_segments >= _number_of_committed_segments, "just checking");
  const size_t reserved_segments_alignment = MAX2((size_t)os::vm_page_size(), granularity);
  const size_t reserved_segments_size = align_size_up(_number_of_reserved_segments, reserved_segments_alignment);
  const size_t committed_segments_size = align_to_page_size(_number_of_committed_segments);

  // 初始化_segmap
  if (!_segmap.initialize(reserved_segments_size, committed_segments_size)) {
    return false;
  }

  MemTracker::record_virtual_memory_type((address)_segmap.low_boundary(), mtCode);
  //校驗初始化的結果
  assert(_segmap.committed_size() >= (size_t) _number_of_committed_segments, "could not commit  enough space for segment map");
  assert(_segmap.reserved_size()  >= (size_t) _number_of_reserved_segments , "could not reserve enough space for segment map");
  assert(_segmap.reserved_size()  >= _segmap.committed_size()     , "just checking");

  //將所有的segment標記爲待分配
  clear();
  return true;
}

void CodeHeap::on_code_mapping(char* base, size_t size) {
#ifdef LINUX
  extern void linux_wrap_code(char* base, size_t size);
  linux_wrap_code(base, size);
#endif
}

size_t   size_to_segments(size_t size) const { return (size + _segment_size - 1) >> _log2_segment_size; }


void CodeHeap::clear() {
  _next_segment = 0;
  //將所有的segment標記爲待分配,如果已經分配給某個Block則認爲其已分配
  mark_segmap_as_free(0, _number_of_committed_segments);
}

void CodeHeap::mark_segmap_as_free(size_t beg, size_t end) {
  assert(0   <= beg && beg <  _number_of_committed_segments, "interval begin out of bounds");
  assert(beg <  end && end <= _number_of_committed_segments, "interval end   out of bounds");
  // setup _segmap pointers for faster indexing
  address p = (address)_segmap.low() + beg;
  address q = (address)_segmap.low() + end;
  // initialize interval
  while (p < q) *p++ = 0xFF;
}

bool CodeHeap::expand_by(size_t size) {
  //判斷當前已分配內存是否足夠
  size_t dm = align_to_page_size(_memory.committed_size() + size) - _memory.committed_size();
  //dm大於0表示需要擴展內存
  if (dm > 0) {
    char* base = _memory.low() + _memory.committed_size();
    //申請新的內存失敗,返回false
    if (!_memory.expand_by(dm)) return false;
    on_code_mapping(base, dm);
    size_t i = _number_of_committed_segments;
    //重新計算_number_of_committed_segments
    _number_of_committed_segments = size_to_segments(_memory.committed_size());
    assert(_number_of_reserved_segments == size_to_segments(_memory.reserved_size()), "number of reserved segments should not change");
    assert(_number_of_reserved_segments >= _number_of_committed_segments, "just checking");
    // expand _segmap space
    size_t ds = align_to_page_size(_number_of_committed_segments) - _segmap.committed_size();
    if (ds > 0) {
      //擴展_segmap
      if (!_segmap.expand_by(ds)) return false;
    }
    assert(_segmap.committed_size() >= (size_t) _number_of_committed_segments, "just checking");
    //i是原來分配的segment,將i以後的即新分配的segment初始化
    mark_segmap_as_free(i, _number_of_committed_segments);
  }
  return true;
}

static size_t align_to_page_size(size_t size) {
  const size_t alignment = (size_t)os::vm_page_size();
  assert(is_power_of_2(alignment), "no kidding ???");
  return (size + alignment - 1) & ~(alignment - 1);
}

 4、allocate

       allocate方法用於從CodeHeap中分配指定內存大小的一塊內存,其調用鏈如下:

 

其源碼實現說明如下:

void* CodeHeap::allocate(size_t instance_size, bool is_critical) {
  //計算需要佔用多少個segment
  size_t number_of_segments = size_to_segments(instance_size + sizeof(HeapBlock));
  //判斷number_of_segments對應的內存是否滿足FreeBlock
  assert(segments_to_size(number_of_segments) >= sizeof(FreeBlock), "not enough room for FreeList");

  // First check if we can satify request from freelist
  debug_only(verify());
  HeapBlock* block = search_freelist(number_of_segments, is_critical);
  debug_only(if (VerifyCodeCacheOften) verify());
  //如果有可用的空閒Block
  if (block != NULL) {
    //CodeCacheMinBlockLength表示一個block最小的segment的個數,注意不是字節數,x86 C2下默認值是4
    //校驗block的合法性,後面一個條件必須是true,是因爲如果爲false,block就會被截短成length個segment,從而保證這個條件成立
    assert(block->length() >= number_of_segments && block->length() < number_of_segments + CodeCacheMinBlockLength, "sanity check");
    //block必須被標記成已用
    assert(!block->free(), "must be marked free");
    //返回該block的除去Header的可用空間的起始地址
    return block->allocated_space();
  }
  
  //如果沒有可用的空閒Block
  if (number_of_segments < CodeCacheMinBlockLength) {
    //保證分配block的最低值
    number_of_segments = CodeCacheMinBlockLength;
  }

  if (!is_critical) {
    //校驗剩餘可分配空間是否充足
    if (segments_to_size(number_of_segments) > (heap_unallocated_capacity() - CodeCacheMinimumFreeSpace)) {
      //空間不足,返回NULL
      return NULL;
    }
  }
  //如果可用的segment充足,_next_segment表示下一個可用的segment
  if (_next_segment + number_of_segments <= _number_of_committed_segments) {
   //將_next_segment後面的多個segment標記爲已分配
    mark_segmap_as_used(_next_segment, _next_segment + number_of_segments);
    //根據segment的序號計算對應的block的起始地址,並初始化HeapBlock
    HeapBlock* b =  block_at(_next_segment);
    b->initialize(number_of_segments);
    //重置_next_segment
    _next_segment += number_of_segments;
    //返回該block的除去Header的可用空間的起始地址
    return b->allocated_space();
  } else {
  //可用的segment充足不足,返回NULL,這時調用方需要調用expand_by方法擴展CodeHeap的內存空間,增加_number_of_committed_segments
    return NULL;
  }
}


//注意這裏的入參length並不是內存大小的字節數,而是轉換後的滿足指定內存大小的segment的個數
FreeBlock* CodeHeap::search_freelist(size_t length, bool is_critical) {
  FreeBlock *best_block = NULL;
  FreeBlock *best_prev  = NULL;
  size_t best_length = 0;

  // 查找大於指定大小的最小 block
  FreeBlock *prev = NULL;
  FreeBlock *cur = _freelist;
  while(cur != NULL) {
    size_t l = cur->length();
    if (l >= length && (best_block == NULL || best_length > l)) {

      // Non critical allocations are not allowed to use the last part of the code heap.
      if (!is_critical) {
        //確保可用空間充足,cur表示block的起始地址,將其強轉成size_t,加上待分配空間的大小length,可計算待分配空間的終止地址
        //CodeCacheMinimumFreeSpace表示最低空閒空間,默認值是500k
        if (((size_t)cur + length) > ((size_t)high_boundary() - CodeCacheMinimumFreeSpace)) {
          //因爲freelist按照地址的順序排序,所以有一個的剩餘可用空間不足,則後面的block都空間不足
          break;
        }
      }

      //記錄符合要求的block,該block的前一個block,該block的長度
      best_block = cur;
      best_prev  = prev;
      best_length = best_block->length();
    }

    //遍歷下一個block
    prev = cur;
    cur  = cur->link();
  }

  //best_block爲空,即沒有找到符合要求的block
  if (best_block == NULL) {
    // None found
    return NULL;
  }
  
  //校驗查找的best_block是否合法
  assert((best_prev == NULL && _freelist == best_block ) ||
         (best_prev != NULL && best_prev->link() == best_block), "sanity check");

  //如果best_block在分配length的內存後的剩餘可用空間不足CodeCacheMinBlockLength則將其從_freelist中移除
  if (best_length < length + CodeCacheMinBlockLength) {
    length = best_length;
    //如果沒有前一個元素,說明best_block就是_freelist
    if (best_prev == NULL) {
      assert(_freelist == best_block, "sanity check");
      //將下一個元素置爲鏈表頭
      _freelist = _freelist->link();
    } else {
      //將best_block的前一個元素和後一個元素建立連接
      best_prev->set_link(best_block->link());
    }
  } else {
    //將best_block截短到除去length的剩餘空間
    best_block->set_length(best_length - length);
    //獲取當前block的下一個block,該block的長度就是length
    best_block = following_block(best_block);
    //找到best_block對應的segment的序號
    size_t beg = segment_for(best_block);
    //將後面的segment都標記爲已用
    mark_segmap_as_used(beg, beg + length);
    //設置長度
    best_block->set_length(length);
  }

  //標記爲已用
  best_block->set_used();
  //_freelist
  _freelist_segments -= length;
  return best_block;
}

//根據b的起始地址和內存大小計算下一個block的起始地址
FreeBlock *CodeHeap::following_block(FreeBlock *b) {
  return (FreeBlock*)(((address)b) + _segment_size * b->length());
}
//根據地址計算所屬的segment的序號
size_t   segment_for(void* p) const            { return ((char*)p - _memory.low()) >> _log2_segment_size; }

void CodeHeap::mark_segmap_as_used(size_t beg, size_t end) {
  assert(0   <= beg && beg <  _number_of_committed_segments, "interval begin out of bounds");
  assert(beg <  end && end <= _number_of_committed_segments, "interval end   out of bounds");
  // setup _segmap pointers for faster indexing
  address p = (address)_segmap.low() + beg;
  address q = (address)_segmap.low() + end;
  // initialize interval
  int i = 0;
  while (p < q) {
    *p++ = i++;
    //0XFF表示未分配給Block,1表示已分配
    if (i == 0xFF) i = 1;
  }
}

char* high_boundary() const                    { return _memory.high_boundary(); }

HeapBlock* block_at(size_t i) const            { return (HeapBlock*)(_memory.low() + (i << _log2_segment_size)); }

 5、deallocate

      deallocate用於將一個HeapBlock標記爲可用,並將其添加到freelist中。其調用鏈如下:

其源碼說明如下: 

void CodeHeap::deallocate(void* p) {
  //如果p不等於find_start方法的返回值,說明p是不合法的,不是allocate方法的返回值
  assert(p == find_start(p), "illegal deallocation");
  //獲取該p對應的HeapBlock的地址,
  HeapBlock* b = (((HeapBlock *)p) - 1);
  //合法校驗
  assert(b->allocated_space() == p, "sanity check");
  //將目標block添加到freelist中
  add_to_freelist(b);

  debug_only(if (VerifyCodeCacheOften) verify());
}

void* CodeHeap::find_start(void* p) const {
  //如果p對應的內存地址不再CodeHeap的內存地址範圍內
  if (!contains(p)) {
    return NULL;
  }
  //獲取p所屬的segment的序號
  size_t i = segment_for(p);
  address b = (address)_segmap.low();
  //如果該segment被標記爲未使用,如果是已使用的block則爲1
  if (b[i] == 0xFF) {
    return NULL;
  }
  //往前遍歷segment,直到該segment被標記爲未使用,0xFF表示未使用,是一個負數
  //即找到這個segment所屬的Block的起始segment
  while (b[i] > 0) i -= (int)b[i];
  //獲取這個segment對應的Block的地址
  HeapBlock* h = block_at(i);
  if (h->free()) {
    return NULL;
  }
  //返回該Block可用於分配內存的起始地址
  return h->allocated_space();
}

//是否在地址範圍內
bool  contains(const void* p) const            { return low_boundary() <= p && p < high(); }

char* low_boundary() const                     { return _memory.low_boundary (); }
char* high() const                             { return _memory.high(); }

size_t   segment_for(void* p) const            { return ((char*)p - _memory.low()) >> _log2_segment_size; }

HeapBlock* block_at(size_t i) const            { return (HeapBlock*)(_memory.low() + (i << _log2_segment_size)); }

void CodeHeap::add_to_freelist(HeapBlock *a) {
  //將其強轉成FreeBlock
  FreeBlock* b = (FreeBlock*)a;
  assert(b != _freelist, "cannot be removed twice");

  //更新_freelist_segments
  _freelist_segments += b->length();
  //b標記爲free
  b->set_free();

  //_freelist爲空,即a是第一個空閒的Block
  if (_freelist == NULL) {
    _freelist = b;
    b->set_link(NULL);
    return;
  }

  // Scan for right place to put into list. List
  // is sorted by increasing addresseses
  FreeBlock* prev = NULL;
  FreeBlock* cur  = _freelist;
  //找到一個合適位置將a放入鏈表中,從_freelist開始遍歷,內存地址是遞增的,_freelist是內存地址最小的一個block
  //找到第一個地址小於b的block,pre是地址大於b的最後一個block
  while(cur != NULL && cur < b) {
    assert(prev == NULL || prev < cur, "must be ordered");
    prev = cur;
    cur  = cur->link();
  }
  //pre爲null,即b是地址最小的,必須小於_freelist
  assert( (prev == NULL && b < _freelist) ||
          (prev < b && (cur == NULL || b < cur)), "list must be ordered");

  if (prev == NULL) {
    //插入到鏈表頭部
    b->set_link(_freelist);
    _freelist = b;
    //按需合併_freelist及其後面的一個block
    merge_right(_freelist);
  } else {
    //將b插入到a的後面
    insert_after(prev, b);
  }
}

void CodeHeap::merge_right(FreeBlock *a) {
  //a必須是空閒的
  assert(a->free(), "must be a free block");
  //如果a的下一個block剛好也在freelist中,即存在兩個連續的空閒block
  if (following_block(a) == a->link()) {
    assert(a->link() != NULL && a->link()->free(), "must be free too");
    //將這兩個連續的空閒block合併成一個
    a->set_length(a->length() + a->link()->length());
    a->set_link(a->link()->link());
    // Update find_start map
    size_t beg = segment_for(a);
    //將兩個block對應的segment標記爲已分配
    mark_segmap_as_used(beg, beg + a->length());
  }
}

void CodeHeap::insert_after(FreeBlock* a, FreeBlock* b) {
  assert(a != NULL && b != NULL, "must be real pointers");

  //將b插入到a的後面
  b->set_link(a->link());
  a->set_link(b);

  //嘗試合併block
  merge_right(b); // Try to make b bigger
  merge_right(a); // Try to make a include b
}

FreeBlock *CodeHeap::following_block(FreeBlock *b) {
  return (FreeBlock*)(((address)b) + _segment_size * b->length());
}

6、 first_block / next_block

       first_block用於獲取CodeHeap中第一個Block,next_block用於獲取指定Block的下一個Block,通過這兩方法的配合可以完成CodeHeap中所有Block的遍歷,其源碼實現如下:

//獲取第一個block
HeapBlock* CodeHeap::first_block() const {
  if (_next_segment > 0)
    return block_at(0);
  return NULL;
}

//獲取目標block的下一個block
// Returns the next Heap block an offset into one
HeapBlock* CodeHeap::next_block(HeapBlock *b) const {
  if (b == NULL) return NULL;
  //segment_for返回b所屬的segment,將其加上b的長度,即b對應的segment的個數
  //即獲取下一個block的起始地址
  size_t i = segment_for(b) + b->length();
  //非法性校驗,_next_segment是下一個待分配的block的序號
  if (i < _next_segment)
    //返回該segment對應的Block的地址
    return block_at(i);
  return NULL;
}

三、ReservedSpace

      ReservedSpace用來分配一段地址連續的內存空間,底層通過mmap實現,注意此時未實際分配內存。其定義位於hotspot src/share/vm/runtime/virtualspace.hpp中。ReservedSpace包含的屬性如下圖:

base表示這段連續內存空間的基地址,size表示內存空間大小,special表示是否走特殊方法分配,executable表示這段內存存儲的數據是否是可執行的。

ReservedSpace的子類有兩個,如下圖:

   ReservedHeapSpace是分配Java堆內存使用的,ReservedCodeSpace是分配CodeCache內存使用的,但是子類並未添加新的字段或者方法,如下圖:

我們這裏重點關注ReservedCodeSpace構造方法實現,如下圖:

上述ReservedSpace構造方法的源碼說明如下:


//size表示分配的總內存大小,alignment表示內存分配的粒度,即內存頁的大小,large表示是否使用大內存頁
//executable用於標識這段內存空間是否存儲可執行的代碼
ReservedSpace::ReservedSpace(size_t size, size_t alignment,
                             bool large,
                             bool executable) {
  initialize(size, alignment, large, NULL, 0, executable);
}

void ReservedSpace::initialize(size_t size, size_t alignment, bool large,
                               char* requested_address,
                               const size_t noaccess_prefix,
                               bool executable) {
  //校驗入參,size和alignment必須是經過對齊取整的
  const size_t granularity = os::vm_allocation_granularity();
  assert((size & (granularity - 1)) == 0,
         "size not aligned to os::vm_allocation_granularity()");
  assert((alignment & (granularity - 1)) == 0,
         "alignment not aligned to os::vm_allocation_granularity()");
  assert(alignment == 0 || is_power_of_2((intptr_t)alignment),
         "not a power of 2");

  alignment = MAX2(alignment, (size_t)os::vm_page_size());

  // Assert that if noaccess_prefix is used, it is the same as alignment.
  assert(noaccess_prefix == 0 ||
         noaccess_prefix == alignment, "noaccess prefix wrong");

  _base = NULL;
  _size = 0;
  _special = false;
  _executable = executable;
  _alignment = 0;
  _noaccess_prefix = 0;
  if (size == 0) {
    return;
  }

  // 判斷是否支持分配大內存頁
  bool special = large && !os::can_commit_large_page_memory();
  char* base = NULL;

  if (requested_address != 0) {
    requested_address -= noaccess_prefix; // adjust requested address
    assert(requested_address != NULL, "huge noaccess prefix?");
  }

  if (special) {
    //如果要求使用大內存頁但是操作系統不支持,則通過reserve_memory_special方法實現
    base = os::reserve_memory_special(size, alignment, requested_address, executable);
    //如果分配成功
    if (base != NULL) {
      if (failed_to_reserve_as_requested(base, requested_address, size, true)) {
        // OS ignored requested address. Try different address.
        return;
      }
      //校驗base地址是對齊的
      assert((uintptr_t) base % alignment == 0,
             err_msg("Large pages returned a non-aligned address, base: "
                 PTR_FORMAT " alignment: " PTR_FORMAT,
                 base, (void*)(uintptr_t)alignment));
      _special = true;
    } else {
      // 分配失敗,後面還會嘗試其他方法
      if (UseLargePages && (!FLAG_IS_DEFAULT(UseLargePages) ||
                            !FLAG_IS_DEFAULT(LargePageSizeInBytes))) {
        if (PrintCompressedOopsMode) {
          tty->cr();
          tty->print_cr("Reserve regular memory without large pages.");
        }
      }
    }
  }

  if (base == NULL) {
    //requested_address表示要求在某個地址上分配內存
    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 {
      //底層調用mmap函數分配內存
      base = os::reserve_memory(size, NULL, alignment);
    }

    if (base == NULL) return;

    //校驗分配的base的合法性
    if ((((size_t)base + noaccess_prefix) & (alignment - 1)) != 0) {
      // Base not aligned, retry
      if (!os::release_memory(base, size)) fatal("os::release_memory failed");
      // Make sure that size is aligned
      size = align_size_up(size, alignment);
      base = os::reserve_memory_aligned(size, alignment);

      if (requested_address != 0 &&
          failed_to_reserve_as_requested(base, requested_address, size, false)) {
        // As a result of the alignment constraints, the allocated base differs
        // from the requested address. Return back to the caller who can
        // take remedial action (like try again without a requested address).
        assert(_base == NULL, "should be");
        return;
      }
    }
  }
  // Done
  _base = base;
  _size = size;
  _alignment = alignment;
  _noaccess_prefix = noaccess_prefix;

  // Assert that if noaccess_prefix is used, it is the same as alignment.
  assert(noaccess_prefix == 0 ||
         noaccess_prefix == _alignment, "noaccess prefix wrong");

  assert(markOopDesc::encode_pointer_as_mark(_base)->decode_pointer() == _base,
         "area must be distinguisable from marks for mark-sweep");
  assert(markOopDesc::encode_pointer_as_mark(&_base[size])->decode_pointer() == &_base[size],
         "area must be distinguisable from marks for mark-sweep");
}

四、VirtualSpace

     VirtualSpace是與ReservedSpace配合使用的,ReservedSpace是預先分配一段連續的內存空間,VirtualSpace負責在這段內存空間內實際申請內存。VirtualSpace定義的屬性如下圖:

Reserved area就是通過ReservedSpace分配的地址空間範圍,Committed area就是通過VirtualSpace實際申請並使用的內存區域,_special和_executable屬性同ReservedSpace。下面的幾個lower,middle,upper屬性描述對應的三個內存區域。

重點關注其initialize方法的實現,如下圖:

bool VirtualSpace::initialize(ReservedSpace rs, size_t committed_size) {
  const size_t max_commit_granularity = os::page_size_for_region_unaligned(rs.size(), 1);
  return initialize_with_granularity(rs, committed_size, max_commit_granularity);
}

bool VirtualSpace::initialize_with_granularity(ReservedSpace rs, size_t committed_size, size_t max_commit_granularity) {
  if(!rs.is_reserved()) return false;  // allocation failed.
  assert(_low_boundary == NULL, "VirtualSpace already initialized");
  assert(max_commit_granularity > 0, "Granularity must be non-zero.");
  
  //Reserved area
  _low_boundary  = rs.base();
  _high_boundary = low_boundary() + rs.size();
  
  //Committed area,初始狀態下未分配內存,所以_high和_low一樣
  _low = low_boundary();
  _high = low();

  //這兩個的屬性取自rs
  _special = rs.special();
  _executable = rs.executable();

  
  _lower_alignment  = os::vm_page_size();
  _middle_alignment = max_commit_granularity;
  _upper_alignment  = os::vm_page_size();

  // End of each region
  _lower_high_boundary = (char*) round_to((intptr_t) low_boundary(), middle_alignment());
  _middle_high_boundary = (char*) round_down((intptr_t) high_boundary(), middle_alignment());
  _upper_high_boundary = high_boundary();

  // High address of each region
  _lower_high = low_boundary();
  _middle_high = lower_high_boundary();
  _upper_high = middle_high_boundary();

  //初始化,申請committed_size大小的內存,底層通過os::commit_memory方法申請
  if (committed_size > 0) {
    if (!expand_by(committed_size)) {
      return false;
    }
  }
  return true;
}

bool VirtualSpace::expand_by(size_t bytes, bool pre_touch) {
  if (uncommitted_size() < bytes) return false;

  if (special()) {
    // don't commit memory if the entire space is pinned in memory
    _high += bytes;
    return true;
  }

  char* previous_high = high();
  char* unaligned_new_high = high() + bytes;
  assert(unaligned_new_high <= high_boundary(),
         "cannot expand by more than upper boundary");

 
  char* unaligned_lower_new_high =
    MIN2(unaligned_new_high, lower_high_boundary());
  char* unaligned_middle_new_high =
    MIN2(unaligned_new_high, middle_high_boundary());
  char* unaligned_upper_new_high =
    MIN2(unaligned_new_high, upper_high_boundary());


  char* aligned_lower_new_high =
    (char*) round_to((intptr_t) unaligned_lower_new_high, lower_alignment());
  char* aligned_middle_new_high =
    (char*) round_to((intptr_t) unaligned_middle_new_high, middle_alignment());
  char* aligned_upper_new_high =
    (char*) round_to((intptr_t) unaligned_upper_new_high, upper_alignment());

  //判斷三個區域那個區域需要增長
  size_t lower_needs = 0;
  if (aligned_lower_new_high > lower_high()) {
    lower_needs =
      pointer_delta(aligned_lower_new_high, lower_high(), sizeof(char));
  }
  size_t middle_needs = 0;
  if (aligned_middle_new_high > middle_high()) {
    middle_needs =
      pointer_delta(aligned_middle_new_high, middle_high(), sizeof(char));
  }
  size_t upper_needs = 0;
  if (aligned_upper_new_high > upper_high()) {
    upper_needs =
      pointer_delta(aligned_upper_new_high, upper_high(), sizeof(char));
  }

  // Check contiguity.
  assert(low_boundary() <= lower_high() &&
         lower_high() <= lower_high_boundary(),
         "high address must be contained within the region");
  assert(lower_high_boundary() <= middle_high() &&
         middle_high() <= middle_high_boundary(),
         "high address must be contained within the region");
  assert(middle_high_boundary() <= upper_high() &&
         upper_high() <= upper_high_boundary(),
         "high address must be contained within the region");

  //按需分別增加三個內存區域的內存
  if (lower_needs > 0) {
    assert(low_boundary() <= lower_high() &&
           lower_high() + lower_needs <= lower_high_boundary(),
           "must not expand beyond region");
    //commit_memory的底層也是調用mmap,跟reserve_memory相比最大的不同是commit_memory指定了起始內存地址
    if (!os::commit_memory(lower_high(), lower_needs, _executable)) {
      debug_only(warning("INFO: os::commit_memory(" PTR_FORMAT
                         ", lower_needs=" SIZE_FORMAT ", %d) failed",
                         lower_high(), lower_needs, _executable);)
      return false;
    } else {
      _lower_high += lower_needs;
    }
  }
  if (middle_needs > 0) {
    assert(lower_high_boundary() <= middle_high() &&
           middle_high() + middle_needs <= middle_high_boundary(),
           "must not expand beyond region");
    if (!os::commit_memory(middle_high(), middle_needs, middle_alignment(),
                           _executable)) {
      debug_only(warning("INFO: os::commit_memory(" PTR_FORMAT
                         ", middle_needs=" SIZE_FORMAT ", " SIZE_FORMAT
                         ", %d) failed", middle_high(), middle_needs,
                         middle_alignment(), _executable);)
      return false;
    }
    _middle_high += middle_needs;
  }
  if (upper_needs > 0) {
    assert(middle_high_boundary() <= upper_high() &&
           upper_high() + upper_needs <= upper_high_boundary(),
           "must not expand beyond region");
    if (!os::commit_memory(upper_high(), upper_needs, _executable)) {
      debug_only(warning("INFO: os::commit_memory(" PTR_FORMAT
                         ", upper_needs=" SIZE_FORMAT ", %d) failed",
                         upper_high(), upper_needs, _executable);)
      return false;
    } else {
      _upper_high += upper_needs;
    }
  }

  if (pre_touch || AlwaysPreTouch) {
    //強制系統提前分配內存,即往對應的內存區域寫入數據
    os::pretouch_memory(previous_high, unaligned_new_high);
  }

  _high += bytes;
  return true;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章