打卡學習JVM,第十天
本人學習過程中所整理的代碼,源碼地址
內存分配
- 堆上分配
- 大多數情況在Eden上分配,偶爾會直接在old上分配
- 細節取決於GC的實現
- 棧上分配
- 原子類型的局部變量
內存回收
GC要做的就是將那些dead的對象所佔用的內存回收掉
- Hotspot認爲沒有引用的對象是dead的
- Hotspot將引用分爲四種:Strong(默認通過Object obj = new Object()這種方式賦值的引用)和Soft、Weak、Phantom(都繼承了Reference)
在Full GC時會對Reference類型的引用進行特殊處理
- Soft:內存不夠時或者長期不用時一定會被GC
- Weak:一定會被GC,當被mark爲dead,會在ReferenceQueue中通知
- Phantom:本來就沒引用,當從JVM堆中釋放時會通知
GC的時機
在分代模型的基礎上,GC從時機上 分爲兩種:MinorGC和Full GC
- Minor GC
- 觸發時機:新對象生產時,Eden空間滿了
- 理論上Eden區大多數對象會在Minor GC回收,複製算法的執行效率會很高,Minor GC時間比較短
- Full GC
- 對整個JVM進行整理,包括新生代,老年代,永久代
- 主要觸發時機:1)老年代滿了 2)永久代滿了 3)System.gc()
- 效率很低,儘量減少Full GC
GC要做的就是將那些dead的對象所佔用的內存回收掉
- Hotspot認爲沒有引用的對象是dead的
- Hotspot將引用分爲四種:Strong(默認通過Object obj = new Object()這種方式賦值的引用)和Soft、Weak、Phantom(都繼承了Reference)
在Full GC時會對Reference類型的引用進行特殊處理
- Soft:內存不夠時或者長期不用時一定會被GC
- Weak:一定會被GC,當被mark爲dead,會在ReferenceQueue中通知
- Phantom:本來就沒引用,當從JVM堆中釋放時會通知
垃圾回收器
- 分代模型:GC的宏觀願景
- 垃圾回收器:GC的具體實現
- Hotspot JVM提供多種垃圾回收器,需要根據具體應用的需要採用不同的垃圾回收器
- 沒有萬能的垃圾回收器
- 垃圾回收器的“並行”和“併發”
- 並行(Parallel):指多個收集器的線程同時工作,但是用戶線程處於等待狀態
- 併發(Concurrent):指收集器在工作的同時可以允許用戶線程工作
併發不代表解決了GC停頓的問題,在關鍵的步驟還是要停頓。比如在收集器標記垃圾的時候。但在清理垃圾的時候,用戶線程可以和GC線程併發執行
- Serial收集器
- 最早的收集器單線程收集器,沒有多線程切換的額外開銷,簡單實用,收集時會暫停所有工作線程(Stop The World,STW),虛擬機運行在Client模式時的默認新生代收集器
- 在新生代採用複製算法,在老年代採用標記-整理算法
- Hotspot Client模式缺省的收集器
- ParNew收集器
- Serial的多線程版本
- 對應的這種收集器是虛擬機運行在Server模式的默認新生代收集器,在單CPU的環境中性能和Serial收集器差不多
- 使用複製算法
- 可以通過-XX:ParallelGCThreads來控制GC線程數的多少,需要結合具體CPU的個數
- Server模式下新生代的缺省收集器
- Parallel Scavenge收集器
Parallel Scavenge 收集器是以吞吐量最大化(即GC時間佔總運行時間最小)爲目標的收集器實現,它允許較長時間的STW換取總吞吐量最大化
- Serial Old收集器
Serial Old是單線程收集器,使用標記-整理算法,是老年代的收集器
- Parallel Old收集器
老年代版本吞吐量優先收集器,使用多線程和標記-整理算法
- Parallel Scavenge在老年代的實現
- 在JVM1.6纔出現
- 採用多線程,標記-整理算法
- 更注重吞吐量
- Parallel Scavenge + Parallel Old = 高吞吐量,但GC停頓可能不理想
- CMS(Concurrent Mark Sweep)收集器
CMS是一種以最短停頓時間爲目標的收集器,使用CMS並不能達到GC效率最高(總體GC時間最小),但它能儘可能降低GC時服務的停頓時間,CMS收集器使用的是標記-清除算法
- 追求最短停頓時間,非常適合Web應用
- 只針對老年代,一般結合ParNew使用
- GC線程與用戶線程儘量併發工作
- 標記-清除算法
- 多CPU環境下才有意義
- 使用-XX:+UseConcMarkSweepGC打開
- 缺點:以犧牲CPU資源的代價來減少用戶線程的停頓;CMS併發清理過程中,由於用戶線程還在允許,因此需要預留一部分空間給用戶線程;碎片化問題
Java內存泄漏的經典原因
- 對象定義在錯誤的範圍(Wrong Scope)
- 如果Foo實例對象的生命較長,會導致臨時性內存泄漏(這裏的names變量其實只有臨時作用)
class Foo {
private String[] names;
public void doIt(int length) {
if (names == null || names.length < length) {
names = new String[length];
populate(names);
System.out.println(names);
}
}
}
- JVM喜歡生命週期短的對象,這樣做已經足夠高效
class Foo {
public void doIt(int length) {
String[] names = new String[length];
populate(names);
System.out.println(names);
}
}
- 異常(Exception)處理不當
Connection conn = DriverManager.getConnection(url,name,pwd);
try {
String sql = "do a query sql";
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
while (rs.next()){
doSomeStuff();
}
rs.close();
conn.close();
}catch (Exception e){
}
如果doSomeStuff()方法拋出異常,rs.close()和conn.close()不會被調用,因此會導致內存泄漏和數據庫連接泄漏
- 集合數據管理不當
- 當使用基於數組的數據結構(ArrayList,HashMap等)時,儘量減少resize
- 如果一個List只需要順序訪問,不需要隨機訪問,用LinkedList代替ArrayList,前者本質是鏈表,不需要resize
GC垃圾收集器的JVM參數定義