本文主要淺談JAVA回收機制丹弱,讓初學者對這一塊大概有個簡單的認識,同時也記錄下自己學習的成果,溫故而知新。
疑問
- 什么是垃圾回收
- 為什么要進行垃圾回收
- 如何進行垃圾回收
- 常用優(yōu)化
1鳍徽、什么是垃圾回收
對于垃圾JVM的垃圾回收機制這里我們稱為 GC ,眾所周知敢课,Java 語言不需要像 C++ 那樣需要自己申請內(nèi)存阶祭,自己釋放內(nèi)存,這些都是JVM幫我們做好了的直秆,那么JVM對于內(nèi)存的回收過程就成為垃圾回收(GC)濒募。
2、為什么要進行垃圾回收
程序是運行在物理機之上圾结,而一臺物理機的內(nèi)存數(shù)是有上限的瑰剃,那么程序?qū)τ趦?nèi)存的使用也是有上限的,例如Java中大量的 new Object() 新建對象筝野,會占用內(nèi)存晌姚,往往我們在程序中只管新建粤剧,沒有去管它的回收,那么這些對象在新建之后就會保存在內(nèi)存當中挥唠,隨著時間增加大量的垃圾對象就會撐爆內(nèi)存抵恋,導致內(nèi)存溢出。
那么我們?nèi)粘i_發(fā)中為什么很少出現(xiàn)這種情況宝磨,這就是JVM的垃圾回收機制弧关,自動幫我們進行了回收,由此可見唤锉,為什么要進行垃圾回收梯醒,是為了更合理使用內(nèi)存,避免大量垃圾對象占用內(nèi)存腌紧。
3茸习、如何進行垃圾回收
3.1、判斷機制
上面談到垃圾回收機制壁肋,實質(zhì)是將垃圾對象進行回收号胚,那么問題來了,如何判斷一個對象是垃圾對象浸遗。
- 引用計數(shù)法
每個對象創(chuàng)建的時候猫胁,會分配一個引用計數(shù)器,當這個對象被引用的時候計數(shù)器就加1跛锌,當不被引用或者引用失效的時候計數(shù)器就會減1弃秆。任何時候,對象的引用計數(shù)器值為0就說明這個對象不被使用了髓帽,就認為是“垃圾”菠赚,可以被GC處理掉。
優(yōu)點:算法實現(xiàn)簡單郑藏。
缺點:無法解決循環(huán)引用(致命問題)
public class A(){
public A a;
}
public class B(){
public B b;
}
public class demo(){
public static void main(String[] args){
A a = new A();
B b = new B();
a.b = b;
b.a = a;
// 這種情況 這兩個對象永遠被引用數(shù)為1衡查,無法被垃圾回收機制回收。
}
}
-
根搜索算法
以一些特定的對象作為基礎(chǔ)原始對象必盖,或者稱作“根”拌牲,不斷往下搜索,到達某一個對象的路徑稱為引用鏈歌粥。如果一個對象和根對象之間有引用鏈塌忽,即根對象到這個對象是可到達的,則這個對象是活著的失驶,不是垃圾土居,還不能回收。反之,如果一個對象和根對象之間沒有引用鏈装盯,根對象到這個對象的路徑是不可達的坷虑,那么這個對象就是可回收的垃圾對象。(紅色區(qū)域被回收)
關(guān)系圖
3.2埂奈、回收機制
上文談到引用計數(shù)法存在致命問題迄损,所以這里講到的回收機制主要講根搜索算法,當JVM根據(jù)根搜索算法找到垃圾對象之后账磺,會如何去回收掉它芹敌,在此過程中會出現(xiàn)什么問題,以及各個算法的優(yōu)勢劣勢垮抗,根據(jù)優(yōu)勢劣勢如果去優(yōu)化等等...
-
標記-清除算法(Mark-Sweep)
i.標記Mark:從GC ROOTS開始氏捞,遍歷堆內(nèi)存區(qū)域的所有根對象,對在引用鏈上的對象都進行標記冒版。這樣下來液茎,如果是存活的對象就會被做了標記,反之如果是垃圾對象辞嗡,則沒做有標記捆等。GC很容易根據(jù)有沒有被做標記就完成了垃圾對象回收。
ii.清除Sweep:遍歷堆中的所有的對象(標記階段遍歷的是所有根節(jié)點)续室,找到未被標記的對象栋烤,直接回收所占的內(nèi)存,釋放空間挺狰。
優(yōu)點:沒有產(chǎn)生額外的內(nèi)存空間消耗明郭,內(nèi)存利用率高。
缺點:效率低丰泊,清除階段要遍歷所有的對象薯定;回收的垃圾對象是在各個角落的,直接回收垃圾對象趁耗,導致存在不連續(xù)的內(nèi)存空間沉唠,產(chǎn)生內(nèi)存碎片。標記-清除算法操作的對象是【垃圾對象】苛败,對于活著的對象(被標記的對象),它則直接不理睬径簿。
-
復制算法(Copying)
復制算法把內(nèi)存區(qū)間中年輕代一分為二(Eden與Survivor)罢屈,有對象存在的一半?yún)^(qū)間稱為“活動區(qū)間”,沒有對象存在處于空閑狀態(tài)的空間則為“空閑區(qū)間”篇亭。當內(nèi)存空間不足時觸發(fā)GC缠捌,先采用根搜索算法標記對象,然后把活著的對象全部復制到另一半空閑區(qū)間上,復制算法的“復制”就來自這一操作曼月。復制到另一半?yún)^(qū)間的時候谊却,嚴格按照內(nèi)存地址依次排列要存放的對象,然后一次性回收垃圾對象哑芹。
這樣原來的空閑區(qū)間在GC后就變成活動區(qū)間炎辨,而且內(nèi)存順序齊整美觀。原來的活動區(qū)間在GC后就變成了完全空的空閑區(qū)間聪姿,等待下一次GC把活的對象被copy進來碴萧。 -
標記-整理算法(Mark-Compact)
i.標記:這個階段和標記-清除Mark-Sweep算法一樣,遍歷GC ROOTS并標記存活的對象末购。
ii.整理:移動所有活著的對象到內(nèi)存區(qū)域的一側(cè)(具體在哪一側(cè)則由GC實現(xiàn))破喻,嚴格按照內(nèi)存地址次序依次排列活著的對象,然后將最后一個活著的對象地址以后的空間全部回收盟榴。優(yōu)點:內(nèi)存空間利用率高曹质,消除了復制算法內(nèi)存減半的情況;GC后不會產(chǎn)生內(nèi)存碎片擎场。
缺點:需要遍歷標記活著的對象咆繁,效率較低;復制移動對象后顶籽,還要維護這些活著對象的引用地址列表玩般。
-
分代回收算法(Generational Collecting)
分代回收算法就是現(xiàn)在JVM使用的GC回收算法。
內(nèi)存結(jié)構(gòu).png
i.先來看看簡單化后的堆的內(nèi)存結(jié)構(gòu):
Java堆 = 年老代 + 年輕代
(空間大小比例一般是3:1)
年輕代 = Eden區(qū) + Survivor區(qū) + Survivor區(qū)
(空間大小比例一般是8:1:1)
ii.按照對象存活時間長短礼饱,我們可以把對象簡單分為三類:
短命對象:存活時間較短的對象坏为,如中間變量對象、臨時對象镊绪、循環(huán)體創(chuàng)建的對象等匀伏。這也是產(chǎn)生最多數(shù)量的對象,GC回收的關(guān)注重點蝴韭。
長命對象:存活時間較長的對象够颠,如單例模式產(chǎn)生的單例對象、數(shù)據(jù)庫連接對象榄鉴、緩存對象等履磨。
長生對象:一旦創(chuàng)建則一直存活。
iii.對象分配區(qū)域
短命對象存在于年輕代庆尘,長命對象存在于年老代剃诅,而長生對象則存在于方法區(qū)中。
由于GC的主要內(nèi)存區(qū)域是堆驶忌,所以GC的對象主要就是短命對象和長命對象這類壽命“有限”的對象矛辕。
年輕代回收
當需要在堆中創(chuàng)建一個新的對象,而年輕代內(nèi)存不足時觸發(fā)一次GC,在年輕代觸發(fā)的GC稱為普通GC聊品,Minor GC飞蹂。注意到年輕代中的對象都是存活時間較短的對象,所以適合使用復制算法翻屈。這里肯定不會使用兩倍的內(nèi)存來實現(xiàn)復制算法了陈哑,牛人們是這樣解決的,把年輕代內(nèi)存組成是80%的Eden妖胀、10%的Survivor和10%的Survivor芥颈,然后在這些內(nèi)存區(qū)域直接進行復制。
剛開始創(chuàng)建的對象是在Eden中赚抡,此時Eden中有對象爬坑,而兩個Survivor區(qū)沒有對象,都是空閑區(qū)間涂臣。第一次Minor GC后盾计,存活的對象被放到其中一個Survivor,Eden中的內(nèi)存空間直接被回收赁遗。在下一次GC到來時署辉,Eden和一個survivor中又創(chuàng)建滿了對象,這個時候GC清除的就是Eden和這個放滿對象的survivor組成的大區(qū)域(占90%)岩四,Minor GC使用復制算法把活的對象復制到另一個空閑的survivor區(qū)間哭尝,然后直接回收之前90%的內(nèi)存。周而復始剖煌。始終會有一個10%空閑的Survivor區(qū)間材鹦,作為下一次Minor GC存放對象的準備空間。
要完成上面的算法耕姊,每次Minor GC過程都要滿足:
存活的對象大小都不能超過Survivor那10%的內(nèi)存空間桶唐,不然就沒有空間復制剩下的對象了。但是茉兰,萬一超過了呢尤泽?前面我們提到過年老代,對规脸,就是把這些大對象放到年老代坯约。
例如下圖中,Eden中有4個對象塊燃辖,其中AC對象如果被標記為垃圾對象鬼店,在GC的時候就會將BD對象復制到Survivor中,然后直接清除Eden跟另外一個Survivor黔龟。當對象被Copy的次數(shù)超過一個配置值的時候,就會從年輕代移動到年老代。
年老代回收
a.在年輕代中氏身,如果一個對象的年齡(GC一次后還存活的對象年歲加1)達到一個閾值(可以配置)巍棱,就會被移動到年老代。
b.Survivor中相同年齡的對象大小總和超過survivor空間的一半蛋欣,則不小于這個年齡的對象都會直接進入年老代航徙。
c.創(chuàng)建的對象的大小超過設(shè)定閾值,這個對象會被直接存進年老代陷虎。
d. 年輕代中大于survivor空間的對象到踏,Minor GC時會被移進年老代。
年老代中的對象特點就是存活時間較長尚猿,而且沒有備用的空閑空間窝稿,所以顯然不適合使用復制算法了,這個時候使用標記-清除算法或者標記-整理算法來實現(xiàn)GC凿掂。負責年老代中GC操作的是全局GC伴榔,Major GC,F(xiàn)ull GC庄萎。
什么時候觸發(fā)Major GC呢踪少?在Minor GC時,先檢測JVM的統(tǒng)計數(shù)據(jù)糠涛,查看歷史上進入老年代的對象平均大小是否大于目前年老代中的剩余空間援奢,如果大于則觸發(fā)Full GC。
3.2忍捡、執(zhí)行機制
串行GC
在搜索掃描和復制過程都是采用單線程實現(xiàn)集漾,適用于單CPU、新生代空間較小或者要求GC暫停時間要求不高的地方锉罐。是client級別的默認方式帆竹。
并行GC
在搜索掃描和復制過程都是采用多線程實現(xiàn),適用于多CPU脓规、或者要求GC暫停時間要求高的地方栽连。是server級別的默認方式。
同步GC
同時允許多個GC任務侨舆,減少GC暫停時間秒紧。主要應用在實時性要求重于總體吞吐量要求的中大型應用,即使如此挨下,降低中斷時間的技術(shù)還是會導致應用程序性能的降低熔恢。
日常優(yōu)化
年老代空間不足
1)分配足夠大空間給old gen。
2)避免直接創(chuàng)建過大對象或者數(shù)組臭笆,否則會繞過年輕代直接進入年老代叙淌。
3)應該使對象盡量在年輕代就被回收秤掌,或待得時間盡量久,避免過早的把對象移進年老代鹰霍。
方法區(qū)的永久代空間不足
1)分配足夠大空間給闻鉴。
2)避免創(chuàng)建過多的靜態(tài)對象。
被顯示調(diào)用System.gc()
通常情況下不要顯示地觸發(fā)GC茂洒,讓JVM根據(jù)自己的機制實現(xiàn)孟岛。
1.年輕代過小(年老代過大)
導致頻繁發(fā)生GC督勺,增大系統(tǒng)消耗
容易讓普通大文件直接進入年老代渠羞,從而更容易誘發(fā)Full GC。
2.年輕代過大(年老大過兄前А)
導致年老代過小次询,從而更容易誘發(fā)Full GC。
GC耗時增加盏触,降低GC的效率渗蟹。
3.Eden過大(survivor過小)
Minor GC時容易讓普通大文件直接繞過survivor進入年老代赞辩,從而更容易誘發(fā)Full GC雌芽。
4Eden過小(survivor過大)
導致GC頻率升高辨嗽,影響系統(tǒng)性能世落。
5調(diào)優(yōu)策略
保證系統(tǒng)吞吐量優(yōu)先
減少GC暫停時間優(yōu)先
堆設(shè)置
-Xms:初始堆大小
-Xmx:最大堆大小
-XX:NewSize=n:設(shè)置年輕代大小
-XX:NewRatio=n:設(shè)置年輕代和年老代的比值。如:為3糟需,表示年輕代與年老代比值為1:3屉佳,年輕代占整個年輕代年老代和的1/4
-XX:SurvivorRatio=n:年輕代中Eden區(qū)與兩個Survivor區(qū)的比值。注意Survivor區(qū)有兩個洲押。如:3武花,表示Eden:Survivor=3:2,一個Survivor區(qū)占整個年輕代的1/5
-XX:MaxPermSize=n:設(shè)置持久代大小
收集器設(shè)置
-XX:+UseSerialGC:設(shè)置串行收集器
-XX:+UseParallelGC:設(shè)置并行收集器
-XX:+UseParalledlOldGC:設(shè)置并行年老代收集器
-XX:+UseConcMarkSweepGC:設(shè)置并發(fā)收集器
垃圾回收統(tǒng)計信息
-XX:+PrintGC
-XX:+PrintGCDetails
-XX:+PrintGCTimeStamps
-Xloggc:filename
并行收集器設(shè)置
-XX:ParallelGCThreads=n:設(shè)置并行收集器收集時使用的CPU數(shù)杈帐。并行收集線程數(shù)体箕。
-XX:MaxGCPauseMillis=n:設(shè)置并行收集最大暫停時間
-XX:GCTimeRatio=n:設(shè)置垃圾回收時間占程序運行時間的百分比。公式為1/(1+n)
并發(fā)收集器設(shè)置
-XX:+CMSIncrementalMode:設(shè)置為增量模式挑童。適用于單CPU情況累铅。
-XX:ParallelGCThreads=n:設(shè)置并發(fā)收集器年輕代收集方式為并行收集時,使用的CPU數(shù)站叼。并行收集線程數(shù)娃兽。