Java記憶體洩漏問題--java既然存在垃圾回收機制,為什麼還存在記憶體洩漏?
1.什麼叫記憶體洩漏?
簡單來說就是一個東西放在記憶體裡的時間太長了,當你的程式都跑完了,它還存在那裡。這時它是白白的佔用了你的記憶體,累積起來佔用的記憶體越來越多……最後就會導致JVM報錯:out of memory。他佔用的是我們的實體記憶體。
2.java記憶體洩漏的根本原因是?
記憶體物件明明已經不需要的時候,還仍然保留著這塊記憶體和它的訪問方式(引用)。
3.java既然存在gc執行緒,為什麼還存在記憶體洩漏?
這個問題,我們需要知道 GC 在什麼時候回收記憶體物件,什麼樣的記憶體物件會被 GC 認為是“不再使用”的。
Java中對記憶體物件的訪問,使用的是引用的方式。在 Java 程式碼中我們維護一個記憶體物件的引用變數,通過這個引用變數的值,我們可以訪問到對應的記憶體地址中的記憶體物件空間。在 Java 程式中,這個引用變數本身既可以存放堆記憶體中,又可以放在程式碼棧的記憶體中(與基本資料型別相同)。 GC 執行緒會從程式碼棧中的引用變數開始跟蹤,從而判定哪些記憶體是正在使用的。如果 GC 執行緒通過這種方式,無法跟蹤到某一塊堆記憶體,那麼 GC 就認為這塊記憶體將不再使用了(因為程式碼中已經無法訪問這塊記憶體了)。
通過這種有向圖的記憶體管理方式,當一個記憶體物件失去了所有的引用之後,GC 就可以將其回收。反過來說,如果這個物件還存在引用,那麼它將不會被 GC 回收,哪怕是 Java 虛擬機器丟擲 OutOfMemoryError 。
例子1:
Vector v = new Vector( 10 ); for ( int i = 1 ;i < 100 ; i ++ ){ Object o = new Object(); v.add(o); o = null ; } |
在這個例子中,程式碼棧中存在Vector 物件的引用 v 和 Object 物件的引用 o 。 在 For 迴圈中,我們不斷的生成新的物件,然後將其新增到 Vector 物件中,之後將 o 引用置空。問題是當 o 引用被置空後, 如果發生 GC , 我們建立的 Object 物件是否能夠被 GC 回收呢? 答案是否定的。 因為, GC 在跟蹤程式碼棧中的引用時, 會發現 v 引用,而繼續往下跟蹤,就會發現 v 引用指向的記憶體空間中又存在指向 Object 物件的引用。也就是說盡管 o 引用已經被置空, 但是 Object 物件仍然存在其他的引用,是可以被訪問到的,所以 GC 無法將其釋放掉。 如果在此迴圈之後, Object 物件對程式已經沒有任何作用, 那麼我們就認為此 Java 程式發生了記憶體洩漏。
例子2:
如果想要看到記憶體溢位,可以按這樣的思路去嘗試一下:定義一個靜態的例項變數(list或其它集合),然後在一個方法裡迴圈往這個靜態變數塞東西,直到這個例項變數撐爆你的jvm記憶體。很快你就能看到out of memory……
import
java.util.ArrayList;
import
java.util.List;
public
class MemoryTest {
private
static
List list =
new
ArrayList();
private
static
int
count =
0
;
public
static
void
main(String[] args)
throws
InterruptedException {
System.out.println(
"申請前的可用記憶體 = "
+getFreeMemory());
while
(
true
){
list.add(
new
byte
[
1024
*
1024
]);
//用例項變數申請1M記憶體,當方法執行完畢時,這個static的變數是不會被釋放
count++;
if
(count %
100
==
0
) {
System.out.println(
"當前list.size()="
+list.size()+
",可用記憶體 = "
+getFreeMemory());
Thread.sleep(
500
);
}
}
}
public
static
long
getFreeMemory() {
return
Runtime.getRuntime().freeMemory() / (
1024
*
1024
);
}
}
|
所以我們要慎用類變數。