Java 中的內存溢出和內存泄露是什麼?我給你舉個有味道的例子

JAVA中的內存溢出和內存泄露分別是什麼,有什麼聯繫和區別,讓咱們來看一看。java

內存泄漏 & 內存溢出

1. 內存泄漏(memory leak )

申請了內存用完了不釋放,好比一共有 1024M 的內存,分配了 521M 的內存一直不回收,那麼能夠用的內存只有 521M 了,彷彿泄露掉了一部分;mysql

通俗一點講的話,內存泄漏就是【佔着茅坑不拉shi】。web

2. 內存溢出(out of memory)

申請內存時,沒有足夠的內存可使用;sql

通俗一點兒講,一個廁所就三個坑,有兩個站着茅坑不走的(內存泄漏),剩下最後一個坑,廁所表示接待壓力很大,這時候一會兒來了兩我的,坑位(內存)就不夠了,內存泄漏變成內存溢出了。緩存

可見,內存泄漏和內存溢出的關係:內存泄露的增多,最終會致使內存溢出。app

這是一個頗有味道的例子。ide

內存泄漏
內存泄漏

如上圖:測試

對象 X 引用對象 Y,X 的生命週期比 Y 的生命週期長;this

那麼當Y生命週期結束的時候,X依然引用着Y,這時候,垃圾回收期是不會回收對象Y的;url

若是對象X還引用着生命週期比較短的A、B、C,對象A又引用着對象 a、b、c,這樣就可能形成大量無用的對象不能被回收,進而佔據了內存資源,形成內存泄漏,直到內存溢出。

泄漏的分類

  • 常常發生:發生內存泄露的代碼會被屢次執行,每次執行,泄露一塊內存;

  • 偶然發生:在某些特定狀況下才會發生;

  • 一次性:發生內存泄露的方法只會執行一次;

  • 隱式泄露:一直佔着內存不釋放,直到執行結束;嚴格的說這個不算內存泄露,由於最終釋放掉了,可是若是執行時間特別長,也可能會致使內存耗盡。

致使內存泄漏的常見緣由

1. 循環過多或死循環,產生大量對象;

2. 靜態集合類引發內存泄漏,由於靜態集合的生命週期和 JVM 一致,因此靜態集合引用的對象不能被釋放;下面這個例子中,list 是靜態的,只要 JVM 不停,那麼 obj 也一直不會釋放。

public class OOM {
 static List list = new ArrayList();

 public void oomTests(){
  Object obj = new Object();

  list.add(obj);
 }
}
複製代碼

3. 單例模式,和靜態集合致使內存泄露的緣由相似,由於單例的靜態特性,它的生命週期和 JVM 的生命週期同樣長,因此若是單例對象若是持有外部對象的引用,那麼這個外部對象也不會被回收,那麼就會形成內存泄漏。

4. 數據鏈接、IO、Socket鏈接等等,它們必須顯示釋放(用代碼 close 掉),不然不會被 GC 回收。

try {
 Connection conn = null;
 Class.forName("com.mysql.jdbc.Driver");
 conn = DriverManager.getConnection("url","""");
 Statement stmt = conn.createStatement() ;
 ResultSet rs = stmt.executeQuery("....") ; 
catch (Exception e) {
 //異常日誌
finally {
 //1.關閉結果集 Statement
 //2.關閉聲明的對象 ResultSet
 //3.關閉鏈接 Connection
}
複製代碼

5. 內部類的對象被長期持有,那麼內部類對象所屬的外部類對象也不會被回收。

6. Hash 值發生改變,好比下面中的這個類,它的 hashCode 會隨着變量 x 的變化而變化:

public class ChangeHashCode {
 private int x ;

 @Override
 public int hashCode() {
  final int prime = 31;
  int result = 1;
  result = prime * result + x;
  return result;
 }

 @Override
 public boolean equals(Object obj) {
  if (this == obj)
   return true;
  if (obj == null)
   return false;
  if (getClass() != obj.getClass())
   return false;
  ChangeHashCode other = (ChangeHashCode) obj;
  if (x != other.x)
   return false;
  return true;
 }
 //省略 set 、get 方法
}
複製代碼

public class HashSetTests {
 public static void main(String[] args){
  HashSet<ChangeHashCode> hs = new HashSet<ChangeHashCode>();

  ChangeHashCode cc = new ChangeHashCode();
  cc.setX(10);//hashCode = 41
  hs.add(cc);

  cc.setX(20);//hashCode = 51
  System.out.println("hs.remove = " + hs.remove(cc));//false

  hs.add(cc);
  System.out.println("hs.size = " + hs.size());//size = 2

 }
}
複製代碼

能夠看到,在測試方法中,當元素的 hashCode 發生改變以後,就再也找不到改變以前的那個元素了;

這也是 String 爲何被設置成了不可變類型,咱們能夠放心地把 String 存入 HashSet,或者把 String 當作 HashMap 的 key 值;

當咱們想把本身定義的類保存到散列表的時候,須要保證對象的 hashCode 不可變。

7. 內存中加載數據量過大;以前項目在一次上線的時候,應用啓動奇慢直到夯死,就是由於代碼中會加載一個表中的數據到緩存(內存)中,測試環境只有幾百條數據,可是生產環境有幾百萬的數據。

會點代碼的大叔 | 文【原創】


敬請關注會點代碼的大叔
敬請關注會點代碼的大叔
相關文章
相關標籤/搜索