系列閱讀
1.深入理解Java虛擬機-GC&運行時數據區
2.深入理解Java虛擬機-類文件結構及加載
3.深入理解Java虛擬機-內存模型及多線程
1. Java虛擬機概念
JDK(Java Developement Kit)
包括Java程序設計語言、Java虛擬機、Java API類庫。
JRE(Java Runtime Environment)
包括Java虛擬機和Java API類庫中的Java SE API子集。
JVM(Java虛擬機)
是指一種能夠運行Java字節碼的虛擬機。Java虛擬機有自己完善的硬體架構,如處理器、堆棧、寄存器等,還具有相應的指令系統,屏蔽了與具體操作系統平臺相關信息。
HotSpot VM
是Sun JDK和Open JDK中所帶的虛擬機,為目前使用范圍最廣的Java虛擬機。OpenJDK是JDK的開源版。Sun JDK歸Oracle所有。Unix內置或者通過軟件源安裝JDK的話,一般是Open JDK。參考
2. Java虛擬機運行時數據區
程序計數器
是當前線程所執行的字節碼的行號指示器,在分支/循環/跳轉/異常處理/線程恢復等有應用。
Java堆
用于分配和存放對象實例。
虛擬機棧
也稱Java棧,描述Java方法執行的內存模型:每個方法在執行的同時會創建一個棧幀用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。
注意:如果局部變量是一個reference類型,它引用的對象在Java堆中可被各個線程共享,但是reference本身在Java棧的局部變量表中,它是線程私有的。
本地方法棧
與虛擬機棧作用相似,但本地方法棧為虛擬機使用到的Native方法服務,虛擬機棧為Java方法(字節碼)服務。
方法區
主要存放類的元數據,用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。 運行時常量池是方法區的一部分。
直接內存
不是虛擬機運行時數據區的一部分。NIO(New Input/Output)類是基于通道和緩沖區的I/O方法,使用Native函數庫直接分配堆外內存,然后通過一個存儲在Java堆中的DirectByteBuffer對象作為這塊內存的引用來進行操作。
垃圾回收器可以對方法區,Java堆和直接內存進行回收.
擴展
HotSpot虛擬機不區分虛擬機棧和本地方法棧。
線程具有獨立的程序計數器和Java虛擬機棧。
方法區和Java堆是被所有線程共享的內存區域。
局部變量表在棧幀中,用于保存函數的參數和局部變量.
在JDK1.6/1.7中,方法區可以理解為永久區(Perm),但是在JDK1.8中,永久區被替換成元數據區.
3. OOM
OOM有memory leak和memory overflow兩種情況。
-Xoos:設置本地方法棧大小
-Xms:堆起始容量
-Xmx:堆最大容量
-Xmn: 新生代大小
-XX:PermSize:永久代(方法區)的初始大小
-XX:MaxPermSize:永久代(方法區)的最大值
-Xss:每個線程堆棧容量,JDK1.5+使用,直接決定函數調用的最大深度。
發生OOM的運行時數據區域
程序計數器是唯一沒有規定任何OOM情況的運行時數據區域。
如果線程請求的棧深度大于虛擬機所允許的深度,將拋出StackOverflowError異常。
如果虛擬機棧或本地方法棧可以動態擴展且擴展時無法申請到足夠的內存將拋出OutOfMemoryError異常。
如果堆或方法區中沒有內存完成實例分配且堆也無法再擴展時將拋出OutOfMemoryError異常。
4. GC(Garbage Collection)
對象創建與回收
判斷是否需要類加載-分配內存-內存空間初始化-對象信息設置-對象賦值
HotSpot VM內存管理系統要求對象起始地址必須是8字節的整數倍。
任何一個對象的Finalize()方法都只會被系統自動調用一次,建議不用。
確定對象是死是活的算法
引用計數法
使用引用計數器對對對象的引用數進行記錄,為0時對象不再使用。缺點是難以解決對象之間相互引用的關系,這個時候互相引用的對象實際上應該被回收,但是因為引用數不為0所以判斷失誤。可達性分析算法
從一系列“GC Roots”對象向下遍歷,此路徑稱為引用鏈。當一個對象到GC Roots沒有任何引用鏈相連(對象不可達)時,則此對象不可用。為Java/C#/Lisp的主流實現。
新生代與老生代
Java堆分為新生代和老年代,新生代被分為三個區域:Eden、From Survivor、To Survivor。
新生代可用區域是Eden + From Survivor,一般是Java對象申請內存及存放的地方。老年代一般存放在新生代中已存活許久的Java對象。
垃圾收集器
新生代大部分要回收,GC一般采用復制算法,快。老年代大部分不需要回收,GC一般采用標記-清除或者標記-整理算法。
Serial收集器
運行在Client模式下的虛擬機,對新生代進行單線程地暫停收集。采用復制算法。ParNew收集器
是Serial收集器的多線程版本,關注點是盡可能地縮短垃圾收集時用戶線程停頓的時間。采用復制算法。Parallele Scavenge收集器
是多線程收集器,采用復制算法。目的是達到一個可控制的吞吐量(吞吐量=運行用戶代碼時間/(運行垃圾收集器時間+運行代碼時間))。Serail Old收集器
是Serial收集器的老年版本。采用標記-整理算法。Parallele Old收集器
是Parallele Scavenge收集器的老年版本。采用標記-整理算法。CMS(Concurrent Mark Sweep)收集器
是一種以獲取最短回收停頓時間為目標的收集器。采用標記-清除算法。G1(Garbage-First)收集器
是一款面向服務端應用的垃圾收集器。Java堆布局中的新生代和老生代不再物理隔離。具備如下特點:并行與并發;分代收集;空間整合;可預測的停頓。G1分代/分區.
G1比CMS有更短的反應停頓時間,但是CMS有更高的吞吐量。但是G1在持續優化。
虛擬機之所以提供多種不同的收集器及調節參數,是因為只有根據實際應用需求和實現方式選擇最優的收集方式才能獲取最高的性能。
JVM參數
UseSerialGC 新生代和老年代都串行收集
UseParallelGC 新生代ParallelGC,老年代串行
UseParNewGC 新生代ParNew,老年代串行
UseParallelOldGC 新生代parallelGC,老年代ParallelOld
UseConcMarkSweepGC 新生代ParNew,老年代CMS+串行收集器
UserG1GC 使用G1回收器
GC模式
- Partial GC:并不收集整個GC堆的模式
- Young GC:只收集young gen的GC。通常與與Minor GC等價。
- Old GC:只收集old gen的GC。只有CMS的concurrent collection是這
個模式 - Mixed GC:收集整個young gen以及部分old gen的GC。只有G1有這個
模式
- Full GC:收集整個堆,包括young gen、old gen、perm gen(永久代,如果存在的話)等所有部分的模式。Major GC通常是跟full GC是等價的。經常會伴隨至少一次的Minor GC,但不絕對。
System.gc()默認觸發Full GC。FullGC會造成Stop-The-World(Java用戶執行線程停頓)。
對于GC分類并無完全準確的定義,所以不必糾結。參考
內存分配及回收策略
- 對象優先在新生代Eden區中分配,當Eden區沒有足夠空間分配時將發起一次Minor GC。
- 大對象直接進入老年代。
- 長期存活的對象將進入老年代。
根據對象的對象年齡(Age)計數器,從Eden出生并經過一次Minor GC仍存活,且被Survivor空間容納,則將被移動到Survivor空間中,Age設為1。以后在Survivor區每熬過一次Minor GC,則Age加1,達到限制則晉升老年代。 - 動態年齡對象判斷
如果Survivor空間中相同年齡所有對象大小總和大于Survivor空間的一半,則年齡大于或等于該年齡的對象可直接進入老年代。 - 空間分配擔保
新生代使用復制收集算法時,當Survivor空間無法容納Minor GC存活對象時,需要老年代進行分配擔保,將這些對象直接進入到老年代。
但是老年代剩余空間要足夠,為應對此風險,只要老年代的連續空間大于新生代對象總大小或者歷次晉升的平均大小就會進行Minor GC,否則將進行Full GC。
注:主要內容摘錄自書籍 深入理解Java虛擬機,周志明 著