一、在理解垃圾回收前需要了解一個對象在堆內存中的引用狀態,分爲三種:
1.可達狀態:對象創建後,有一個或以上的引用變量引用它。
2.可恢復狀態:程序中某個對象不再有任何引用變量引用它,但是還是有可能重新被其他引用變量引用。
3.不可達狀態:對象與所用引用變量的引用都被切斷,且jvm執行finalize()方法進行資源清理後沒有成爲可達狀態,那麼這個對象將永久的失去引用,編程不可達狀態。(GC真正回收的正是處於這種狀態的對象)。
public void test(){
//此處對象爲可達狀態
String a=new String("this is a string object");
//此處a引用只想了另一個變量,則上面的string對象成爲可恢復狀態
a=new String("another string object");
}
二、垃圾回收有如下機制:
1.垃圾回收只負責回收堆內存中的對象,不會回收任何物理資源(如:數據庫連接,網絡IO等)。
2.程序無法精確控制垃圾回收的運行,垃圾回收只有再合適的時候進行。對象成爲不可達狀態後,JVM會再合適的時候回收該對象。
3.垃圾回收任何對象前之前總會調用這個對象的finalize方法,進行資源清理。finalize方法可能使該對象重新成爲可達狀態,從而使使JVM取消對它的回收。
三、強制垃圾回收:
強制垃圾回收其實是建議JVM立即進行GC,但JVM是否進行gc操作是不確定的,垃圾回收機制只是在接收到通知儘快進行垃圾回收。強制垃圾回收有如下兩種方式,
System.gc();
Runtime.getRuntime().gc();
四、理解finalize方法:
系統在垃圾回收前總會調用finalize方法進行資源清理,這個操作和垃圾回收操作是一起發生但要先於GC。只有程序任務需要額外的內存時纔會進行GC。finalize有如下特點:
1.不要主動調用finalize方法,該方法應交給垃圾回收機制調用。
2.finalize方法何時執行無法確定。
3.JVM執行finalize方法時,有可能將對象從可恢復狀態變成可達狀態。
4.JVM執行finalize方法時,垃圾回收機制不會報錯,程序繼續執行。
如下例子說明垃圾回收的不確定性:
public class FinalizeTest {
private static FinalizeTest ft=null;
public void info(){
System.out.println("調用系統的finalize方法");
}
public static void main(String[] args) throws InterruptedException {
//創建對象語句也會進行垃圾回收
new FinalizeTest();
//通知系統立即進行垃圾回收
System.gc();//1
// Thread.sleep(2000);//3
ft.info();
}
//重寫了finalize方法
public void finalize(){
ft=this;
}
}
上述例子中註釋掉3處代碼可以發現程序報空指針異常,取消註釋後,程序打印“ 調用系統的finalize方法”,說明程序顯示執行垃圾回收,但finalize方法不會立即執行,當延遲2s後發現垃圾回收執行了。如果在1和3處代碼中間加入下列代碼則可強制調用finalize方法。
//強制垃圾回收調用可恢復對象的finalize方法,執行此方法前一定先通知系統進行垃圾回收操作
// Runtime.getRuntime().runFinalization();
System.runFinalization();//2
五、是否應該在程序中顯示調用垃圾回收?
首先你需要知道兩個概念:
JVM的gc有minor GC和major GC(也就是大家說的Full GC),JVM會很頻繁的做minor GC,如果內存塊佔滿的話,JVM會做Full GC,Full GC是對整個JVM內存堆做GC,所示耗時比minor GC要長很多。System.gc()最終執行的是Full GC
1,服務器老是崩潰是因爲內存很快被佔滿,在允許的條件下你可以適當加大內存配置,用mx和ms控制內存堆大小
-Xms
-Xmx
2,如果你的程序需要通過System.gc()來減少JVM崩潰的機率,那麼你的程序10有8,9存在問題,需要從你程序本身去進行優化
3,可以把GC配置爲CMS回收方式,以提高回收效率
-XX:+UseConcMarkSweepGC
-XX:+UseParNewGC
-XX:+UseParNewGC
-XX:CMSFullGCsBeforeCompaction=0
-XX:CMSInitiatingOccupancyFraction=75
-XX:+CMSParallelRemarkEnabled
-XX:+CMSPermGenSweepingEnabled
-XX:+CMSClassUnloadingEnabled
4,-XX:+PrintGCTimeStamps -XX:+PrintGCDetails -XX:+PrintHeapAtGC用這些參數輸出GC日誌,然後分析,
通過上述描述可知道,System.gc()最終執行的是Full GC,執行時會對整個JVM堆內存做GC,這將給程序帶來一個嚴重的性能損失。一般不建議這麼做。
六:爲防止內存泄露,對象引用用完後都清空?
清空對象引用應該是一種例外,而不是一種規範行爲,這樣做會使代碼很亂,一般而言只要類是自己管理內存,程序員就應該警惕內存泄露問題。
要防止內存泄露,下面是一些快速上手的實用技巧:
1. 當心集合類,比如 HashMap,ArrayList等,因爲這是最容易發生內存泄露的地方.當集合對象被聲明爲static時,他們的生命週期一般和整個應用程序一樣長。
2. 注意事件監聽和回調.當註冊的監聽器不再使用以後,如果沒有被註銷,那麼很可能會發生內存泄露.
3. "當一個類自己管理其內存空間時,程序員應該注意內存泄露." 常常是一個對象的成員變量需要被置爲null 時仍然指向其他對象。
下面舉一個模擬棧的例子:
public class Stack {
private Object[] elements;
private int size=0;
private static int CAPACITY=16;
public Stack(){
elements=new Object[CAPACITY];
}
public void push(Object e){
ensureCapacity();
elements[size++]=e;
}
public Object pop(){
if(size==0)
throw new EmptyStackException();
return elements[--size];
}
private void ensureCapacity(){
if(elements.length==size){
elements=Arrays.copyOf(elements, 2*size+1);
}
}
}
此例子會有一個內存泄露的現象,從棧中彈出來的對象不會當做垃圾回收,即使棧不再引用這些對象,這是因爲,棧內部還維護者這些彈出對象的過期引用。所謂過期引用是指永遠不會被解除的引用。隨着時間推移,無法回收對象越來越多,程序性能越來越差最後可能導致內存溢出。此時可以做如下處理:
public Object pop2(){
if(size==0)
throw new EmptyStackException();
Object result= elements[--size];
elements[size]=null;
return result;
}
一旦數組元素變成非活動的一部分,就手動清空這些元素。
七、對象的軟、弱和虛引用,
正常程序中的引用變量爲強引用。當程序中需要避免在執行期間將對象留在內存中,可以使用如下幾種引用:
1.軟引用:當系統內存空間不足時,會回收它,但空間足夠時,不會回收,程序也可以使用改對象。
2.弱引用:跟軟引用類似,比軟引用級別耕地,當GC操作時,不管內存是否足夠,對象總會回收。
3.虛引用:和沒有引用效果大致相同,主要用於跟蹤對象垃圾回收狀態,不能單獨使用,必須和引用隊列(ReferenceQueue)聯合使用。