垃圾回收機制概述java
Java語言中一個顯著的特色就是引入了垃圾回收機制,使c++程序員最頭疼的內存管理的問題迎刃而解,它使得Java程序員在編寫程序的時候再也不須要考慮內存管理。因爲有個垃圾回收機制,Java中的對象再也不有「做用域」的概念,只有對象的引用纔有「做用域」。垃圾回收能夠有效的防止內存泄露,有效的使用空閒的內存。c++
ps:內存泄露是指該內存空間使用完畢以後未回收,在不涉及複雜數據結構的通常狀況下,Java 的內存泄露表現爲一個內存對象的生命週期超出了程序須要它的時間長度,咱們有時也將其稱爲「對象遊離」。程序員
垃圾回收簡要過程
這裏必須點出一個很重要的誤區:不可達的對象並不會立刻就會被直接回收,而是至少要通過兩次標記的過程。
第一次被標記過的對象,會檢查該對象是否重寫了finalize()方法。若是重寫了該方法,則將其放入一個F-Query隊列中,不然,直接將對象加入「即將回收」集合。在第二次標記以前,F-Query隊列中的全部對象會逐個執行finalize()方法,可是不保證該隊列中全部對象的finalize()方法都能被執行,這是由於JVM建立一個低優先級的線程去運行此隊列中的方法,極可能在沒有遍歷完以前,就已經被剝奪了運行的權利。那麼運行finalize()方法的意義何在呢?這是對象避免本身被清理的最後手段:若是在執行finalize()方法的過程當中,使得此對象從新與GC Roots引用鏈相連,則會在第二次標記過程當中將此對象從F-Query隊列中清除,避免在此次回收中被清除,恢復成了一個「正常」的對象。但顯然這種好事不能無限的發生,對於曾經執行過一次finalize()的對象來講,以後若是再被標記,則不會再執行finalize()方法,只能等待被清除的命運。
以後,GC將對F-Queue中的對象進行第二次小規模的標記,將隊列中從新與GC Roots引用鏈恢復鏈接的對象清除出「即將回收」集合。全部此集合中的內容將被回收。算法
手動GC回收
public class JVMDemo05 { public static void main(String[] args) { JVMDemo05 jvmDemo05 = new JVMDemo05(); //jvmDemo05 = null; System.gc(); } protected void finalize() throws Throwable { System.out.println("gc在回收對象..."); } }
finalize做用apache
Java技術使用finalize()方法在垃圾收集器將對象從內存中清除出去前,作必要的清理工做。這個方法是由垃圾收集器在肯定這個對象沒有被引用時對這個對象調用的。它是在Object類中定義的,所以全部的類都繼承了它。子類覆蓋finalize()方法以整理系統資源或者執行其餘清理工做。finalize()方法是在垃圾收集器刪除對象以前對這個對象調用的。瀏覽器
內存泄露
內存泄漏的定義:對象已經沒有被應用程序使用,可是垃圾回收器沒辦法移除它們,由於還在被引用着。服務器
要想理解這個定義,咱們須要先了解一下對象在內存中的狀態。下面的這張圖就解釋了什麼是無用對象以及什麼是未被引用對象。數據結構
上面圖中能夠看出,裏面有被引用對象和未被引用對象。未被引用對象會被垃圾回收器回收,而被引用的對象卻不會。未被引用的對象固然是再也不被使用的對象,由於沒有對象再引用它。然而無用對象卻不全是未被引用對象。其中還有被引用的。就是這種狀況致使了內存泄漏。多線程
如何防止內存泄露
下面是幾條容易上手的建議,來幫助你防止內存泄漏的發生。併發
- 特別注意一些像HashMap、ArrayList的集合對象,它們常常會引起內存泄漏。當它們被聲明爲static時,它們的生命週期就會和應用程序同樣長。
- 特別注意事件監聽和回調函數。當一個監聽器在使用的時候被註冊,但再也不使用以後卻未被反註冊。
- 「若是一個類本身管理內存,那開發人員就得當心內存泄漏問題了。」 一般一些成員變量引用其餘對象,初始化的時候須要置空。
垃圾回收機制算法
引用計數法
1.1概述
給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值就加1;當引用失效時,計數器值就減1;任什麼時候刻計數器都爲0的對象就是再也不被使用的,垃圾收集器將回收該對象使用的內存。
1.2優缺點
優勢:
引用計數收集器能夠很快的執行,交織在程序運行中。對程序須要不被長時間打斷的實時環境比較有利。
缺點:
沒法檢測出循環引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數永遠不可能爲0.並且每次加減很是浪費內存。
複製算法
S0和s1將可用內存按容量分紅大小相等的兩塊,每次只使用其中一塊,當這塊內存使用完了,就將還存活的對象複製到另外一塊內存上去,而後把使用過的內存空間一次清理掉。這樣使得每次都是對其中一塊內存進行回收,內存分配時不用考慮內存碎片等複雜狀況,只須要移動堆頂指針,按順序分配內存便可,實現簡單,運行高效。
複製算法的缺點顯而易見,可以使用的內存降爲原來一半。
複製算法用於在新生代垃圾回收
標記清除算法
標記-清除(Mark-Sweep)算法顧名思義,主要就是兩個動做,一個是標記,另外一個就是清除。
標記就是根據特定的算法(如:引用計數算法,可達性分析算法等)標出內存中哪些對象能夠回收,哪些對象還要繼續用。
標記指示回收,那就直接收掉;標記指示對象還能用,那就原地不動留下。
缺點
- 標記與清除沒有連續性效率低;
- 清除以後內存會產生大量碎片;
因此碎片這個問題還得處理,怎麼處理,看標記-整理算法。
標記-壓縮算法
標記壓縮法在標記清除基礎之上作了優化,把存活的對象壓縮到內存一端,然後進行垃圾清理。(java中老年代使用的就是標記壓縮法)
分代收集算法
根據內存中對象的存活週期不一樣,將內存劃分爲幾塊,java的虛擬機中通常把內存劃分爲新生代和年老代,當新建立對象時通常在新生代中分配內存空間,當新生代垃圾收集器回收幾回以後仍然存活的對象會被移動到年老代內存中,當大對象在新生代中沒法找到足夠的連續內存時也直接在年老代中建立。
對於新生代和老年代來講,新生代回收頻率很高,可是每次回收耗時很短,而老年代回收頻率較低,可是耗時會相對較長,因此應該儘可能減小老年代的GC.
爲何老年代使用標記壓縮、新生代使用複製算法。
垃圾回收時的停頓現象
垃圾回收的任務是識別和回收垃圾對象進行內存清理,爲了讓垃圾回收器能夠更高效的執行,大部分狀況下,會要求系統進如一個停頓的狀態。停頓的目的是爲了終止全部的應用線程,只有這樣的系統纔不會有新垃圾的產生。同時停頓保證了系統狀態在某一個瞬間的一致性,也有利於更好的標記垃圾對象。所以在垃圾回收時,都會產生應用程序的停頓。
垃圾收集器
什麼是Java垃圾回收器
Java垃圾回收器是Java虛擬機(JVM)的三個重要模塊(另外兩個是解釋器和多線程機制)之一,爲應用程序提供內存的自動分配(Memory Allocation)、自動回收(Garbage Collect)功能,這兩個操做都發生在Java堆上(一段內存快)。某一個時點,一個對象若是有一個以上的引用(Rreference)指向它,那麼該對象就爲活着的(Live),不然死亡(Dead),視爲垃圾,可被垃圾回收器回收再利用。垃圾回收操做須要消耗CPU、線程、時間等資源,因此容易理解的是垃圾回收操做不是實時的發生(對象死亡立刻釋放),當內存消耗完或者是達到某一個指標(Threshold,使用內存佔總內存的比列,好比0.75)時,觸發垃圾回收操做。有一個對象死亡的例外,java.lang.Thread類型的對象即便沒有引用,只要線程還在運行,就不會被回收。
串行回收器(Serial Collector)
單線程執行回收操做,回收期間暫停全部應用線程的執行,client模式下的默認回收器,經過-XX:+UseSerialGC命令行可選項強制指定。參數能夠設置使用新生代串行和老年代串行回收器
年輕代的回收算法(Minor Collection)
把Eden區的存活對象移到To區,To區裝不下直接移到年老代,把From區的移到To區,To區裝不下直接移到年老代,From區裏面年齡很大的升級到年老代。 回收結束以後,Eden和From區都爲空,此時把From和To的功能互換,From變To,To變From,每一輪迴收以前To都是空的。設計的選型爲複製。
年老代的回收算法(Full Collection)
年老代的回收分爲三個步驟,標記(Mark)、清除(Sweep)、合併(Compact)。標記階段把全部存活的對象標記出來,清除階段釋放全部死亡的對象,合併階段 把全部活着的對象合併到年老代的前部分,把空閒的片斷都留到後面。設計的選型爲合併,減小內存的碎片。
並行回收
並行回收器(ParNew回收器)
並行回收器在串行回收器基礎上作了改進,他可使用多個線程同時進行垃
圾回收,對於計算能力強的計算機而言,能夠有效的縮短垃圾回收所需的尖
際時間。
ParNew回收器是一個工做在新生代的垃圾收集器,他只是簡單的將串行回收
器多線程快他的回收策略和算法和串行回收器同樣。
使用XX:+UseParNewGC 新生代ParNew回收器,老年代則使用市行回收器
ParNew回收器工做時的線程數量可使用XX:ParaleiGCThreads參數指
定,通常最好和計算機的CPU至關,避免過多的栽程影響性能。
並行回收集器(ParallelGC)
老年代ParallelOldGC回收器也是一種多線程的回收器,和新生代的
ParallelGC回收器同樣,也是一種關往吞吐量的回收器,他使用了標記壓縮
算法進行實現。
-XX:+UseParallelOldGC 進行設置
-XX:+ParallelCThread也能夠設置垃圾收集時的線程教量。
並CMS(併發GC)收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間爲目標的收集器。CMS收集器是基於「標記-清除」算法實現的,整個收集過程大體分爲4個步驟:
①.初始標記(CMS initial mark)
②.併發標記(CMS concurrenr mark)
③.從新標記(CMS remark)
④.併發清除(CMS concurrent sweep)
其中初始標記、從新標記這兩個步驟任然須要停頓其餘用戶線程。初始標記僅僅只是標記出GC ROOTS能直接關聯到的對象,速度很快,併發標記階段是進行GC ROOTS 根搜索算法階段,會斷定對象是否存活。而從新標記階段則是爲了修正併發標記期間,因用戶程序繼續運行而致使標記產生變更的那一部分對象的標記記錄,這個階段的停頓時間會被初始標記階段稍長,但比並發標記階段要短。
因爲整個過程當中耗時最長的併發標記和併發清除過程當中,收集器線程均可以與用戶線程一塊兒工做,因此總體來講,CMS收集器的內存回收過程是與用戶線程一塊兒併發執行的。
CMS收集器的優勢:併發收集、低停頓,可是CMS還遠遠達不到完美,器主要有三個顯著缺點:
CMS收集器對CPU資源很是敏感。在併發階段,雖然不會致使用戶線程停頓,可是會佔用CPU資源而致使引用程序變慢,總吞吐量降低。CMS默認啓動的回收線程數是:(CPU數量+3) / 4。
CMS收集器沒法處理浮動垃圾,可能出現「Concurrent Mode Failure「,失敗後而致使另外一次Full GC的產生。因爲CMS併發清理階段用戶線程還在運行,伴隨程序的運行自熱會有新的垃圾不斷產生,這一部分垃圾出如今標記過程以後,CMS沒法在本次收集中處理它們,只好留待下一次GC時將其清理掉。這一部分垃圾稱爲「浮動垃圾」。也是因爲在垃圾收集階段用戶線程還須要運行,
即須要預留足夠的內存空間給用戶線程使用,所以CMS收集器不能像其餘收集器那樣等到老年代幾乎徹底被填滿了再進行收集,須要預留一部份內存空間提供併發收集時的程序運做使用。在默認設置下,CMS收集器在老年代使用了68%的空間時就會被激活,也能夠經過參數-XX:CMSInitiatingOccupancyFraction的值來提供觸發百分比,以下降內存回收次數提升性能。要是CMS運行期間預留的內存沒法知足程序其餘線程須要,就會出現「Concurrent Mode Failure」失敗,這時候虛擬機將啓動後備預案:臨時啓用Serial Old收集器來從新進行老年代的垃圾收集,這樣停頓時間就很長了。因此說參數-XX:CMSInitiatingOccupancyFraction設置的太高將會很容易致使「Concurrent Mode Failure」失敗,性能反而下降。
最後一個缺點,CMS是基於「標記-清除」算法實現的收集器,使用「標記-清除」算法收集後,會產生大量碎片。空間碎片太多時,將會給對象分配帶來不少麻煩,好比說大對象,內存空間找不到連續的空間來分配不得不提早觸發一次Full GC。爲了解決這個問題,CMS收集器提供了一個-XX:UseCMSCompactAtFullCollection開關參數,用於在Full GC以後增長一個碎片整理過程,還可經過-XX:CMSFullGCBeforeCompaction參數設置執行多少次不壓縮的Full GC以後,跟着來一次碎片整理過程。
G1回收器
G1回收器(Garbage-First)實在]dk1.7中提出的垃圾回收器,從長期目標來看是爲了取
代CMS回收器,G1回收器擁有獨特的垃圾回收策略,G1屬於分代垃圾回收器,區分
新生代和老年代,依然有eden和from/to區,它並不要求整個eden區或者新生代、老
年代的空間都連續,它使用了分區算法。
並行性: G1回收期間可多線程同時工做。
井發性G1擁有與應用程序交替執行能力,部分工做可與應用程序同時執行,在整個
GC期間不會徹底阻塞應用程序。
分代GC:G1依然是一個分代的收集器,可是它是非兩新生代和老年代一杯政的雜尊。
空間基理,G1在國收過程當中,不會微CMS那樣在若千tacAy 要進行碎片整理。
G1
來用了有效複製對象的方式,減小空間碎片。
利得程,用於分區的緣由,G能夠貝造取都分區城進行回收,帽小了國收的格想,
提高了性能。
使用.XXX:+UseG1GC 應用G1收集器,
Mills指定最大停頓時間
使用-XX:MaxGCPausel
設置並行回收的線程數量
使用-XX:ParallelGCThreads
Tomcat配置調優測試
Jmeter壓力測試工具
JMeter是一款在國外很是流行和受歡迎的開源性能測試工具,像LoadRunner 同樣,它也提供了一個利用本地Proxy Server(代理服務器)來錄製生成測試腳本的功能,可是這個功能並很差用。因此在本文中介紹一個更爲經常使用的方法——使用Badboy錄製生成 JMeter 腳本。
簡單的介紹一下Badboy。Badboy是一款不錯的Web自動化測試工具,若是你將它用於非商業用途,或者用於商業用途可是安裝Badboy 的機器數量不超過5臺,你是不須要爲它支付任何費用的。也許是一種推廣策略,Badboy提供了將Web測試腳本直接導出生成JMeter 腳本的功能,而且這個功能很是好用,也很是簡單。你能夠跟着下面的試驗步驟來邁出你在開源世界的第一步。
1. 經過Badboy的官方網站下載Badboy的最新版本;
2. 安裝Badboy。安裝過程同通常的Windows 應用程序沒有什麼區別,安裝完成後你能夠在桌面和Windows開始菜單中看到相應的快捷方式——若是找不到,能夠找一下Badboy安裝目錄下的Badboy.exe 文件,直接雙擊啓動Badboy;
3. 啓動Badboy,你能夠看到下面的界面。
在地址欄(圖中紅色方框標註的部分)中輸入你須要錄製的Web應用的URL——這裏咱們以http://www.yahoo.com 爲例,並點擊GO 按鈕開始錄製。若是你用過LoadRunner之類的商業工具,對於這個操做必定不會陌生吧 ^_^
4. 開始錄製後,你能夠直接在Badboy內嵌的瀏覽器(主界面的右側)中對被測應用進行操做,全部的操做都會被記錄在主界面左側的編輯窗口中——在這個試驗中,咱們在Yahoo的搜索引擎中輸入 JMeter 進行搜索。不過你將看到,錄製下來的腳本並非一行行的代碼,而是一個個Web對象——這就有點像LoadRunner的VuGen中的Tree View視圖;
5. 錄製完成後,點擊工具欄中的「中止錄製」按鈕,完成腳本的錄製;
6. 選擇「File -> Export to JMeter」菜單,填寫文件名「login_mantis.jmx」,將錄製好腳本導出爲JMeter腳本格式。也能夠選擇「File -> Save」菜單保存爲Badboy腳本;
7. 啓動JMeter並打開剛剛生成的測試腳本。
也許你已經急不可待的準備開始嘗試着用JMeter處理你手頭的工做了^_^ 在下面的幾節,我將繼續爲你們介紹如何在 JMeter 中完成一個測試場景的設置和JMeter測試結果分析入門,以及如何參數化JMeter腳本。
固然,若是你的動手能力很強,幾分鐘你就能夠熟悉這些內容。不過仍是請容許我一點點由淺入深的來幫你們完成「JMeter從入門到精通」的過程。我相信在這個過程當中你將會了解到更多有關性能測試的知識和經驗,甚至包括一些LoadRunner等商業測試工具所沒法提供給你的經驗。
測試串行吞吐量
-XX:+PrintGCDetails -Xmx32M -Xms32M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseSerialGC -XX:PermSize=32M |
項目啓動GC回收6次 吞吐量390 |
擴大堆的內存
-XX:+PrintGCDetails -Xmx512M –Xms532M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseSerialGC -XX:PermSize=32M |
GC回收6次 445 |
結論 最大內存越大,吞吐量越高。 |
調整初始堆
-XX:+PrintGCDetails -Xmx512M –Xms512M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseSerialGC -XX:PermSize=32M |
GC回收0次 492 |
並行回收(UseParNewGC)
-XX:+PrintGCDetails -Xmx512M –Xms512M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParNewGC -XX:PermSize=32M |
GC回收0次 吞吐量452 |
並行合併回收(UseParallelGC)
-XX:+PrintGCDetails -Xmx512M -Xms256M -XX:+HeapDumpOnOutOfMemoryError -XX:+UseParallelGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=8 -XX:PermSize=32M |
GC回收0次 吞吐量 |
調優總結
初始堆值和最大堆內存內存越大,吞吐量就越高。
最好使用並行收集器,由於並行手機器速度比串行吞吐量高,速度快。
設置堆內存新生代的比例和老年代的比例最好爲1:2或者1:3。
減小GC對老年代的回收。