JAVA中的內存溢出和內存泄露分別是什麼,有什麼聯繫和區別,讓咱們來看一看。java
申請了內存用完了不釋放,好比一共有 1024M 的內存,分配了 521M 的內存一直不回收,那麼能夠用的內存只有 521M 了,彷彿泄露掉了一部分;mysql
通俗一點講的話,內存泄漏就是【佔着茅坑不拉shi】。web
申請內存時,沒有足夠的內存可使用;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. 內存中加載數據量過大;以前項目在一次上線的時候,應用啓動奇慢直到夯死,就是由於代碼中會加載一個表中的數據到緩存(內存)中,測試環境只有幾百條數據,可是生產環境有幾百萬的數據。
會點代碼的大叔 | 文【原創】