一位面試官詢問我:Java中的JVM記憶體溢位和記憶體洩露是什麼?我這麼回答成功拿到了offer。
一位面試官詢問我:Java中的JVM記憶體溢位和記憶體洩露是什麼?我這麼回答成功拿到了offer。
1. 記憶體洩漏(memory leak )
申請了記憶體用完了不釋放,比如一共有 1024M 的記憶體,分配了 521M 的記憶體一直不回收,那麼可以用的記憶體只有 521M 了,彷彿洩露掉了一部分;
通俗一點講的話,記憶體洩漏就是【佔著茅坑不拉shi】。
2. 記憶體溢位(out of memory)
申請記憶體時,沒有足夠的記憶體可以使用;
通俗一點兒講,一個廁所就三個坑,有兩個站著茅坑不走的(記憶體洩漏),剩下最後一個坑,廁所表示接待壓力很大,這時候一下子來了兩個人,坑位(記憶體)就不夠了,記憶體洩漏變成記憶體溢位了。
可見,記憶體洩漏和記憶體溢位的關係:記憶體洩露的增多,最終會導致記憶體溢位。
這是一個很有味道的例子。
如上圖:
物件 X 引用物件 Y,X 的生命週期比 Y 的生命週期長;
那麼當Y生命週期結束的時候,X依然引用著Y,這時候,垃圾回收期是不會回收物件Y的;
如果物件X還引用著生命週期比較短的A、B、C,物件A又引用著物件 a、b、c,這樣就可能造成大量無用的物件不能被回收,進而佔據了記憶體資源,造成記憶體洩漏,直到記憶體溢位。
洩漏的分類
經常發生:發生記憶體洩露的程式碼會被多次執行,每次執行,洩露一塊記憶體;
偶然發生:在某些特定情況下才會發生;
一次性:發生記憶體洩露的方法只會執行一次;
隱式洩露:一直佔著記憶體不釋放,直到執行結束;嚴格的說這個不算記憶體洩露,因為最終釋放掉了,但是如果執行時間特別長,也可能會導致記憶體耗盡。
導致記憶體洩漏的常見原因
-
迴圈過多或死迴圈,產生大量物件;
-
靜態集合類引起記憶體洩漏,因為靜態集合的生命週期和 JVM 一致,所以靜態集合引用的物件不能被釋放;下面這個例子中,list 是靜態的,只要 JVM 不停,那麼 obj 也一直不會釋放。
public class OOM {
static List list = new ArrayList();
public void oomTests(){
Object obj = new Object();
list.add(obj);
}
}
-
單例模式,和靜態集合導致記憶體洩露的原因類似,因為單例的靜態特性,它的生命週期和 JVM 的生命週期一樣長,所以如果單例物件如果持有外部物件的引用,那麼這個外部物件也不會被回收,那麼就會造成記憶體洩漏。
-
資料連線、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
}
-
內部類的物件被長期持有,那麼內部類物件所屬的外部類物件也不會被回收。
-
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 不可變。
- 記憶體中載入資料量過大;之前專案在一次上線的時候,應用啟動奇慢直到夯死,就是因為程式碼中會載入一個表中的資料到快取(記憶體)中,測試環境只有幾百條資料,但是生產環境有幾百萬的資料。
最後
最新2020整理收集的一線網際網路公司面試真題(都整理成文件),有很多幹貨,包含netty,spring,執行緒,spring cloud等詳細講解,也有詳細的學習規劃圖,面試題整理等,我感覺在面試這塊講的非常清楚:獲取面試資料只需:點選這裡領取!!!暗號:CSDN