上篇文章我們介紹了JVM所管理的內存結構也就是運行時數據區(Run-Time Data Areas),現在我們將介紹JVM的內存分配與回收靜態內存分配與動態內存分配
JVM的內存分配主要分為兩種:靜態內存分配與動態內存分配與之對應的是基本類型內存分配與對象內存分配;1、靜態內存分配靜態內存分配在編譯時已確定好內存空間,程序載入時JVM把一次內存分配給它,此后不會再發生變化。這些內容包括:方法中的局部變量(基本數據類型)、類變量(基本數據類型)、對象的引用;對于方法中的局部變量是存儲在java棧的局部變量表中,方法執行結束棧幀出棧,局部變量也會跟著收回內存空間;而類變量是存儲在方法區中的,這里的內存回收時間是不確定的。2、動態內存分配Java里給對象分配內存是動態分配的,編譯的時候并不能確定對象的存儲空間大小,只是創建對象時才能進行內存空間分配,而內存空間的回收是在對象不再被引用時才能回收。對象是存儲在堆中,只有當GC觸發時才清理回收那些沒有被引用的對象的內存空間;如下面代碼段;public class Test {byte[] bytes=new byte[1024*1024*5];long a=1000;public static void main(String[] args){long b=2;byte[] base=new byte[1024*1024*1];Test t=new Test();}}bytes:是數組內存分配在堆中的,但引用存儲才方法區中,JVM將使用動態內存分配,編譯時無法確定存儲空間只有當創建test對象時才為bytes分配存儲空間,只有當該對象沒有被引用時才能被GC回收;a:是類變量,基本數據類型,當Test編譯載入時就確定了存儲空間,存儲在方法區中;b:是方法中的基本數據類型局部變量只有執行該方法時才在棧幀的局部變量表中分配存儲空間方法執行完成后就釋放該棧幀,也回收局部變量表中的存儲空間;base:局部變量,只有執行到該語句時才在堆中為它分配存儲空間,引用存儲在棧幀局部變量表中沒有被引用時才能被GC回收;t:對象與數組一樣在堆中分配存儲空間,但對象引用存儲在棧幀局部變量表中,對象沒有被引用時GC才能回收存儲空間;
確定是否可被回收接下來我們將介紹JVM的內存回收,JVM在進行垃圾回收時首先要做的是確定哪些是垃圾,然后才能回收垃圾所占用的內存空間。上面我們說過,對象要是被回收內存的前提是該對象沒被活動的對象引用,那JVM是怎么知道一個對象是否被引用的呢。一般用于確定對象是否被引用的算法有兩種:引用計數算法、根搜索算法;引用計數算法(Reference Counting):在對象中添加個引用計數器,當有引用時對象計數器加一,引用消失時,計數器減一,只要引用計數器為0時該對象就是沒有被引用的,比少編程語言使用該算法,如:python等。根搜索算法(GC Roots Tracing):Java、C#等使用該算法判斷對象是否存活,該算法從一系列為“GC Roots”的對象為起點,從這些節點開始搜索,走過的路徑為引用鏈(Reference Chain),當對象到GC Roots無任何引用鏈時,此對象就是不可用的,此不可用的對象將作為回收的對象。
1、2、3為活動對象,三個對象到GC Roots均有引用鏈 4、5 為死對象,對象到GC Roots不可達
Java中,可用于作為GC Roots的對象有:? 虛擬機棧的棧幀中局部變量表中引用的對象? 本地方法棧中的引用對象? 方法區中常量引用的對象? 方法區的中的類靜態屬性引用的對象? 類的Class對象的引用
垃圾收集算法1、 標記-清除算法(Mark-Sweep),算法分為了標記、清除兩個階段,他根據我們前面所講的方法判斷對象是否可被回收,如可回收則標記起來,待標記完所有對象后統一進行回收;2、 復制算法(Copying)復制收集算法把內存分為兩塊容量相同的空間,每次只使用一快,當一快用完時就將還存活的對象復制到另一塊中,然后把原來使用過的內存空間進行回收。3、 標記-整理算法(Mark-Compact)該算法與標記清除算法一樣首先對要清理的對象進行標記,不同的是,接下來他將存活的對象往一端移動,然后回收該端邊界以外的內存空間。4、 分代收集算法(Generational Collection),現在不少虛擬機采用該算法,如:HotSpot,該算法把對象按不同的存活周期將內存瓜分為幾個區域。如:HotSpot把堆分為新生代與老年代,然后根據各個區域的特點采用比較適合的收集算法。一般新生代對象生命周期比較短,所以采用復制算法;而老年代對象生命周期較長,所以比較適合采用標記-清理、標記-整理算法來回收。
文章首發地址:Solinx
http://www.solinx.co/archives/45
新聞熱點
疑難解答