可以參考這段文章:
link
A1:通過以下步驟可以很容易産生記憶體洩露(程式代碼不能通路到某些對象,但是它們仍然儲存在記憶體中):
上文中提到了使用ThreadLocal造成了記憶體洩露,但是寫的不清不楚,簡直不是人寫的文字,太差了。。。用另一篇清晰的文章來解釋吧:
http://www.cnblogs.com/onlywujun/p/3524675.html
如下圖,實線代表強引用,虛線代表弱引用.:
每個thread中都存在一個map, map的類型是ThreadLocal.ThreadLocalMap. Map中的key為一個threadlocal執行個體.
這個Map的确使用了弱引用,不過弱引用隻是針對key. 每個key都弱引用指向threadlocal.
當把threadlocal執行個體置為null以後,沒有任何強引用指向threadlocal執行個體,是以threadlocal将會被gc回收.
但是,我們的value卻不能回收,因為存在一條從current thread連接配接過來的強引用.
隻有目前thread結束以後, current thread就不會存在棧中,強引用斷開, Current Thread, Map, value将全部被GC回收.
是以得出一個結論就是隻要這個線程對象被gc回收,就不會出現記憶體洩露,但在threadLocal設為null和線程結束這段時間不會被回收的,
就發生了我們認為的記憶體洩露。其實這是一個對概念了解的不一緻,也沒什麼好争論的。
最要命的是線程對象不被回收的情況,這就發生了真正意義上的記憶體洩露。比如使用線程池的時候,線程結束是不會銷毀的,會再次使用的。
就可能出現記憶體洩露。
PS.Java為了最小化減少記憶體洩露的可能性和影響,在ThreadLocal的get,set的時候都會清除線程Map裡所有key為null的value。
是以最怕的情況就是,threadLocal對象設null了,開始發生“記憶體洩露”,然後使用線程池,這個線程結束,線程放回線程池中不銷毀,
這個線程一直不被使用,或者配置設定使用了又不再調用get,set方法,那麼這個期間就會發生真正的記憶體洩露。
先了解一下ThreadLocal類提供的幾個方法:
public T get() { }
public void set(T value) { }
public void remove() { }
protected T initialValue() { }
使用的例子:
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>();
ThreadLocal<String> stringLocal = new ThreadLocal<String>();
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread(){
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
輸出結果:
已進行驗證:
1
main
10
Thread-0
1
main
在main線程中,如果沒有先set,直接get的話,運作時會報空指針異常。但是如果改成下面這段代碼,即重寫了initialValue方法:
public class Test {
ThreadLocal<Long> longLocal = new ThreadLocal<Long>(){
protected Long initialValue() {
return Thread.currentThread().getId();
};
};
ThreadLocal<String> stringLocal = new ThreadLocal<String>(){;
protected String initialValue() {
return Thread.currentThread().getName();
};
};
public void set() {
longLocal.set(Thread.currentThread().getId());
stringLocal.set(Thread.currentThread().getName());
}
public long getLong() {
return longLocal.get();
}
public String getString() {
return stringLocal.get();
}
public static void main(String[] args) throws InterruptedException {
final Test test = new Test();
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
Thread thread1 = new Thread(){
public void run() {
test.set();
System.out.println(test.getLong());
System.out.println(test.getString());
};
};
thread1.start();
thread1.join();
System.out.println(test.getLong());
System.out.println(test.getString());
}
}
以上運作正常。
ThreadLocal的應用場景
最常見的ThreadLocal使用場景為 用來解決 資料庫連接配接、Session管理等。如:
private static ThreadLocal<Connection> connectionHolder
= new ThreadLocal<Connection>() {
public Connection initialValue() {
return DriverManager.getConnection(DB_URL);
}
};
public static Connection getConnection() {
return connectionHolder.get();
}
A2:JVM的GC不可達區域,比如通過native方法配置設定的記憶體。
A3:如果HashSet未正确實作(或者未實作)hashCode()或者equals(),會導緻集合中持續增加“副本”。如果集合不能地忽略掉它應該忽略的元素,它的大小就隻能持續增長,而且不能删除這些元素。
如果你想要生成錯誤的鍵值對,可以像下面這樣做:
class BadKey {
// no hashCode or equals();
public final String key;
public BadKey(String key) { this.key = key; }
}
Map map = System.getProperties();
map.put(new BadKey("key"), "value"); // Memory leak even if your threads die.
以下的,感覺比較瑣碎。有時間再研究。
A4:除了被遺忘的監聽器,靜态引用,hashmap中key錯誤/被修改或者線程阻塞不能結束生命周期等典型記憶體洩露場景,下面介紹一些不太明顯的Java發生記憶體洩露的情況,主要是線程相關的。
- 當ThreadGroup自身沒有線程但是仍然有子線程組時調用ThreadGroup.destroy()。發生記憶體洩露将導緻該線程組不能從它的父線程組移除,不能枚舉子線程組。
- 使用WeakHashMap,value直接(間接)引用key,這是個很難發現的情形。這也适用于繼承Weak/SoftReference的類可能持有對被保護對象的強引用。