一、背景:
Java程序員編寫程序時,對於新建的對象,當不再需要此對象時,不必去釋放這個對象所佔用的空間,這個工作是由Java虛擬機自己完成的 ,即內存回收或垃圾回收。
二、如何知道一個對象所佔用的空間可以回收了呢?
1.常用的一種算法是引用計數法,如果一個對象的引用爲0了,那就可以回收了。但是對於這種方法致命缺陷,當對象之間存在循環引用的時候,A引用B,B引用A,這樣A和B的引用就一直不會爲0,那就無法回收了。
2、Java採用的算法爲根搜索算法,以一系列GC Roots爲起點,向下搜索,如果存在引用,則對象依然在用,不能回收;如果不存在引用,則可以回收。
可以作爲GC Roots的有如下幾種:
a、虛擬機棧(棧幀中的本地變量表)中的引用的對象
b、方法區中的類靜態屬性引用的對象
c、方法區中的常量引用的對象
d、本地方法棧中JNI的引用的對象
三、java堆棧內存結構劃分:
1.新生代(Young Generation):又細分爲三塊(eden,S0,S1)
新的對象總是在新生代創建。
2.老年代(Old Generation)
在新生代存放一定時間之後,在新生代空間不夠時,對象會被移到老年代。
3.永久代(Permanent)
用於存在類信息。對於動態生成的類,後續不會再使用到,所以可以進行回收。
四、爲什麼採用分代(區)的方式來進行堆棧空間的管理呢?
五、主要的空間回收算法:
1.複製算法
對於新生代,劃分了eden/S0/S1三塊區域,開始對象創建在eden區域上。
下面來描述一下可能的操作過程,各個java虛擬機的實現會有差異,下面是大致進行闡述。
第一次清理的時候,將可以清除的對象刪除,將要保留的對象都複製到S0區域,eden區域清空,對象年齡標記增長1;
第二次清理的時候,將可以清除的對象刪除,將要保留的對象從eden和S0都複製到S1,並將eden和S0都清空,對象年齡標記增長1;
第三次清理的時候,將可以清除的對象刪除,將要保留的對象從eden和S1都複製到S0,並將eden和S1都清空,對象年齡標記增長1;
後續清理的時候,以此重複上面的步驟,當對象的年齡達到一定值時,就被移到老年代中去。
對於上面的過程,可以通過jvisualvm觀測到,會發現eden和S0同時被清空,對象轉移到S1,或eden和S1同時被清空,對象轉移到S0.
下面給出兩張jvisualvm的截圖:
時刻1:
時刻2:
時刻3:
通過上面三張圖對比可以看到,當S0被佔用時,S1就是空的;S1被佔用時,S0就是空的,這也就印證了上面算法描述裏說的信息。
2.標記清除算法(包括整理)
因爲老年代中,可以回收的對象很少,所以觸發對老年代的回收機率也比較低,對於老年代也不採用複製算法,因爲那樣空間利用率比較低,而是採用標記清除(整理)方式。如果對應對象的空間可以回收了,就進行回收,並對對象存儲位置進行移動,將對象移到到一起,避免中間存在空隙,便於後續內存分配。
六、幾個可以用於指定java虛擬機各個塊大小的參數:
-XX:PermSize 設置永久代初始大小(樣例:-XX:PermSize=40M)
-XX:MaxPermSize 設置永久代最大大小(樣例:-XX:MaxPermSize=100M)
(注:java對此參數值限制,具體限制可能因操作系統、CPU/內存差異而又不同,在Window7上測試時,最小值爲12M;不能設置爲奇數,必須爲偶數)
-XX:SurvivorRatio 設置新生代中eden和survivor區(S0/S1)的比例,通過驗證,實際大小比例爲該值*1.2,如設置爲8,則eden空間大小/S0=8*1.2(樣例:-XX:SurvivorRatio=8)
-Xmx 設置堆棧最大值,包括老年代和新生代,不包括永久代(樣例-Xmx100M)
-Xms 設置堆棧初始大小(樣例:-Xms50M)
-XX:NewRatio 老年代和新生代的大小比例,設置此參數時不能設置MaxNewSize,否則此參數失效(樣例:-XX:NewRatio=2)
參考資料:
http://www.oracle.com/webfolder/technetwork/tutorials/obe/java/gc01/index.html
http://www.open-open.com/lib/view/open1380593930103.html
http://blog.csdn.net/time_hunter/article/details/12405127