Java內存泄漏和垃圾收集器是什麼樣的關係呢 Java內存泄漏 內存太多

在這篇博文中,我想詳細介紹一下 java.lang.OutOfMemoryError 錯誤這個錯誤是如何在Java應用程序中發生的。

在前面的條目中,我們看到 **OutOfMemoryError **有完全不同的類型。然而,最常見的錯誤是

Exception in thread "main": java.lang.OutOfMemoryError: Java heap space

此錯誤意味着堆上不再有足夠的可用內存來填充新對象的內存請求,即不能在堆上生成新對象。由於根據JVM規範,每個堆都必須有一個垃圾收集器,這也意味着它不能再清空任何內存,堆被“活動”對象完全佔用。

爲了更好地理解這種情況是如何產生的,我首先要描述什麼是Java中的“活動”對象。

在Java中,對象是在堆上創建的,只要它們仍然被引用,就一直存在。垃圾收集器在GC階段檢查對象是否仍然被引用,如果沒有,垃圾收集器會將其標記爲“垃圾”,並在稍後進行清理(還有其他GC算法,例如複製收集器或垃圾優先方法,但這些方法與理解無關)。然而,並不是每個引用都對對象的生存起決定性作用,只有所謂的GC根引用才起決定性作用。特別是在與Java內存泄漏相關的情況下, GC ROOT 是一箇中心概念,您必須理解它才能識別對對象的關鍵引用。垃圾收集器根是未詳細引用的對象,負責將引用的對象保留在內存中。如果一個對象沒有被GC根直接或間接引用,它將被標記爲“不可訪問”並被釋放到垃圾收集。垃圾收集根有三種類型:

  • 線程堆棧上的臨時變量
  • 類的統計變量
  • JNI中的特殊本機引用

這個具體的例子是最好的方式來說明這一點:

public class MyFrame extends javax.swing.JFrame {

  // reachable via Classloader as soon class is loaded
  public static final ArrayList STATIC = new ArrayList();

  // as long as the JFrame is not dispose()'d,
  // it is reachable via a native window
  private final ArrayList jni = new ArrayList()

  // while this method is executing parameter is kept reachable,
  // even if it is not used
  private void myMethod(ArrayList parameter) {

    // while this method is running, this list is reachable from the stack
    ArrayList local = new ArrayList();
  }

}

基本上,您可以在堆中看到與 Java OutOfMemoryError 問題相關的三個不同問題區域:

  • 仍然具有GC根引用但從未在應用程序代碼中使用的對象。這是 Java內存泄漏
  • 對象太多或太大。意味着沒有足夠的堆可用於執行應用程序,因爲內存中保存了太大的對象樹(例如緩存)。
  • 臨時對象太多。意味着Java代碼中的處理暫時需要太多內存。

Java內存泄漏

當對象仍然具有GC根引用,但在應用程序中不再使用時,就會產生Java內存泄漏。這些“遊蕩對象”證明了JVM內存的完整持續時間。如果在應用程序邏輯中連續創建這樣的“對象體”,典型的問題子對象是靜態集合,它們被用作一種緩存。 add() 和 remove() 方法在這裏使用的頻率是多少。添加的對象被靜態集合項引用,並且由於GC根引用(static)而不能再釋放。

在內存泄漏的上下文中,也經常提到所謂的支配者或支配樹。

支配者的概念來源於圖論,當一個節點只能到達另一個節點時,它就被定義爲另一個節點的支配者。因此,當沒有其他對象C引用B時,對象A是另一個對象B的支配者。支配者樹則是一個子樹,其中來自根節點的條件應用於所有子節點。如果根引用被釋放,整個支配樹將被釋放。因此,在內存泄漏搜索中,非常大的控制樹是非常好的候選。

根據不再需要的對象的生成頻率和大小,以及Java堆的配置大小,OutOfMemoryError遲早會發生。正是後一種變體,即所謂的“爬行內存泄漏”,在許多應用程序中都會發現,而且這些問題通常會被“忽略”,並且會遇到以下措施:

更大的堆來爭取時間,直到錯誤發生。不幸的是,在64位jvm時代,這種方法正變得越來越流行。

晚上重啓應用服務器。這將導致內存重置。如果內存在24小時內沒有完全填滿,可以通過重新啓動來避免錯誤。

這兩個版本都是危險的,因爲它們對性能有負面影響,並且有可能由於用戶行爲的改變或更多的通信量而導致錯誤比預期更快地發生。性能也受到垃圾收集器的負面影響,因爲越來越滿的“終身生成”意味着GC必須經歷更多的對象,“標記”階段需要越來越多的時間,隨着大量堆,要分析的對象的數量變得更大。因此,本系列文章將詳細分析這些內存泄漏,以避免出現這種情況。

內存太多

還有一些情況下,堆中的OutOfMemoryError不是由實際意義上的內存泄漏引起的,而是應用程序消耗了太多內存。在這種情況下,要麼選擇的堆太小,必須將其放大,要麼必須減少應用程序的內存消耗,例如選擇較小的緩存大小。

然而,臨時存儲的高消耗也特別重要,因爲它會導致某些並行訪問應用程序中發生OutOfMemoryError,因此這些應用程序是不確定的,因此會造成更大的不適,因爲你不能在晚上重新開始。以下示例顯示了可能的問題代碼:

byte[] image = getTheByteImage();
response.setContentType("image/jpeg");
ServletOutputStream out = response.getOutputStream();
out.write(image);
out.flush();
out.close();

內存消耗在這裏並不明顯,但是,圖像在每次調用時都作爲字節數組放在堆上,然後再發送到瀏覽器。更好的選擇是直接簡化圖像:

InputStream image = getTheImageAsStream();
response.setContentType("image/jpeg");
ServletOutputStream out = response.getOutputStream();
IOUtils.copy(image, out);
out.flush();
out.close();

( BufferedStreams 和 ioutil 在內部使用 byte ,但它們通常要小得多)

在這方面,我們第一次只有 java.lang.OutOfMemoryError 錯誤說明堆中的問題。

在本系列的下一部分“Java虛擬機的配置和監視”中,我將向您展示如何在sun jvm上配置和優化堆設置,以及如何使用JVM資源監視內存。

因此,接下來的兩個部分將更實際,而不是理論性的,我計劃整合一些小屏幕截圖來給出說明性的例子。

有一些有趣的場景,引用不再可訪問,但內存無法釋放,例如:

public void someMethod() {

  try {
     String xml = getSomeBigXML();
     // do something
  } catch (AnException) {
     // handle exception
  }

  // After this point xml is not reachable,
  // but cannot be freed until the method is finished...

  // do something long running
}

在這種情況下,GC無法釋放“大”xml對象,因爲它仍然有一個GC根,但在 try-catch 塊之外無法訪問它。如果該方法長時間運行,可能會導致內存問題。

“控制”應用程序內存的一個好選擇是 java.lang.ref 文件告訴JVM如何處理對象的引用—例如,如果您使用 WeakReference ,如果您的應用程序中不再使用該對象,則該引用不會阻止GC完成該對象(就像“正常”引用一樣)。 java.util.WeakHashMap 文件對所有條目使用 weakreference ,因此這是一種可能的緩存實現。這些類型的引用還允許您更好地“攔截”對象的生命週期。

來源:https://www.tuicool.com/articles/Uj2yqyz

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章