一、淺談java中的垃圾回收機制
根據(jù)很多同學與朋友,日常工作與面試時經(jīng)常會被問到JVM垃圾回收機制,接下來博主就給你捋一捋,疏理知識點,如有不同意見或建議,歡迎聯(lián)系博主以便交流。
1.GC技術背景
淺談JVM垃圾回收的前世今。說起垃圾回收機制(GC),大部分人都把這項技術當做Java語言的伴生產物。事實上,GC的歷史比Java久遠,早在1960年Lisp這門語言中就使用了內存動態(tài)分配和垃圾回收技術。
2.JVM內存回收
程序員們知道,jvm內存結構分為五大區(qū)域:程序計數(shù)器、虛擬機棧、本地方法棧、堆區(qū)、方法區(qū)。其中虛擬機棧、本地方法棧與程序計數(shù)器這3個區(qū)域隨線程而生、隨線程而滅,因此就不需要考慮過多內存垃圾回收問題,因為一個方法調用結束或者線程結束時,內存自然就跟隨著回收了。
我們就把重點放在方法區(qū)與堆區(qū),這部分內存的分配和回收是動態(tài)的,正是垃圾收集器所需關注的部分。
二、GC中的算法
1.直面問題,哪些可以回收,哪些暫時還不能回收?
垃圾收集器在對堆區(qū)和方法區(qū)進行回收工作前,首先肯定確定這些區(qū)域內對象哪些可以被回收,哪些暫時還不能回收,這時就要用到判斷對象是否存活的算法!如何失去任何引用,垃圾收集器就把它收走。
(1)引用計數(shù)算法
早期策略。在這種算法中,堆中每個對象實例都有一個引用計數(shù)。當一個對象被創(chuàng)建時,就將該對象實例分配給一個變量,該變量計數(shù)設置為1。當任何其它變量被賦值為這個對象的引用時,計數(shù)加1(但當一個對象實例的某個引用超過了生命周期或者被設置為一個新值時,對象實例的引用計數(shù)器減1。任何引用計數(shù)器為0的對象實例可以被當作垃圾收集。當一個對象實例被垃圾收集時,它引用的任何對象實例的引用計數(shù)器減1。
缺點:循環(huán)引用時無效
如:如父對象有一個對子對象的引用,子對象反過來引用父對象。
(2)可達性分析算法
可達性分析算法是從離散數(shù)學中引入的,也是如今正在使用的策略,程序把所有的引用關系看作一張圖(DOM圖類似),從一個節(jié)點GC ROOT開始,尋找對應的引用節(jié)點,找到這個節(jié)點以后,繼續(xù)尋找這個節(jié)點的引用節(jié)點,就如遞歸思想一般,遍歷所有,當所有的引用節(jié)點尋找完畢之后,剩余的節(jié)點則被認為是沒有被引用到的節(jié)點,即無用的節(jié)點,無用的節(jié)點將會被判定為是可回收的對象。
三、引用與回收問題
無論是通過引用計數(shù)算法判斷對象的引用數(shù)量,還是通過可達性分析算法判斷對象的引用鏈是否可達,判定對象是否存活都與“引用”有關。
當對象失去所有引用時,我們就可以說對象生命周期結束了,即為死亡,就該回收它啦。
四、常見的垃圾回收器
1.Serial收集器
新生代單線程收集器,標記和清理都是單線程,優(yōu)點是簡單高效。是client級別默認的GC方式。
2.Serial Old收集器
老年代單線程收集器,Serial收集器的老年代版本。
3.ParNew收集器
新生代收集器,可以認為是Serial收集器的多線程版本,在多核CPU環(huán)境下有著比Serial更好的表現(xiàn)。
4.Parallel Scavenge收集器
并行收集器,追求高吞吐量,高效利用CPU。吞吐量一般為99%, 吞吐量= 用戶線程時間/(用戶線程時間+GC線程時間)。適合后臺應用等對交互相應要求不高的場景。是server級別默認采用的GC方式。
5.Parallel Old收集器
Parallel Scavenge收集器的老年代版本,并行收集器,吞吐量優(yōu)先。
6.CMS(Concurrent Mark Sweep)收集器
高并發(fā)、低停頓,追求最短GC回收停頓時間,cpu占用比較高,響應時間快,停頓時間短,多核cpu 追求高響應時間的選擇。
五、垃圾回收機制什么時候觸發(fā)?
由于會遇到的問題不一樣,因此垃圾回收區(qū)域、時間、方法也不一樣。GC有兩種類型:Full GC & Scavenge GC 。
1.FULL GC
GC在優(yōu)先級最低的線程中運行,一般在應用程序空閑即沒有應用線程在運行時被調用。
對整個堆進行整理,包括Young(新生區(qū))、Tenured(老年區(qū))和Perm(靜態(tài)區(qū))。Full GC因為需要對整個堆進行回收,所以比Scavenge GC要慢,因此應該盡可能減少Full GC的次數(shù)。在對JVM調優(yōu)的過程中,很大一部分工作就是對于Full GC的調節(jié)。
但下面的條件例外。
2.Scavenge GC
(1)Java堆內存不足時,Scavenge GC會被調用。
(2)當應用線程在運行,并在運行過程中創(chuàng)建新對象,若這時內存空間不足,JVM就會強制調用Scavenge GC線程。若GC一次之后仍不能滿足內存分配,JVM會再進行兩次GC,若仍無法滿足要求,則JVM將報“out of memory”的錯誤,java虛擬機將停止運行。
六、GC的兩個重要方法
(1)System.gc()方法
使用System.gc()可以不管JVM使用的是哪一種垃圾回收的算法,都可以請求Java的垃圾回收。
在命令行中有一個參數(shù)-verbosegc可以查看Java使用的堆內存的情況,由于這種方法會影響系統(tǒng)性能,不推薦使用。
(2)finalize()方法
在JVM垃圾回收器收集一個對象之前,一般要求程序調用適當?shù)姆椒ㄡ尫刨Y源,但在沒有明確釋放資源的情況下,Java提供了缺省機制來終止該對象心釋放資源,這個方法就是finalize()。
它的原型為:protected void finalize() throws Throwable
在finalize()方法返回之后,對象消失,垃圾收集開始執(zhí)行。之所以要使用finalize(),是存在著垃圾回收器不能處理的特殊情況。
七、GC使用與性能優(yōu)化管理
(1)不要顯式調用System.gc()。此函數(shù)建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發(fā)主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數(shù)。大大的影響系統(tǒng)性能。
(2)盡量減少臨時對象的使用。臨時對象在跳出函數(shù)調用后,會成為垃圾,少用臨時變量就相當于減少了垃圾的產生,從而延長了出現(xiàn)上述第二個觸發(fā)條件出現(xiàn)的時間,減少了主GC的機會。
(3)對象不用時最好顯式置為Null。一般而言,為Null的對象都會被作為垃圾處理,所以將不用的對象顯式地設為Null,有利于GC收集器判定垃圾,從而提高了GC的效率。
(4)盡量使用StringBuffer,而不用String來累加字符串。由于String是固定長的字符串對象,累加String對象時,并非在一個String對象中擴增,而是重新創(chuàng)建新的String對象,如Str5=Str1+Str2+Str3+Str4,這條語句執(zhí)行過程中會產生多個垃圾對象,因為對次作“+”操作時都必須創(chuàng)建新的String對象,但這些過渡對象對系統(tǒng)來說是沒有實際意義的,只會增加更多的垃圾。避免這種情況可以改用StringBuffer來累加字符串,因StringBuffer是可變長的,它在原有基礎上進行擴增,不會產生中間對象。
(5)能用基本類型如Int,Long,就不用Integer,Long對象;绢愋妥兞空加玫膬却尜Y源比相應對象占用的少得多,如果沒有必要,最好使用基本變量。
(6)盡量少用靜態(tài)對象變量。靜態(tài)變量屬于全局變量,不會被GC回收,它們會一直占用內存。
(7)注意分散對象創(chuàng)建或刪除的時間。集中在短時間內大量創(chuàng)建新對象,特別是大對象,會導致突然需要大量內存,JVM在面臨這種情況時,只能進行主GC,以回收內存或整合內存碎片,從而增加主GC的頻率。集中刪除對象,道理也是一樣的。
八、結束語
Java垃圾回收機制靠一篇博文很難講解完,雖然本人做了很大努力,但是還得投降,對于各個垃圾收集器的區(qū)別、運行過程中各內存區(qū)域參數(shù)的設置、GC日志的查看等內容,希望繼續(xù)關注博客,聯(lián)系博主,我會不斷的更新的!