JVM虛擬機器垃圾回收
JVM虛擬機器的基本結構:
JDK1.8版本:
先說說與JDK1.7版本的細微區別:
1. 沒有了方法區,取而代之的是元空間;
2. 原來方法區中的執行時常量池中的字串常量池,靜態變數都存在了堆中;
3. 方法區中的其它的(例如類載入資訊)存放在元空間中,執行時元空間存在於直接記憶體中;
JVM內部各個組成介紹:
堆(Heap):
1. JVM中所管理記憶體中最大的一塊,在虛擬機器啟動的時候建立;
2. 唯一的目的是存放物件例項,幾乎所有的物件例項和陣列都是在這裡分配記憶體;
3. 堆是垃圾收集管理的主要區域,所以也被稱為“GC”;
4. 原來方法區中的執行時常量池中的字串常量池和靜態變數,都存放在堆中;
方法區:
1. 在JDK1.7之前執行時常量池存放在方法區,此時的hotspot虛擬機器堆方法區的實現為永久代;
2. 在JDK1.7時,字串常量池被拿到了堆中,剩餘的東西還是在方法區,也就是hotspot中的永久代;
3. 在JDK1.8,hotspot移除了永久代,使用元空間取代,字串常量池還在堆中,執行時常量池還在方法區,只不過方法區的實現從永久代變成了元空間;
ps: 靜態常量池(class檔案常量池) 在編譯的時候每個class都有的,在編譯階段,存放的是常量的符號引用; 執行時常量池: 在類載入完成後,將每個class常量池中的符號引用值轉存到執行時常量池中,也就是每個class都有一個執行時常量池,類在解析之後,將符號引用替換為直接引用,與全域性常量池中的引用值保持一致; 字串常量池: 全域性字串池中的內容是在類載入完成,經過驗證,準備階段之後再堆中生成字串物件例項,然後將字串物件例項的引用值存放到字串常量池中,需要注意的是這裡面存放的是引用值而不是具體的例項物件;
元空間(metaspace):
在JDK1.8中,永久代已經不存在,儲存的類資訊,編譯後的程式碼資料已經移動到了元空間中,元空間並不在堆中,而是在本地記憶體中,元空間的本質和永久代類似,都是對JVM規範中的方法區的實現,不過元空間和永久代的區別是:元空間並不在虛擬機器中,而是使用本地記憶體;
為什麼要使用元空間,取消永久代? 1.字串存在永久代中,容易出現效能問題和記憶體溢位; 2.不會再有java.lang.OutOfMemoryError:PermGen的問題; 3.類及方法的資訊等比較難確定其大小,因此永久代的大小指定比較困難,太小容易出現永久代溢位,太大容易導致老年代溢位;
PC暫存器:(程式計數器)
PC暫存器是儲存程式當前執行的指令的地址(也可以說儲存下一條指令的所在儲存單元的地址),當CPU需要執行指令時,需要從程式計算器中得到當前需要執行的指令所在的儲存單元的地址,然後根據得到的地址獲取指令,在得到指令之後,程式計數器便自動加1或者根據轉移指標得到下一條指令的地址,如此迴圈,直到執行完所有的指令;
Java棧(虛擬機器棧):
java棧是java方法執行的記憶體模型,主要存放區域性變數,運算元棧、動態連結、以及是方法的出口;
本地方法棧:
本地方法棧與java棧作用相似,區別是java棧是為java方法服務的,而本地方法棧是為執行本地方法服務的;
JVM堆空間佈局:
JDK1.7版本:
在jdk1.8版本中,取消了永久代,使用了元空間取而代之;
ps:為什麼要堆記憶體要分代? 1. 不同型別的物件的生命週期是不同的; 2.如果不分代,每次垃圾回收都需要遍歷整個堆記憶體空間,花費時間較長,效率較低; 3.不同年代的物件採用不同的垃圾回收方式,以便提高回收效率;
年輕代:
1. 新生成的物件首先是分配在年輕代的;
2.年輕代的目標就是儘可能快速的收集掉那些生命週期短的物件;
3.Eden:Eden是用來存放JVM剛分配的物件;
4.Survivor1和Survivor2:2個Survivor的空間一樣大,當Eden中的物件經過垃圾回收沒有被回收掉時,會在2個Survivor之間來回copy,當滿足某個條件時,比如copy的次數,就會copy到Tenured;
老年代:
1.在年輕代中經歷了N次垃圾回收後仍然存活的物件,就會被放到老年代;
2.老年代中存放的都是一些生命週期較長的物件,比如:Session物件、Socket連線等;
怎麼判斷物件為垃圾物件?
1.引用計數演算法
給每一個建立的物件增加一個引用計數器,每當有一個地方引用它的時候,這個計數器就加1,而當引用失效的時候,計數器就減1,當這個計數器的值為0的時候,就是這個物件沒有任何地方在使用它了,那就是一個無效的物件了,可以進行垃圾回收。
2.根搜尋演算法:
通過一系列名為“GC Root”的物件作為終點,當一個物件到GC Roots之間無法通過引用到達時,那麼該物件便可以進行回收了。
如何進行垃圾回收(垃圾回收的演算法)?
1.標記並清除
分為“標記”和“清除”兩個階段,首先根據上面的演算法標記出所有需要回收的物件,在標記完成後,統一回收掉所有的 被標記的物件。
缺點:
①效率低:標記和清除這兩個過程效率都不高。
②容易產生記憶體碎片:因為記憶體的申請通常是不連續的,那麼清除一些物件後,那麼就會產生大量不連續的記憶體碎片。
2.複製演算法
將可用記憶體按容量劃分為大小相等的兩塊區域,每次只使用其中一塊,當這一塊的記憶體用完了,就將活著的物件複製到另一塊區域中,然後把已使用的記憶體空間一次性清理掉。這樣的話,就不用考慮記憶體碎片的問題了,但是缺點就是一般的記憶體是閒置的,資源浪費嚴重,並且如果物件存活率較高的話,每次需要複製大量的物件,效率會變得很低。
3.標記整理演算法
首先標記出所有存活物件,然後讓所有存活物件向一端移動,直接清理到端邊界以外的記憶體,這種方式的只有物件的存活率比較高時,該演算法的效率才會高。
4.分代演算法
根據物件的生命週期不同將堆記憶體分為新生代和老年代,每個代採用不同的垃圾回收演算法。
垃圾收集器:
Serial、ParNew、Parallel Scavenge、Serial Old、Parallel Old、CMS、G1
新生代收集器:Serial、ParNew、Parallel Scavenge
老年代收集器:Serial Old、Parallel Old、CMS
整堆收集器:G1
名詞解釋: Minor GC:又稱為新生代GC,指發生在新生代的垃圾收集動作,因為java物件大多數都是朝生夕滅,所以Minor GC會非常頻繁,而且速度很快; Full GC:又稱為Major GC或者老年代GC,指發生在老年代的GC;出現Full GC經常會伴隨至少一次的Minor GC(不是絕對),Major GC速度比Minor GC要慢10倍以上;
Serial收集器:
採用的演算法為標記並清除,單執行緒;是虛擬機器執行在Client模式下的預設新生代收集器;
ParNew收集器:
ParNew收集器是Serial收集器的多執行緒版本,是虛擬機器在Server模式下的預設新生代收集器;
Parallel Scavenge收集器:
Parallel Scavenge收集器是一個新生代的收集器,採用的是複製演算法,關注點是吞吐量;
Serial Old收集器:
是一個老年代的收集器,同樣是一個單執行緒收集器,採用的演算法是標記並整理演算法;
Parallel Old收集器:
是Parallel Scavenge收集器的老年代版本,使用多執行緒,採用的是標記並整理演算法;
CMS收集器:
CMS收集器是一種以獲取最短回收停頓時間為目標的收集器,使用的演算法是標記並清除演算法
涉及到的引數配置:
JVM基本引數:
引數名稱 | 含義 | 預設值 |
-Xms | 初始堆大小 | 實體記憶體的1/64(<1GB) |
-Xmx | 最大堆大小 | 實體記憶體的1/4(<1GB) |
-Xmn | 年輕代大小 | |
-XX:NewSize | 設定年輕代大小 | |
-XX:MaxNewSize | 年輕代最大值 | |
-XX:PermSize | 設定永久代(perm gen)初始值 | 實體記憶體的1/64 |
-XX:MaxPermSize | 設定永久代最大值 | 實體記憶體的1/4 |
-Xss | 每個執行緒的堆疊大小 | |
-XX:NewRatio | 年輕代(包括Eden和兩個Survivor區)與年老代的比值(除去持久代) | |
-XX:SurvivorRatio | Eden區與Survivor區的大小比值 | |
-XX:MaxDirectMemorySize | 設定最大可用直接記憶體大小 |
垃圾收集器引數:
引數名稱 | 含義 |
-XX:+UseSerialGC | 序列垃圾回收器 |
-XX:+UseParallelGC | 並行垃圾回收器 |
-XX:ParallelGCThreads | 並行收集器的執行緒數 |
-XX:+UseParallelOldGC | 年老代垃圾收集方式為並行收集 |
-XX:+UseConcMarkSweepGC | 設定年老代為併發收集 |
-XX:+UseParNewGC | 設定年輕代為並行收集 |
-XX:+UseG1GC | 使用G1垃圾回收器 |
JVM日誌引數:
引數名稱 | 含義 |
-XX:+PrintGC | 輸出GC日誌 |
-XX:+PrintGCDetails | 輸出GC的詳細日誌 |
-XX:+PrintGCTimeStamps | 輸出GC的時間戳(以基準時間的形式) |
-XX:+PrintGCDateStamps | 輸出GC的時間戳(以日期的形式輸出) |
-XX:+PrintHeapAtGC | 在進行GC的前後打印出堆的資訊 |