jvm(3)-垃圾收集器与内存分配策略
生活随笔
收集整理的這篇文章主要介紹了
jvm(3)-垃圾收集器与内存分配策略
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
【0】README
0.1)本文部分文字轉自:深入理解jvm,旨在學習?垃圾收集器與內存分配策略 的基礎知識;
【1】垃圾回收概述 1)GC(Garbage Collection)需要完成的3件事情:哪些內容需要回收;什么時候回收;以及如何回收? 2)垃圾回收機制關注的內存是:java 堆 和 方法區,因為這部分內存的分配都是動態的;
【1.1】對象是否已死(判斷) 1)可達性分析算法:通過java,C#等都是通過該算法來判斷對象是否存活?。其基本思想就是通過一系列稱為“GC Root”的對象作為起點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈,但一個對象到GC Root沒有任何引用鏈相連的話,則證明對象是不可用的; 2)java中,可作為GC Roots的對象包括下面幾種(Objects): O1)虛擬機棧中引用的對象; O2)方法區中類靜態屬性引用的對象; O3)方法區中常量引用的對象; O4)本地方法棧中JNI(Native方法)引用的對象;
【1.2】java引用類型 1)引用分為:?強引用,軟引用,弱引用,虛引用;以上四種引用強度逐漸減弱; 2)引用詳解: 2.1)強引用:指在程序中普遍存在的,類似 Object obj = new Object();只要強引用存在,就不會被回收; 2.2)軟引用:用來描述一些還有用但并非必須的對象;在系統將要發生內存溢出異常前,將會把這些對象列進回收范圍中進行第二次回收;(SoftReference類來實現軟引用) 2.3)弱引用:也是用來描述非必須對象的,但它的強度比軟引用還要弱,被弱引用關聯的對象只能生存到下一次GC之前;(WeakReference類來實現弱引用) 2.4)虛引用:它是最弱的一種引用關系。一個對象是否有虛引用的存在,完全不會對其生存時間構成威脅,也無法通過一個虛引用獲得一個對象實例。為一個對象設置虛引用的目的是: 能夠在這個對象被回收時 收到一個系統通知。(PhantomReference 類來提供虛引用)
【1.3】生存還是死亡 1)即使在可達性分析算法中不可達的對象,而并非是“非死不可”的:?這時候他們暫時處于緩刑階段,要真正宣告一個對象死亡,需要兩次 標記marks;(干貨——宣告一個對象死亡(被回收),需要兩次標記marks) mark1)如果對象在進行可達性分析后發現沒有與 GC roots 相連接的引用鏈,那他將會被第一次標記并且進行一次篩選;篩選條件是此對象是否有必要執行 finalize方法。如果該對象沒有覆蓋 finalize方法,或者finalize方法已經被虛擬機調用過,虛擬機將這兩種cases 都視為沒有必要執行; mark2)如果這個對象被判定有必要執行 finalize方法,那么這個對象將會放置到一個叫做F-Queue的隊列中,并在稍后由一個虛擬機自動建立的,低優先級的finalizer 線程去執行它。 這里所謂的“執行”是指虛擬機會觸發這個方法,但并不承諾會等待它運行結束。這樣做的原因是,如果一個對象在finalize()方法中執行緩慢,或者發生了死循環(更極端的情況),將很可能會導致F-Queue隊列中的其他對象永久處于等待狀態,甚至導致整個內存回收系統崩潰。finalize()方法是對象逃脫死亡命運的最后一次機會,稍后GC將對F-Queue中的對象進行第二次小規模的標記,如果對象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變量或對象的成員變量,那在第二次標記時它將被移除出“即將回收”的集合;如果對象這時候還沒有逃脫,那它就真的離死不遠了。(干貨——finalize()方法是對象逃脫死亡命運的最后一次機會) 2)垃圾回收前的對象自救——荔枝演示 2.1)源代碼 /*** 此代碼演示了兩點: * 1.對象可以在被GC時自我拯救。 * 2.這種自救的機會只有一次,因為一個對象的finalize()方法最多只會被系統自動調用一次* @author zzm*/ public class FinalizeEscapeGC {public static FinalizeEscapeGC SAVE_HOOK = null;public void isAlive() {System.out.println("yes, i am still alive :)");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize mehtod executed!");FinalizeEscapeGC.SAVE_HOOK = this; // System.gc()調用到了這里,拯救成功}public static void main(String[] args) throws Throwable {SAVE_HOOK = new FinalizeEscapeGC();//對象第一次成功拯救自己SAVE_HOOK = null;System.gc();// 因為Finalizer方法優先級很低,暫停0.5秒,以等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("no, i am dead :(");}// 下面這段代碼與上面的完全相同,但是這次自救卻失敗了SAVE_HOOK = null;System.gc();// 因為Finalizer方法優先級很低,暫停0.5秒,以等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("no, i am dead :(");}} } 2.2)打印結果 finalize mehtod executed! yes, i am still alive :) no, i am dead :( 3)不推薦使用 finalize方法(but try-finally) 筆者并不鼓勵大家使用這種方法來拯救對象。相反,筆者建議大家盡量避免使用它,因為它不是C/C++中的析構函數,而是Java剛誕生時為了使C/C++程序員更容易接受它所做出的一個妥協。它的運行代價高昂,不確定性大,無法保證各個對象的調用順序。finalize()能做的所有工作,使用try-finally或其他方式都可以做得更好、更及時,大家完全可以忘掉Java語言中還有這個方法的存在。
【1.4】回收方法區 1)永久代的垃圾收集主要回收兩部分內容:廢棄常量和無用的類?;厥諒U棄常量與回收Java堆中的對象非常類似。以常量池中字面量的回收為例,假如一個字符串“abc”已經進入了常量池中,但是當前系統沒有任何一個String對象是叫做“abc”的,換句話說是沒有任何String對象引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果在這時候發生內存回收,而且必要的話,這個“abc”常量就會被系統“請”出常量池。 2)判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面3個條件(Conditions)才能算是“無用的類”:
【2】垃圾收集算法 1)復制算法:
2)分代收集算法: 當前商業虛擬機的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法并沒有什么新的思想,只是根據對象的存活周期的不同將內存劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。
【2】垃圾收集器
【1】垃圾回收概述 1)GC(Garbage Collection)需要完成的3件事情:哪些內容需要回收;什么時候回收;以及如何回收? 2)垃圾回收機制關注的內存是:java 堆 和 方法區,因為這部分內存的分配都是動態的;
【1.1】對象是否已死(判斷) 1)可達性分析算法:通過java,C#等都是通過該算法來判斷對象是否存活?。其基本思想就是通過一系列稱為“GC Root”的對象作為起點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈,但一個對象到GC Root沒有任何引用鏈相連的話,則證明對象是不可用的; 2)java中,可作為GC Roots的對象包括下面幾種(Objects): O1)虛擬機棧中引用的對象; O2)方法區中類靜態屬性引用的對象; O3)方法區中常量引用的對象; O4)本地方法棧中JNI(Native方法)引用的對象;
【1.2】java引用類型 1)引用分為:?強引用,軟引用,弱引用,虛引用;以上四種引用強度逐漸減弱; 2)引用詳解: 2.1)強引用:指在程序中普遍存在的,類似 Object obj = new Object();只要強引用存在,就不會被回收; 2.2)軟引用:用來描述一些還有用但并非必須的對象;在系統將要發生內存溢出異常前,將會把這些對象列進回收范圍中進行第二次回收;(SoftReference類來實現軟引用) 2.3)弱引用:也是用來描述非必須對象的,但它的強度比軟引用還要弱,被弱引用關聯的對象只能生存到下一次GC之前;(WeakReference類來實現弱引用) 2.4)虛引用:它是最弱的一種引用關系。一個對象是否有虛引用的存在,完全不會對其生存時間構成威脅,也無法通過一個虛引用獲得一個對象實例。為一個對象設置虛引用的目的是: 能夠在這個對象被回收時 收到一個系統通知。(PhantomReference 類來提供虛引用)
【1.3】生存還是死亡 1)即使在可達性分析算法中不可達的對象,而并非是“非死不可”的:?這時候他們暫時處于緩刑階段,要真正宣告一個對象死亡,需要兩次 標記marks;(干貨——宣告一個對象死亡(被回收),需要兩次標記marks) mark1)如果對象在進行可達性分析后發現沒有與 GC roots 相連接的引用鏈,那他將會被第一次標記并且進行一次篩選;篩選條件是此對象是否有必要執行 finalize方法。如果該對象沒有覆蓋 finalize方法,或者finalize方法已經被虛擬機調用過,虛擬機將這兩種cases 都視為沒有必要執行; mark2)如果這個對象被判定有必要執行 finalize方法,那么這個對象將會放置到一個叫做F-Queue的隊列中,并在稍后由一個虛擬機自動建立的,低優先級的finalizer 線程去執行它。 這里所謂的“執行”是指虛擬機會觸發這個方法,但并不承諾會等待它運行結束。這樣做的原因是,如果一個對象在finalize()方法中執行緩慢,或者發生了死循環(更極端的情況),將很可能會導致F-Queue隊列中的其他對象永久處于等待狀態,甚至導致整個內存回收系統崩潰。finalize()方法是對象逃脫死亡命運的最后一次機會,稍后GC將對F-Queue中的對象進行第二次小規模的標記,如果對象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關聯即可,譬如把自己(this關鍵字)賦值給某個類變量或對象的成員變量,那在第二次標記時它將被移除出“即將回收”的集合;如果對象這時候還沒有逃脫,那它就真的離死不遠了。(干貨——finalize()方法是對象逃脫死亡命運的最后一次機會) 2)垃圾回收前的對象自救——荔枝演示 2.1)源代碼 /*** 此代碼演示了兩點: * 1.對象可以在被GC時自我拯救。 * 2.這種自救的機會只有一次,因為一個對象的finalize()方法最多只會被系統自動調用一次* @author zzm*/ public class FinalizeEscapeGC {public static FinalizeEscapeGC SAVE_HOOK = null;public void isAlive() {System.out.println("yes, i am still alive :)");}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("finalize mehtod executed!");FinalizeEscapeGC.SAVE_HOOK = this; // System.gc()調用到了這里,拯救成功}public static void main(String[] args) throws Throwable {SAVE_HOOK = new FinalizeEscapeGC();//對象第一次成功拯救自己SAVE_HOOK = null;System.gc();// 因為Finalizer方法優先級很低,暫停0.5秒,以等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("no, i am dead :(");}// 下面這段代碼與上面的完全相同,但是這次自救卻失敗了SAVE_HOOK = null;System.gc();// 因為Finalizer方法優先級很低,暫停0.5秒,以等待它Thread.sleep(500);if (SAVE_HOOK != null) {SAVE_HOOK.isAlive();} else {System.out.println("no, i am dead :(");}} } 2.2)打印結果 finalize mehtod executed! yes, i am still alive :) no, i am dead :( 3)不推薦使用 finalize方法(but try-finally) 筆者并不鼓勵大家使用這種方法來拯救對象。相反,筆者建議大家盡量避免使用它,因為它不是C/C++中的析構函數,而是Java剛誕生時為了使C/C++程序員更容易接受它所做出的一個妥協。它的運行代價高昂,不確定性大,無法保證各個對象的調用順序。finalize()能做的所有工作,使用try-finally或其他方式都可以做得更好、更及時,大家完全可以忘掉Java語言中還有這個方法的存在。
【1.4】回收方法區 1)永久代的垃圾收集主要回收兩部分內容:廢棄常量和無用的類?;厥諒U棄常量與回收Java堆中的對象非常類似。以常量池中字面量的回收為例,假如一個字符串“abc”已經進入了常量池中,但是當前系統沒有任何一個String對象是叫做“abc”的,換句話說是沒有任何String對象引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果在這時候發生內存回收,而且必要的話,這個“abc”常量就會被系統“請”出常量池。 2)判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面3個條件(Conditions)才能算是“無用的類”:
C1)該類所有的實例都已經被回收,也就是Java堆中不存在該類的任何實例。
C2)加載該類的ClassLoader已經被回收。
C3)該類對應的java.lang.Class 對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
3)是否對類進行回收:?HotSpot虛擬機提供了-Xnoclassgc參數進行控制,還可以使用-verbose:class及-XX:+TraceClassLoading、 -XX:+TraceClassUnLoading查看類的加載和卸載信息。【2】垃圾收集算法 1)復制算法:
為了解決效率問題,一種稱為“復制”(Copying)的收集算法出現了,它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內存空間一次清理掉。(現在的商業虛擬機都采用這種收集算法來回收新生代)
2)分代收集算法: 當前商業虛擬機的垃圾收集都采用“分代收集”(Generational Collection)算法,這種算法并沒有什么新的思想,只是根據對象的存活周期的不同將內存劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。在新生代中,每次垃圾收集時都發現有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記-清理”或“標記-整理”算法來進行回收。
【2】垃圾收集器
- 1)Serial收集器(單線程收集器)
- 2)ParNew收集器(Serial收集器的多線程版本)
- 3)Parallel Scavenge收集器:?Parallel Scavenge收集器的目標:?則是達到一個可控制的吞吐量(Throughput)。所謂吞吐量就是CPU用于運行用戶代碼的時間與CPU總消耗時間的比值,即吞吐量 = 運行用戶代碼時間 /(運行用戶代碼時間 + 垃圾收集時間),虛擬機總共運行了100分鐘,其中垃圾收集花掉1分鐘,那吞吐量就是99%。(干貨——吞吐量的定義)
- 4)Serial Old收集器(是Serial收集器的老年代版本)
- 5)Parallel Old收集器(Parallel Scavenge收集器的老年代版本)
- 6)CMS收集器(Concurrent Mark Sweep):目的是獲取最短回收停頓時間,并發收集,低停頓;
- 7)G1收集器(Garbage-First):可建立可預測的停頓時間模型,且面向server 的 應用;
【3】理解GC日志
1)閱讀GC日志是處理jvm 內存問題的基礎技能
2)看個荔枝:
對以上日志的分析(Analysis):
- A1)33.125:代表GC發生的時間,表示從 jvm 啟動以來經過的秒數;
- A2)GC日志開頭的 [GC 和 [FullGC 說明了這次垃圾收集的停頓類型:?如果是調用 System.gc() 方法進行收集,將顯示 [Full GC(System);
- A3)[DefNew,[Tenured,[Perm 表示:?GC發生的區域,這里顯示的區域名稱與使用的GC收集器密切相關,如 DefNew == Default New Generation;
- A4)后面方括號內部的 3324K->152K(3712K):表示 GC 前該內存區域已經使用容量->GC后該內存區域已使用容量(該內存區域的總容量);
- A5)括號外的 3324K->152K(11904K): 表示GC前java 堆已使用容量->GC后堆已使用容量(java堆總容量);
- A6)0.0031680 secs表示:?該內存區域進行GC 所占用的時間;
- A7)有的收集器會給出具體的GC時間,?如[time: user=0.01 sys=0.00 real=0.02 secs],user, sys, real 分別代表用戶態消耗的CPU時間, 內核態消耗的CPU事件 和 操作從開始到結束所經過的墻鐘時間。
3)垃圾收集器參數總結
總結
以上是生活随笔為你收集整理的jvm(3)-垃圾收集器与内存分配策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 怎么免费制作网站(怎么免费创建网站)
- 下一篇: jvm(4)-虚拟机性能监控与故障处理工