執行時資料結構(1)-java堆
堆的記憶體劃分
整體劃分為年輕代和老年代細分如下:
1. eden 空間 ------------------ 年輕代
2. from survivor 空間 -------- 年輕代
3. to survivor 空間 ------------ 年輕代
4. old generation - 老年代
建立的物件存放在哪裡
當我們建立一個物件的時候,比如 new Student(); 這個可能存放在以下3個地方
-
eden 空間 這塊區域是物件建立最活躍的區域,當我們一般新建立的物件都會在這
-
TLAB(Thread Local Allocation Buffer) 執行緒分配緩衝區,我們知道執行緒與執行緒之間是相互隔離的,每個執行緒各自擁有的資料隨著執行緒結束而自動被回收,工作中遇到的絕大部分物件的建立是在方法上進行的方法呼叫結束其實這部分物件就沒有用了,如果物件建立在eden空間,那麼就得等到下次垃圾收集的時候進行回收,如果我們能在每個私有執行緒劃分一塊區域用來建立該執行緒方法中建立的物件,那麼隨著執行緒結束立即將物件銷燬就會大大減少GC的負擔。可以使用 -XX:+/-UseTLAB
- TLAB 是需要從堆中劃分指定一部分割槽域的,比如100KB,那麼以後每個執行緒訪問的時候就會預先申請100KB
- 當申請的這TLAB 100KB不夠用了,這個時候就需要再去向堆中申請一塊內容來用
如果同時存在多個執行緒,那麼久可能存在多個執行緒同時申請同一塊記憶體,所以再次申請TLAB記憶體的時候需要進行同步
-
棧(執行緒逃逸分析技術) 由於執行緒逃逸技術的不斷成熟,在堆上分配(TLAB本質也是使用堆記憶體)就變的不那麼絕對了,如果能夠確定執行緒呼叫方法棧幀中的物件確定不會被其它執行緒訪問到(不存線上程共享問題)那麼在棧上分配會變的更加高效因為會存在以下優點 — 這裡做一個大致的介紹
1. 隨著棧結束物件自動銷燬減少垃圾收集器的壓力 2. 就可以取消對物件原本對的同步操作
雖然有很多優點SUN公司也實現了,但是現目前還不成熟
物件記憶體分配方式,是執行緒安全的嗎?
通過上面的介紹我們瞭解到了,建立的物件會在哪一塊記憶體區域,那麼這塊區域是如何劃分出來的呢?如果多個執行緒建立物件會不會存線上程安全問題呢?下面來看看物件記憶體分配的具體方式主要有2種
- 指標碰撞: 假設我們的記憶體是規整的,用過的記憶體緊緊的挨在一起在左邊,沒有用過的記憶體在右邊,那麼在左邊使用過的記憶體的盡頭放一個指標,當新建立的物件需要分配記憶體的時候,往右邊移動相應的大小區域就可以了
- 空閒列表(free list):
那麼虛擬機器根據上面情況來採用以上2中分配方式呢?這就和我們的堆記憶體是否規整有關了,如果是規整的就採用指標碰撞否則採用空閒列表 那麼如何確定堆記憶體是否規整呢?這裡就要大概介紹一下垃圾收集的3個演算法
- 標記清楚法 進行GC的時候,首先會標記需要清除的物件,由於物件分佈在各個地方,那麼GC清除後,記憶體肯定就是不間斷的也就是不規整的了。
- 複製演算法 將記憶體劃分為大小相等塊,然後以每一小塊為單位,劃分出一個eden,2個 survivor ,建立物件分配記憶體的時候使用eden和其中一個survivor,當進行垃圾回收的時候一次性的把GC剩下的不規整的一個個的複製到另外一塊survivor上去,然後清空掉原來使用的區域。這樣記憶體就是規整的了。
- 標記整理法 這個步驟和標記清楚很想,區別是,標記了清楚的物件後,將存活以及不存活的想分別放在2端 然後清楚掉標記的物件,這樣記憶體也就是規整的了。
那麼問題又來了,虛擬機器GC時候,何時採用以上3中演算法呢?這就跟我們採用什麼垃圾收集器有關了,由於這裡主要是在討論堆就不詳述了,大家可以去看下GC垃圾收集器和演算法,我改天在寫一篇文章進行討論