理解Java記憶體回收_java

來源:互聯網
上載者:User

當程式建立對象、數組等參考型別的實體時,系統會在堆記憶體中為這一對象分配一塊記憶體,對象就儲存在這塊記憶體中,當這塊記憶體不再被任何引用變數引用時,這塊記憶體就變成垃圾,等待記憶體回收機制進行回收。記憶體回收機制具有三個特徵:

記憶體回收機制只負責回收堆記憶體中的對象,不會回收任何實體資源(例如資料庫連接,開啟的檔案資源等),也不會回收以某種建立對象的方式以外的方式為該對像分配的記憶體,(例如對象調用本地方法中malloc的方式申請的記憶體)
程式無法精確控制記憶體回收的運行,只可以建議記憶體回收進行,建議的方式有兩種System.gc() 和Runtime.getRuntime().gc()
在記憶體回收任何對象之前,總會先調用它的finalize()方法,但是同記憶體回收的時機一致,調用finalize()方法的時機也不確定。
針對以上三個特徵,有三個問題:

1、必須手動的進行清理工作,釋放除建立對象的方式以外的方式分配的記憶體和其它的實體資源。並且要注意消除到期的對象引用,否則可能引起OOM。

手動清理通常用到try...finally...這樣的代碼結構。

樣本如下:

import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.IOException;public class ManualClear { public static void main(String[] args) {  FileInputStream fileInputStream = null;  try {   fileInputStream = new FileInputStream("./src/ManualClear.java");  } catch (FileNotFoundException e) {   System.out.println(e.getMessage());   e.printStackTrace();   return;  }  try {   byte[] bbuf = new byte[1024];   int hasRead = 0;   try {    while ((hasRead = fileInputStream.read(bbuf)) > 0) {     System.out.println(new String(bbuf, 0, hasRead));    }   } catch (IOException e) {    e.printStackTrace();   }  } finally {   try {    fileInputStream.close();   } catch (IOException e) {    e.printStackTrace();   }  } }}

對於到期對象的引用,引起的OOM通常有三種常見的情況,這三種情況通常都不易發現,短時間內運行也不會有什麼問題,但是時間久了後,泄漏的對象增加後終會引起程式崩潰。

類自己管理記憶體時,要警惕記憶體流失
樣本如下:

import java.util.Arrays;import java.util.EmptyStackException;class Stack{ private Object[] elements; private int size; private static final int DEFAULT_INITAL_CAPACITY = 16;  public Stack() {  elements = new Object[DEFAULT_INITAL_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 class StackDemo {  public static void main(String[] args) {  Stack stack = new Stack();    for (int i = 0; i < 10000; i++) {   stack.push(new Object());  }    for(int i = 0; i < 10000; i++) {   stack.pop();  } }}

之所以會記憶體流失,是因為那些出棧的對象即使程式其它對象不再引用,但是Stack類中的elements[]數組依然儲存著這些對象的引用,導致這些對象不會被記憶體回收所回收,所以,當需要類自己管理記憶體事,要警惕內部維護的這些到期引用是否被及時解除了引用,本例中只需在出棧後,顯示的將

elements[size] = null;即可。

緩衝是要警惕記憶體流失
出現這樣情況通常是一旦將對象放入緩衝,很可能長時間不使用很容易遺忘,通常可以用WakeHashMap代表緩衝,在緩衝中的項到期後,他們可以被自動刪除。或者可以由一個後台線程定期執行來清除緩衝中的到期項。

監聽器或回調的註冊,最好可以顯示的取消註冊。
2、不要手動調用finalize(),它是給記憶體回收行程調用的

3、避免使用finalize()方法,除非用來作為判斷終結條件以發現對象中沒有被適當清理的部分;用來作為安全網在手動清理忘記調用的情況下清理系統資源,延後清理總別永不清理要強,並且如果同時記錄下忘記清理資源的資訊的話,也方便後面發現錯誤,並及時修改忘記清理的代碼;釋放對象中本地方法獲得的不是很關鍵的系統資源。

finalize()方法由於其執行時間以及是否確定被執行都不能準確確保,所以最好不用來釋放關鍵資源,但是可用於上面所說的三種情況。其中第一種情況,樣本如下:

class Book { boolean checkout = false; public Book(boolean checkout) {  this.checkout = checkout; }  public void checkin(){  checkout = false; }  @Override protected void finalize() throws Throwable {  if (checkout) {   System.out.println("Error: check out");  } }}public class FinalizeCheckObjectUse { public static void main(String[] args) {  new Book(true);  System.gc(); }}

執行結果:

Error: check out
例子中的Book對象,在釋放前必須處於checkIn的狀態,否則不能釋放,finalize中的實現可以協助及時發現不合法的對象,或者更直接的,在finalize中直接使用某個引用變數引用,使其重新進入reachable的狀態,然後再次對其進行處理。

另一點需要注意的時,子類如果覆蓋了父類的finalize方法,但是忘了手工調用super.finalize或者子類的finalize過程出現異常導致沒有執行到super.finalize時,那麼父類的終結方法將永遠不會調到。

如下:

class Parent{  @Override  protected void finalize() throws Throwable {    System.out.println(getClass().getName() + " finalize start");  }}class Son extends Parent{  @Override  protected void finalize() throws Throwable {    System.out.println(getClass().getName() + " finalize start");  }}public class SuperFinalizeLost {  public static void main(String[] args) {    new Son();    System.gc();  }}

運行結果:

Son finalize start
或者

class Parent{  @Override  protected void finalize() throws Throwable {    System.out.println(getClass().getName() + " finalize start");  }}class Son extends Parent{  @Override  protected void finalize() throws Throwable {    System.out.println(getClass().getName() + " finalize start");    int i = 5 / 0;    super.finalize();  }}public class SuperFinalizeLost {  public static void main(String[] args) {    new Son();    System.gc();  }}

執行結果:

Son finalize start
對於第二種情況,可以使用try...finally...結構解決,但是對於第一種情況,最好使用一種叫終結方法守護者的方式。樣本如下

class Parent2{  private final Object finalizeGuardian = new Object() {    protected void finalize() throws Throwable {      System.out.println("在此執行父類終結方法中的邏輯");    };  };}class Son2 extends Parent2{  @Override  protected void finalize() throws Throwable {    System.out.println(getClass().getName() + " finalize start");    int i = 5 / 0;    super.finalize();  }}public class FinalizeGuardian {  public static void main(String[] args) {    new Son2();    System.gc();  }}

執行結果:

在此執行父類終結方法中的邏輯
Son2 finalize start
這樣可以保證父類的終結方法中所需做的操作執行到。

以上就是本文的全部內容,希望對大家的學習有所協助。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.