第二部分:第三章
1.垃圾回收條件
jvm垃圾回收分為倆點,一是對象的內存回收也就是堆內存回收,二是字面量(運行時常量池)和類信息內存回收也就是方法區內存回收。
1.1 對象內存回收(heap)
如何判斷對象是否可以回收,市場上有引用計數算法和可達性分析算法兩種方法 :
1.1.1 引用計數算法:簡單來說就是,一個對象A被引用一次,對象A的引用次數就加一。當對象A的引用次數為0(也就是沒有其他地方引用此對象)時可以回收。然而這里存在個問題就是 對象之間互相引用 A->B,B->A時 AB無法回收。不過微軟公司的com(componet object model)技術用的就是這個算法,而目前流行的主流的jvm沒有選用此算法的。
1.1.2 可達性分析算法:簡單來說就是,通過一系統成為Gc Roots的對象為起始點,當沖GC Roots到達不了對象A時,則對象A是不可用的,可回收的。
java語言中,GC Roots包括四種對象:
1.1.2.1 虛擬機棧(棧幀中的本地變量表)中引用的對象
1.1.2.2 本地方法棧中(native方法)引用的對象
1.1.2.3 方法區中類靜態屬性引用的對象
1.1.2.4 方法區中常量引用的對象
之所以是上面四種,總結為:GC管理的主要區域是Java堆,一般情況下只針對堆進行垃圾回收。方法區、棧和本地方法區不被GC所管理,因而選擇這些區域內的對象作為GC roots
1.2 方法區內存回收:jvm規范不要求虛擬機在方法區實現垃圾收集(因為性價比低,回收一次回收的空間實際情況很小),但是jvm還是有方法區回收的。
1.2.1 字面量回收:如果一個字面量沒有被引用就回被回收;
1.2.2 類信息回收:類信息回收需要同時滿足3個條件:
1.2.2.1 java堆中不存在此類的實例;
1.2.2.2 加載該類的classloader已經被回收
1.2.2.3 該類對應的java.lang.Class對象沒有任何地方引用,也就是說無法再任何地方通過反射訪問該類的方法。
1.2.3 其他常量池的東西 暫不清楚。
1.3 四大類型引用:自jdk1.2之后(jdk1.1引用分的種類不夠明確),jvm中還有幾種類型引用比較特殊;。分別是 強引用、軟引用、弱引用、虛引用。
1.3.1 強引用:Object obj=new Object();只要強引用還存在,垃圾收集器永遠不會回收被引用的對象。假如obj=null或者obj引用的棧幀出棧(不存在)就會被回收。
1.3.2 軟引用:將要發生內存溢出溢出之前,將會把軟引用的對象進行回收。SoftReference類實現軟引用。
1.3.3 弱引用:只能生存到下一次gc之前。WeakReference類實現弱引用。
1.3.4 虛引用:這個需要注意,虛引用無法用來取得一個對象的實例。虛引用不會對對象的生存時間有任何影響,只是能在這個對象被回收時收到一個系統通知。推薦這篇文章更加了解虛引用:http://www.mamicode.com/info-detail-988201.html。
關於四種引用推薦一篇文章里面有詳細例子:http://blog.csdn.net/u011277123/article/details/53908315
1.4 finalize() 對象自救
對象在可達性分析之后還需要進行一些邏輯。哪怕對象在可達性分析之后沒有發現與GcRoots的引用鏈,jvm還需要進行下面幾步(需要倆次標記):
1.4.1 沒有發現引用鏈,進行第一次標記
1.4.2 判斷對象是否要執行finalize()方法:當對象沒有覆蓋finalize()方法或者已經被執行過一次finalize()方法;則此對象不執行finalize()方法,否則進行執行finalize()方法(把對象放在F-Quene隊列中);
jvm自動建立的,低優先級的Finalizer線程去執行放在F-Quene的對象里的finalize()方法。
1.4.3 進行第二次標記,判斷對象是否有引用鏈,沒有則回收。
所以如果對象自救(第一次標記沒有引用鏈,第二次標記有引用鏈),只有在覆蓋finalize()方法激活對象(讓對象被引用)。
2.垃圾收集算法:jvm垃圾收集可以分為四種(嚴格來說是三種):標記-清除算法;標記-整理算法;復制算法;分代收集算法。
2.1 標記-清除算法:先把可回收內存標記,然后在清除掉;缺點是:會存在內存碎片導致內存泄漏。
2.2 標記-整理算法:先把可回收內存標記,然后讓存活的對象都向一端移動,最后清除點端邊界以外的內存;缺點:比較標記-清除時間增長;
2.3 復制算法:把內存平分為兩份s1,s2;保證只用一個內存區s1,另一個為空s2;把存活的對象復制到為空的那個內存區域s2,然后在清除掉這個區域s1;缺點:內存減半
2.4 分代收集算法:根據jvm特性,年輕代實際每次gc時存活對象較少,故用推薦復制算法;年老代存活對象較多,並且沒有其他內存為年老代分配擔保(分配擔保:舉個栗子:年老代為年輕代進行分配擔保,當年輕代minor gc內存不足時【比如有個大對象obj1】obj1會放到年老代中;而年老代沒有其他空間為其分擔了)所以推薦標記-整理算法。
3.hotspot算法實現:暫時未深入理解
4.垃圾收集器:共七中垃圾收集器,分別是serial、parnew、parallel scavenge和cms、serial old、 parallel old、G1;其中serial、parnew、parallel scavenge用於年輕代,cms、serial old、 parallel old用於年老代,g1用於年輕代和年老代。所有收集器都存在stop the world,不過在java發展中 一直在優化停頓時間。
先總結比較這七個收集器:
年輕代:
4.1.Serial:適用於年輕代垃圾回收,復制算法,jdk1.3之前 推薦用於客戶端模式(Client)下的虛擬機,屬於單線程,無對stop the world優化,屬於最老的年輕代垃圾收集器產品;
4.2 ParNew:適用於年輕代垃圾回收,復制算法,jdk1.3發布,推薦用於服務端模式(Server)下的虛擬機,屬於多線程,無對stop the world優化,其實就是Serial的多線程版本;是服務端開發的首先;
4.3 Parallel Scavenge:適用於年輕代垃圾回收,復制算法,jdk1.4發布,屬於多線程,無對stop the world優化,它是一個可以控制吞吐量的收集器,擁有自適應調節策略;需要注意一點的是,gc停頓時間縮短是犧牲吞吐量和新生代空間來換取的。
stop The World:gc時 需要停止所有線程進行垃圾回收;
吞吐量=運行用戶代碼時間/(運行用戶代碼時間+垃圾收集時間)
自適應調節策略:虛擬機會根據當前系統的運行情況收集性能監控信息,動態調整參數以提高最合適的停頓時間或最大吞吐量。
年老代
4.4 Serial old :適用於年老代垃圾回收,標記-整理算法,jdk1.5之前,主要用於client下的虛擬機,如果用在server模式下主要有倆個作用:一是jdk1.5以及之前用於與Parallel Scavenger搭配使用,二就是為cms提供后備預案,屬於單線程,無stop the world優化。
4.5 Parallel Old:適用於年老代垃圾回收,標記-整理算法,jdk1.6發布,屬於多線程,注重於吞吐量控制,是為了Parallel Scavenge定制的;
4.6 CMS: 適用於年老代垃圾回收,標記-清除算法,jdk1.5發布,推薦server模式下,屬於多線程,對stop the world有優化,CMS是一種以獲取最短回收停頓時間為目標的收集器,也就是優化服務器響應速度。CMS分為4步:其中 初始化標記,重新標記 stop the world
4.6.1 初始標記:僅僅只是標記GcRoots可以直接關聯的對象;
4.6.2 並發標記:進行gcroots tracing(也就是 引用鏈搜索);優化成並發
4.6.3 重新標記:修正並發標記因用戶程序繼續運行而導致標記產生變動那一部分對象的標記記錄。
4.6.4 並發清除:並發清理標記的對象內存。
4.6.5 CMS缺點:
4.6.5.1 CMS對cpu資源非常敏感,默認啟動的回收線程數是(cpu數量+3)/4;垃圾回收線程數量不少於25%的cpu資源,當cpu越大,線程數/cpu總量 越小;但是當cpu小於4個時 顯得就不怎么合適了;
4.6.5.2 無法處理浮動垃圾,需要等下一次gc才能處理;並且還需要預留一部分空間提供並發收集時的用戶程序運作使用,即啟動閾值(-XX:CMSInitiatingOccupancyFraction 閾值百分比);要是CMS運行期間預留的空間不滿足程序需要,就會出現一次“Concurrent Mode Failure”失敗,這是虛擬機啟動后備預案:臨時啟用Serial Old,這樣一來停頓時間就很長了,所以-XX:CMSInitiatingOccupancyFraction 設置太高容易導致大量“Concurrent Mode Failure”失敗,性能反而降低
4.6.5.3 由於用的是標記-清除算法,所以會出現內存碎片;CMS提供-XX:UseCMSCompactAtFullCollection開關參數(默認為開),用於CMS收集器要進行FullGC時進行內存碎片合並整理,-XX:CMSFullGCsBeforeCompaction 用來設置執行多少次不壓縮Full GC后跟着來一次帶壓縮的(默認為0,表示每次進入Full GC都進行碎片壓縮)。
年輕代+年老代
4.7 G1:jdk1.7發布,整體看是“標記-整理算法”,從局部(倆個Region之間)上來看是基金“復制”算法實現,也就是說沒有內存碎片;
如果應用追求低停頓,那G1現在可以作為一個嘗試的選擇,如果應該追求吞吐量,G1並不會帶來什么特別的好處!!!
5.內存分配策略:共有 4個策略
5.1 對象優先在Eden分配:major gc時經常會伴隨至少一次的minor gc,但並非絕對,比如說 Parallel Scavenge就是直接major gc沒有伴隨minor gc;
5.2 大對象直接進入老年代 -XX:PretenureSizeThreshold設置最大對象,單位B;PretenureSizeThreshold參數只對Serial和ParNew兩款收集器有效;Parallel Scavenge收集器不需要設置;如果遇到必須使用此參數的場景,可以考慮ParNew+CMS組合;
5.3 長期存活的對象放入老年代:虛擬機給每個對象定義了一個對象年齡(Age)計數器;gc一次Age+1,當Age大於一定程度(默認為15歲)就會被晉升到老年代;-XX:MaxTenuringThreshold:最大年齡;
5.4 動態對象年齡判定:如果在Survivor空間中相同年齡所有對象大小的總和大於Survivor空間的一半,年齡大於或等於改年齡的對象就可以直接進入老年代,無須等到MaxTenuringThreshold中要求的年齡;
5.4空間分配擔保:jdk6 update 24后,只要老年代的連續空間大於新生代對象總大小或者歷次晉升的平均大小就會進行major gc,否則Full GC;
第二部分:第四章
1.jdk的命令行工具簡單總結介紹
1.1 jps: 獲取虛擬機進程 vmid;jps -v :顯示虛擬機對應啟動的參數
1.2 jinfo:獲取虛擬機參數值和動態修改部分運行期可改的參數。jinfo [option] pid例如:jinfo -flag PermSize vmid;顯示vid對應的虛擬機的方法區大小;jinfo -flag +/- name:添加或刪除name屬性;
1.3 jmap:java內存影像工具, jmap -heap vmid:顯示堆的詳細信息,如參數配置,分代狀況;jmap -histo vmid:顯示堆中對象統計信息,包括類,實例數量,合計容量。
1.4 jstat:虛擬機監控工具,jstat -gccause vmid:輸出已使用空間占各自總空間的百分比;
1.5jhat:虛擬機堆轉儲快照分析工具;jstack:java堆棧跟蹤工具;
2.jdk的可視化工具:jConsole和visualVm;其中VisualVM有個BTrace插件值得注意。
2.1 BTrace 動態日志跟蹤:可以打印調用堆棧、參數、返回值、性能監視、定位連接泄漏、內存泄漏、解決多線程競爭問題。如生產上遇到問題時需要方法輸入參數和返回輸出參數,但開發時沒有日記記錄,平常做法就是補充上日志,然后在重現上線。而BTrace可以在不停止jvm的情況下動態調試代碼。(ps:BTrace后續深入研究)
推薦一篇文章:http://huanghaifeng1990.iteye.com/blog/2121419
第二部分:第五章
摘自:https://blog.csdn.net/hupoling/article/details/62887251