Java內(nèi)存區(qū)域
Java虛擬機(jī)在執(zhí)行Java程序時, 會把管理的內(nèi)存, 分為幾個區(qū)域;
平時我們一般粗略的分成棧和堆; 棧存放對象引用指針, 堆則存放棧中指針指向的對象;
Java虛擬機(jī)棧
- 線程私有, 生命周期和Thread相同;
- 虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型: 每個方法執(zhí)行時,都會創(chuàng)建一個棧幀, 用于存儲局部變量表,操作數(shù)棧,動態(tài)鏈接,方法出口等信息;每一個方法執(zhí)行,都對應(yīng)一個棧幀在虛擬機(jī)棧中入棧到出棧;
- 平時我們說的棧,即局部變量表; 它存放了編譯器可知的各種基本數(shù)據(jù)類型, 和對象引用等
Java堆
- Java堆是被所有線程共享內(nèi)存區(qū)域, 在虛擬機(jī)啟動時創(chuàng)建;
- 用來存放對象實例, 幾乎所有的對象實例和數(shù)組都在此分配內(nèi)存;
- Java堆是內(nèi)存回收的主要區(qū)域,很多時候稱之為GC堆
方法區(qū)
- 各個線程共享的內(nèi)存區(qū)域
- 用來存放被虛擬機(jī)加載的類信息,常量,靜態(tài)變量,編譯器編譯后的代碼等
- 也稱之為Non-Heap(非堆), 自己程序開發(fā)一般不關(guān)注 - -
垃圾收集器
gc收集時, 需要完成3件事:
- 哪些內(nèi)存需要回收
- 什么時候回收
- 如何回收
判斷那些內(nèi)存需要回收
回收時,肯定要確定哪些對象要被回收,要判斷哪些對象已死(即不可達(dá));
引用計數(shù)法
給對象一個引用計數(shù)器, 每當(dāng)一個地方引用它時, 計數(shù)器加1; 當(dāng)引用失效時, 計數(shù)器減1; 任何時候計數(shù)器為0的對象, 即不可達(dá)對象, 可被回收;
引用技術(shù)簡單, 但遇見相互引用時, 無法判定; eg, A引用B, B同時引用A, A和B都不再有其他任何引用, 實際對象不可訪問, 但是引用計數(shù)器卻不為0;
可達(dá)性分析
通過一系列GC Root對象作為起點, 從這些節(jié)點向下搜索,搜索走過的路徑稱之為引用鏈, 當(dāng)一個對象到GC Root沒有任何引用鏈來連接時, 則此對象不可達(dá), 可以用來回收;
而用來作為GC Root的對象有下面幾種:
- 虛擬機(jī)棧,即本地變量表中引用的對象
- 方法區(qū)中靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
- 本地方法棧JNI, 即Native方法引用的對象
垃圾收集算法
標(biāo)記-清除算法
Mark-Sweep算法,如同名稱一樣, 分標(biāo)記和清楚兩步: 首先標(biāo)記出需要回收的對象, 在標(biāo)記完成后, 統(tǒng)一回收所有被標(biāo)記的對象;
主要有2個不足: 一是效率, 標(biāo)記和清除效率都不高; 二是空間問題, 清除后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片, 空間碎片太多,可能導(dǎo)致程序運行時, 需要分配較大內(nèi)存的對象時, 無法找到足夠的連續(xù)內(nèi)存而不得不在一次觸發(fā)gc;
復(fù)制算法
為了解決效率問題, Copying的收集算法出現(xiàn)了, 將內(nèi)存分為容量相等的兩塊, 每次只使用其中一塊, 當(dāng)一塊內(nèi)存用完時, 就將還存活著的對象復(fù)制到另一塊上去, 然后把已使用過的內(nèi)存一次清理;
標(biāo)記-整理算法
先需要從根節(jié)點開始對所有可達(dá)對象做一次標(biāo)記,但之后,它并不簡單地清理未標(biāo)記的對象,而是將所有的存活對象壓縮到內(nèi)存的一端。之后,清理邊界外所有的空間。這種方法既避免了碎片的產(chǎn)生,又不需要兩塊相同的內(nèi)存空間;
分代收集算法
將所有的新建對象都放入稱為年輕代的內(nèi)存區(qū)域,年輕代的特點是對象會很快回收,因此,在年輕代就選擇效率較高的復(fù)制算法。當(dāng)一個對象經(jīng)過幾次回收后依然存活,對象就會被放入稱為老生代的內(nèi)存空間。對于新生代適用于復(fù)制算法,而對于老年代則采取標(biāo)記-整理,或者標(biāo)記-清除算法
垃圾收集器
收集器是真正實現(xiàn)收集算法的地方,有各種版本, 主要說下CMS
CMS收集器
Concurrent Mark Sweep, 名稱可以看出是基友標(biāo)記-清除算法的收集器; 為了使用回收停頓時間(Stop The World`)最短為目標(biāo)的收集器;
工作流程分為下面幾個步驟:
- 初始標(biāo)記, CMS initial mark
- 并發(fā)標(biāo)記, CMS concurrent mark
- 重新標(biāo)記, CMS remark
- 并發(fā)清楚, CMS concurrent sweep
其中,初始標(biāo)記和重新標(biāo)記需要Stop The World. 初始標(biāo)記僅標(biāo)記GC Root能直接關(guān)聯(lián)的對象; 并發(fā)標(biāo)記即進(jìn)行初始標(biāo)記后的引用可達(dá)遍歷; 重新標(biāo)記則是修正并發(fā)標(biāo)記期間程序繼續(xù)運行而導(dǎo)致標(biāo)記變動的對象;