心血來潮之 棧 和 堆 的思考

源頭

常常看到一些編程語言的書上有說,程序運行過程當中,會涉及到 棧(Stack)和 堆(Heap)。尤爲遇到涉及調查內存泄漏的場景時,書本會提醒讓開發人員查看 堆 的狀況javascript

基於上述現象,來了兩個疑問:java

  • 程序執行過程當中,爲何常常會涉及到 棧 和 堆 兩個數據結構,他們一般的做用是什麼?c++

  • 選用 棧 能夠理解,但爲何選用 堆 呢?對比其餘的數據結構,在程序執行過程當中,堆 的優點是什麼?編程

相關資料

(資料主要來源於 stackoverflow,進行消化以及整理)數據結構

關於 堆棧,首先引入兩個解釋:多線程

  • 不管是 堆 仍是 棧,它們都是底層操做系統分配的內存區域
  • 在多線程環境中,每一個線程擁有獨立的棧,但它們會共享一個堆。面向堆數據的併發訪問,將會受到嚴格的控制

關於堆

  • 堆包含着一個列表,記錄着哪些區塊是空閒的,哪些是被使用的。在堆上分配新的內容,意味着從空閒的區塊中,選擇合適的區塊進行分配。這個過程須要堆更新自身的區塊記錄列表。區塊記錄列表做爲元數據,會在堆上被一直記錄着
  • 隨着堆越長越大,堆的大小可能會超過已經分配的大小,這時候應用程序須要向底層系統申請內存空間對堆進行擴展
  • 在堆上進行屢次內存分配或者回收,會致使堆的連續內存空間上有許多空閒的區塊分散在被應用的區塊之間,這時候,若是須要堆分配一個大塊區塊,可能一會兒沒法分配,須要對堆進行區塊的從新整理
  • 若是堆上一個使用中的區塊臨近一個空閒區塊,當使用中的區塊被釋放,該區塊會直接與相鄰的空閒區塊進行合併,下降堆上的內存進行碎片化整理的頻率
  • 堆 的內存劃分在不一樣的系統或者 runtime 上並不徹底一致,因此,編程語言書中提到的 堆(heap)並非徹底相同的東西,只是抽象概念統一
  • 重要:堆內存 和 堆結構 其實指的是兩個東西,此 Heap 非彼 heap。操做系統 和 runtime 上常常提到的 堆 其實是指 堆內存:一段特別組織的內存,而不是指 堆 數據結構。參考自 stackoverflow 的回答 why are two different concepts both called "heap?"

關於棧

  • 計算機 cpu 有專門的硬件結構針對棧進行操做,棧的操做在 cpu 上會很是高效快速
  • 當一個函數被 cpu 調用時,調用該函數的指令地址會被壓棧,而後 cpu 的執行指針會指向函數的開始指令的地址
  • 當 cpu 進入一個函數前,函數的對應的傳參也會被進行壓棧,進入函數後,傳參出棧
  • 當 cpu 離開函數前,函數的返回會被壓棧,離開後,返回結果出棧,此時函數的調用方能夠得到函數的返回數據
  • 根據上述的過程,計算機能夠支持函數的互相調用 或者 遞歸調用,固然,整個過程須要消耗棧的空間,而棧的大小並非無限大的,函數調用層次過深,就會出現咱們一般說的爆棧現象

回答疑問

根據上述資料可知,上述的兩個疑問提到的 堆 其實都不是對應數據結構中的堆的概念,因此問題自己是不許確的~架構

至於 棧 和 堆 在程序運行過程當中起到什麼樣的做用,詳細過程又是怎麼樣的,上述資料已經能夠窺探一二,而其中的細節,就不是這個篇章能夠容納的了(並且不一樣的系統架構,不一樣的 runtime 細節上又不同),就不在這裏展開細述了。併發

追加思考:堆在 javascript runtime 中是怎麼表現的呢?

參考 runtime:v8 引擎

查看源碼

  1. 先看一下 v8 源碼中的 heap 頭文件,能夠看到有個 HeapStats 類,對 heap 進行描述
class HeapStats {
 public:
  static const int kStartMarker = 0xDECADE00;
  static const int kEndMarker = 0xDECADE01;

  intptr_t* start_marker;                  // 0
  size_t* ro_space_size;                   // 1
  size_t* ro_space_capacity;               // 2
  size_t* new_space_size;                  // 3
  size_t* new_space_capacity;              // 4
  size_t* old_space_size;                  // 5
  size_t* old_space_capacity;              // 6
  size_t* code_space_size;                 // 7
  size_t* code_space_capacity;             // 8
  size_t* map_space_size;                  // 9
  size_t* map_space_capacity;              // 10
  size_t* lo_space_size;                   // 11
  size_t* code_lo_space_size;              // 12
  size_t* global_handle_count;             // 13
  size_t* weak_global_handle_count;        // 14
  size_t* pending_global_handle_count;     // 15
  size_t* near_death_global_handle_count;  // 16
  size_t* free_global_handle_count;        // 17
  size_t* memory_allocator_size;           // 18
  size_t* memory_allocator_capacity;       // 19
  size_t* malloced_memory;                 // 20
  size_t* malloced_peak_memory;            // 21
  size_t* objects_per_type;                // 22
  size_t* size_per_type;                   // 23
  int* os_error;                           // 24
  char* last_few_messages;                 // 25
  char* js_stacktrace;                     // 26
  intptr_t* end_marker;                    // 27
};
複製代碼

從狀態類上,咱們能夠看到,有針對 新生代 和 老生代 的描述:new_space_size,new_space_capcacity,old_space_size,old_space_capacity,可知 javascript 的堆內存參考過 jvm 的堆內存管理模式,以便優化內存回收的效率app

  1. 查看 objects.h 文件,從該文件的註釋描述中,能夠看到有對 HeapObject 的描述,以下:
//
// Most object types in the V8 JavaScript are described in this file.
//
// Inheritance hierarchy:
// - Object
// - Smi (immediate small integer)
// - HeapObject (superclass for everything allocated in the heap)
// - JSReceiver (suitable for property access)
// - JSObject
// - JSArray
// - JSArrayBuffer
// - JSArrayBufferView
// - JSTypedArray
// - JSDataView
// - JSBoundFunction
// - JSCollection
// - JSSet
// - JSMap
// - JSDate
// - JSFunction
// - JSGeneratorObject
// - JSGlobalObject
// - JSGlobalProxy
// - JSMapIterator
// - JSMessageObject
// - JSModuleNamespace
// - JSPrimitiveWrapper
// - JSRegExp
// - JSSetIterator
// - JSStringIterator
// - JSWeakCollection
// - JSWeakMap
// - JSWeakSet
// - JSCollator // If V8_INTL_SUPPORT enabled.
// - JSDateTimeFormat // If V8_INTL_SUPPORT enabled.
// - JSListFormat // If V8_INTL_SUPPORT enabled.
// - JSLocale // If V8_INTL_SUPPORT enabled.
// - JSNumberFormat // If V8_INTL_SUPPORT enabled.
// - JSPluralRules // If V8_INTL_SUPPORT enabled.
// - JSRelativeTimeFormat // If V8_INTL_SUPPORT enabled.
// - JSSegmenter // If V8_INTL_SUPPORT enabled.
// - JSSegmentIterator // If V8_INTL_SUPPORT enabled.
// - JSV8BreakIterator // If V8_INTL_SUPPORT enabled.
// - WasmExceptionObject
// - WasmGlobalObject
// - WasmInstanceObject
// - WasmMemoryObject
// - WasmModuleObject
// - WasmTableObject
// - JSProxy
複製代碼

能夠看到,v8 在執行 js 過程當中,runtime 內部產生的 js 對象會繼承自 HeapObject 這個類,而 HeapObject 會根據策略從 runtime 的 堆 中進行 存儲、移動、釋放等操做jvm

咱們也知道,javascript 的數據類型會區分 基本類型(String,Number,Boolean,Null,Undfined,Symbol) 和 引用數據類型(Object,Array,Function)

從上面的註釋能夠看出來,基本數據類型 並無繼承自 HeapObject,在 js 的執行過程當中,它們會被記錄到 棧(Stack)中,當函數執行完畢後,即從棧中退出。而引用數據類型 則繼承自 HeapObject,在執行過程當中,從堆中存儲,再也不被引用後,將會等待 GC 依據策略將其清理

相關文章
相關標籤/搜索