淺談靜態(tài)變量的回收問(wèn)題

今天工作中遇到一個(gè)用于緩存數(shù)據(jù)到內(nèi)存的靜態(tài)變量Stringbuffer熟丸;當(dāng)緩存數(shù)據(jù)大小達(dá)到5M的時(shí)候就把該緩存數(shù)據(jù)寫(xiě)到S3上华望;然后清空該緩存buffer;看了這段代碼我覺(jué)得是不是有點(diǎn)問(wèn)題唠倦;先貼大概的代碼

package com.sanpang.demo;

public class StaticVariableDemo {
    
    //定義一個(gè)緩存buffer;存放數(shù)據(jù)到內(nèi)存;數(shù)據(jù)達(dá)到5M的時(shí)候就寫(xiě)到S3上面;
    public static StringBuffer cachebuffer = new  StringBuffer();

    public static void main(String[] args) {
        
        cachebuffer.append("存的數(shù)據(jù) 達(dá)到5M");
        //將數(shù)據(jù)存到S3 ;然后清空buffer
        cachebuffer = new StringBuffer();

    }

}

看了以上代碼;我擔(dān)心一個(gè)問(wèn)題是這里通過(guò)new 個(gè)新對(duì)象來(lái)起到清空buffer的作用:

cachebuffer = new StringBuffer();

這樣的寫(xiě)法會(huì)不會(huì)導(dǎo)致這個(gè)靜態(tài)變量cachebuffer 之前引用的實(shí)例還存在不;有沒(méi)有被GC回收;如果沒(méi)有會(huì)不會(huì)出現(xiàn)OOM的問(wèn)題丛塌?
帶著這個(gè)疑惑問(wèn)了很多技術(shù)小伙伴得到的回復(fù)都沒(méi)有解決我疑惑的問(wèn)題;于是自己分析了一下畜疾;自己主要疑惑的一個(gè)是這個(gè)靜態(tài)變量在JVM 中的內(nèi)存存儲(chǔ)赴邻;還有一個(gè)疑惑就是這個(gè)靜態(tài)對(duì)象之前引用的地址所指向的實(shí)例會(huì)不會(huì)被GC立即回收;
帶著問(wèn)題看了些關(guān)于JVM GC 的文章啡捶;下面寫(xiě)下我看了這些文章的自己的理解姥敛;不對(duì)的地方希望可以指出;

首先是JVM內(nèi)存結(jié)構(gòu)

根據(jù)《java虛擬機(jī)規(guī)范》規(guī)定届慈,JVM的基本結(jié)構(gòu)一般如下圖所示

Paste_Image.png

從左圖可知徒溪,JVM主要包括四個(gè)部分:
1.類加載器(ClassLoader):在JVM啟動(dòng)時(shí)或者在類運(yùn)行時(shí)將需要的class加載到JVM中忿偷。(右圖表示了從java源文件到JVM的整個(gè)過(guò)程金顿,可配合理解。 關(guān)于類的加載機(jī)制鲤桥,可以參考http://blog.csdn.net/tonytfjing/article/details/47212291
2.執(zhí)行引擎:負(fù)責(zé)執(zhí)行class文件中包含的字節(jié)碼指令(執(zhí)行引擎的工作機(jī)制揍拆,這里也不細(xì)說(shuō)了,這里主要介紹JVM結(jié)構(gòu))茶凳;
3.內(nèi)存區(qū)(也叫運(yùn)行時(shí)數(shù)據(jù)區(qū)):是在JVM運(yùn)行的時(shí)候操作所分配的內(nèi)存區(qū)嫂拴。運(yùn)行時(shí)內(nèi)存區(qū)主要可以劃分為5個(gè)區(qū)域,如圖:

Paste_Image.png
  • 方法區(qū)(Method Area):用于存儲(chǔ)類結(jié)構(gòu)信息的地方贮喧,包括常量池筒狠、靜態(tài)變量、構(gòu)造函數(shù)等箱沦。雖然JVM規(guī)范把方法區(qū)描述為堆的一個(gè)邏輯部分辩恼, 但它卻有個(gè)別名non-heap(非堆),所以大家不要搞混淆了。方法區(qū)還包含一個(gè)運(yùn)行時(shí)常量池灶伊。
  • java堆(Heap):存儲(chǔ)java實(shí)例或者對(duì)象的地方疆前。這塊是GC的主要區(qū)域(后面解釋)。從存儲(chǔ)的內(nèi)容我們可以很容易知道聘萨,方法區(qū)和堆是被所有java線程共享的竹椒。
  • java棧(Stack):java棧總是和線程關(guān)聯(lián)在一起米辐,每當(dāng)創(chuàng)建一個(gè)線程時(shí)胸完,JVM就會(huì)為這個(gè)線程創(chuàng)建一個(gè)對(duì)應(yīng)的java棧。在這個(gè)java棧中又會(huì)包含多個(gè)棧幀儡循,每運(yùn)行一個(gè)方法就創(chuàng)建一個(gè)棧幀舶吗,用于存儲(chǔ)局部變量表、操作棧择膝、方法返回值等誓琼。每一個(gè)方法從調(diào)用直至執(zhí)行完成的過(guò)程,就對(duì)應(yīng)一個(gè)棧幀在java棧中入棧到出棧的過(guò)程肴捉。所以java棧是現(xiàn)成私有的腹侣。
  • 程序計(jì)數(shù)器(PC Register):用于保存當(dāng)前線程執(zhí)行的內(nèi)存地址。由于JVM程序是多線程執(zhí)行的(線程輪流切換)齿穗,所以為了保證線程切換回來(lái)后傲隶,還能恢復(fù)到原先狀態(tài),就需要一個(gè)獨(dú)立的計(jì)數(shù)器窃页,記錄之前中斷的地方跺株,可見(jiàn)程序計(jì)數(shù)器也是線程私有的。
  • 本地方法棧(Native Method Stack):和java棧的作用差不多脖卖,只不過(guò)是為JVM使用到的native方法服務(wù)的乒省。

4.本地方法接口:主要是調(diào)用C或C++實(shí)現(xiàn)的本地方法及返回結(jié)果。二畦木、內(nèi)存分配
我覺(jué)得了解垃圾回收之前袖扛,得先了解JVM是怎么分配內(nèi)存的,然后識(shí)別哪些內(nèi)存是垃圾需要回收十籍,最后才是用什么方式回收蛆封。
Java的內(nèi)存分配原理與C/C++不同,C/C++每次申請(qǐng)內(nèi)存時(shí)都要malloc進(jìn)行系統(tǒng)調(diào)用勾栗,而系統(tǒng)調(diào)用發(fā)生在內(nèi)核空間惨篱,每次都要中斷進(jìn)行切換,這需要一定的開(kāi)銷围俘,而Java虛擬機(jī)是先一次性分配一塊較大的空間砸讳,然后每次new時(shí)都在該空間上進(jìn)行分配和釋放机断,減少了系統(tǒng)調(diào)用的次數(shù),節(jié)省了一定的開(kāi)銷绣夺,這有點(diǎn)類似于內(nèi)存池的概念吏奸;二是有了這塊空間過(guò)后,如何進(jìn)行分配和回收就跟GC機(jī)制有關(guān)了陶耍。
java一般內(nèi)存申請(qǐng)有兩種:靜態(tài)內(nèi)存和動(dòng)態(tài)內(nèi)存奋蔚。很容易理解,編譯時(shí)就能夠確定的內(nèi)存就是靜態(tài)內(nèi)存烈钞,即內(nèi)存是固定的泊碑,系統(tǒng)一次性分配,比如int類型變量毯欣;動(dòng)態(tài)內(nèi)存分配就是在程序執(zhí)行時(shí)才知道要分配的存儲(chǔ)空間大小馒过,比如java對(duì)象的內(nèi)存空間。根據(jù)上面我們知道酗钞,java棧腹忽、程序計(jì)數(shù)器、本地方法棧都是線程私有的砚作,線程生就生窘奏,線程滅就滅,棧中的棧幀隨著方法的結(jié)束也會(huì)撤銷葫录,內(nèi)存自然就跟著回收了着裹。所以這幾個(gè)區(qū)域的內(nèi)存分配與回收是確定的,我們不需要管的米同。但是java堆和方法區(qū)則不一樣骇扇,我們只有在程序運(yùn)行期間才知道會(huì)創(chuàng)建哪些對(duì)象,所以這部分內(nèi)存的分配和回收都是動(dòng)態(tài)的面粮。一般我們所說(shuō)的垃圾回收也是針對(duì)的這一部分少孝。
總之Stack的內(nèi)存管理是順序分配的,而且定長(zhǎng)但金,不存在內(nèi)存回收問(wèn)題韭山;而Heap 則是為java對(duì)象的實(shí)例隨機(jī)分配內(nèi)存郁季,不定長(zhǎng)度冷溃,所以存在內(nèi)存分配和回收的問(wèn)題;
三梦裂、垃圾檢測(cè)似枕、回收算法
垃圾收集器一般必須完成兩件事:檢測(cè)出垃圾;回收垃圾年柠。怎么檢測(cè)出垃圾凿歼?一般有以下幾種方法:
引用計(jì)數(shù)法:給一個(gè)對(duì)象添加引用計(jì)數(shù)器,每當(dāng)有個(gè)地方引用它,計(jì)數(shù)器就加1答憔;引用失效就減1味赃。
好了,問(wèn)題來(lái)了虐拓,如果我有兩個(gè)對(duì)象A和B心俗,互相引用,除此之外蓉驹,沒(méi)有其他任何對(duì)象引用它們城榛,實(shí)際上這兩個(gè)對(duì)象已經(jīng)無(wú)法訪問(wèn),即是我們說(shuō)的垃圾對(duì)象态兴。但是互相引用狠持,計(jì)數(shù)不為0,導(dǎo)致無(wú)法回收瞻润,所以還有另一種方法:
可達(dá)性分析算法:以根集對(duì)象為起始點(diǎn)進(jìn)行搜索喘垂,如果有對(duì)象不可達(dá)的話,即是垃圾對(duì)象绍撞。這里的根集一般包括java棧中引用的對(duì)象王污、方法區(qū)常良池中引用的對(duì)象
本地方法中引用的對(duì)象等。
總之楚午,JVM在做垃圾回收的時(shí)候昭齐,會(huì)檢查堆中的所有對(duì)象是否會(huì)被這些根集對(duì)象引用,不能夠被引用的對(duì)象就會(huì)被垃圾收集器回收矾柜。一般回收算法也有如下幾種:
1.標(biāo)記-清除(Mark-sweep)
算法和名字一樣阱驾,分為兩個(gè)階段:標(biāo)記和清除。標(biāo)記所有需要回收的對(duì)象怪蔑,然后統(tǒng)一回收里覆。這是最基礎(chǔ)的算法,后續(xù)的收集算法都是基于這個(gè)算法擴(kuò)展的缆瓣。
不足:效率低喧枷;標(biāo)記清除之后會(huì)產(chǎn)生大量碎片。效果圖如下:

Paste_Image.png
Paste_Image.png

2. 復(fù)制(Copying)
此算法把內(nèi)存空間劃為兩個(gè)相等的區(qū)域弓坞,每次只使用其中一個(gè)區(qū)域隧甚。垃圾回收時(shí),遍歷當(dāng)前使用區(qū)域渡冻,把正在使用中的對(duì)象復(fù)制到另外一個(gè)區(qū)域中戚扳。此算法每次只處理正在使用中的對(duì)象,因此復(fù)制成本比較小族吻,同時(shí)復(fù)制過(guò)去以后還能進(jìn)行相應(yīng)的內(nèi)存整理帽借,不會(huì)出現(xiàn)“碎片”問(wèn)題珠增。當(dāng)然,此算法的缺點(diǎn)也是很明顯的砍艾,就是需要兩倍內(nèi)存空間蒂教。效果圖如下:

Paste_Image.png

3.標(biāo)記-整理(Mark-Compact)
此算法結(jié)合了“標(biāo)記-清除”和“復(fù)制”兩個(gè)算法的優(yōu)點(diǎn)。也是分兩階段脆荷,第一階段從根節(jié)點(diǎn)開(kāi)始標(biāo)記所有被引用對(duì)象悴品,第二階段遍歷整個(gè)堆,把清除未標(biāo)記對(duì)象并且把存活對(duì)象“壓縮”到堆的其中一塊简烘,按順序排放苔严。此算法避免了“標(biāo)記-清除”的碎片問(wèn)題,同時(shí)也避免了“復(fù)制”算法的空間問(wèn)題孤澎。效果圖如下:

Paste_Image.png

4.分代收集算法
這是當(dāng)前商業(yè)虛擬機(jī)常用的垃圾收集算法届氢。分代的垃圾回收策略,是基于這樣一個(gè)事實(shí):不同的對(duì)象的生命周期是不一樣的覆旭。因此退子,不同生命周期的對(duì)象可以采取不同的收集方式,以便提高回收效率型将。

  • 年輕代:是所有新對(duì)象產(chǎn)生的地方寂祥。年輕代被分為3個(gè)部分——Enden區(qū)和兩個(gè)Survivor區(qū)(From和to)當(dāng)Eden區(qū)被對(duì)象填滿時(shí),就會(huì)執(zhí)行Minor GC七兜。并把所有存活下來(lái)的對(duì)象轉(zhuǎn)移到其中一個(gè)survivor區(qū)(假設(shè)為from區(qū))丸凭。Minor GC同樣會(huì)檢查存活下來(lái)的對(duì)象,并把它們轉(zhuǎn)移到另一個(gè)survivor區(qū)(假設(shè)為to區(qū))腕铸。這樣在一段時(shí)間內(nèi)惜犀,總會(huì)有一個(gè)空的survivor區(qū)。經(jīng)過(guò)多次GC周期后狠裹,仍然存活下來(lái)的對(duì)象會(huì)被轉(zhuǎn)移到年老代內(nèi)存空間虽界。通常這是在年輕代有資格提升到年老代前通過(guò)設(shè)定年齡閾值來(lái)完成的。需要注意涛菠,Survivor的兩個(gè)區(qū)是對(duì)稱的莉御,沒(méi)先后關(guān)系,from和to是相對(duì)的俗冻。
  • 年老代:在年輕代中經(jīng)歷了N次回收后仍然沒(méi)有被清除的對(duì)象礁叔,就會(huì)被放到年老代中,可以說(shuō)他們都是久經(jīng)沙場(chǎng)而不亡的一代言疗,都是生命周期較長(zhǎng)的對(duì)象晴圾。對(duì)于年老代和永久代颂砸,就不能再采用像年輕代中那樣搬移騰挪的回收算法噪奄,因?yàn)槟切?duì)于這些回收戰(zhàn)場(chǎng)上的老兵來(lái)說(shuō)是小兒科死姚。通常會(huì)在老年代內(nèi)存被占滿時(shí)將會(huì)觸發(fā)Full GC,回收整個(gè)堆內(nèi)存。
  • 持久代:用于存放靜態(tài)文件勤篮,比如java類都毒、方法等。持久代對(duì)垃圾回收沒(méi)有顯著的影響碰缔。
    分代回收的效果圖如下:
Paste_Image.png

最后講分代账劲,是因?yàn)榉执锷婕傲饲懊鎺追N算法。年輕代:涉及了復(fù)制算法金抡;年老代:涉及了“標(biāo)記-整理(Mark-Sweep)”的算法瀑焦。

總結(jié)

通過(guò)對(duì)以上知識(shí)點(diǎn)的理解;再來(lái)看我的問(wèn)題:
這里就先不說(shuō)那么細(xì)梗肝;我的理解是在JVM啟動(dòng)時(shí)候通過(guò)類裝載器將class加載到JVM中榛瓮;
靜態(tài)變量

public static StringBuffer cachebuffer = new  StringBuffer();

cachebuffer 被JVM存儲(chǔ)在方法區(qū);

new StringBuffer(); 這個(gè)對(duì)象被JVM存儲(chǔ)在java堆(Heap)中
后面創(chuàng)建的StringBuffer();也存在java堆(Heap)中巫击;
當(dāng)靜態(tài)變量指向新的對(duì)象的時(shí)候

cachebuffer = new StringBuffer();

之前的5M的StringBuffer對(duì)象是還在內(nèi)存中的禀晓;沒(méi)有立馬被GC回收的;
按照GC收集的機(jī)制:觸發(fā)GC回收年輕代的數(shù)據(jù)是年輕代的堆內(nèi)存滿了坝锰;才會(huì)觸發(fā)粹懒;
如果之前cachebuffer 還沒(méi)被清空(創(chuàng)建新的對(duì)象取代)的時(shí)候就觸發(fā)了年輕代的GC 那么cachebuffer 就會(huì)會(huì)被從Enden區(qū)到survivor區(qū)甚至到Old Memory區(qū)域;
可以想象出這個(gè)代碼是有問(wèn)題的顷级;
后來(lái)又研究了下這個(gè)靜態(tài)變量會(huì)什么時(shí)候被GC回收:
靜態(tài)變量是屬于類的不是屬于實(shí)例對(duì)象的凫乖;所以這個(gè)類存在靜態(tài)變量就會(huì)一直存在;靜態(tài)變量存儲(chǔ)在方法區(qū)弓颈;用于保存類常量以及字符串常量拣凹。注意,這個(gè)區(qū)域不是用于存儲(chǔ)那些從老年代存活下來(lái)的對(duì)象恨豁,按照分代屬于持久代嚣镜;這個(gè)區(qū)域也可能發(fā)生GC。發(fā)生在這個(gè)區(qū)域的GC事件也被算為 Major GC 橘蜜。只不過(guò)在這個(gè)區(qū)域發(fā)生GC的條件非常嚴(yán)苛菊匿,必須符合以下三種條件才會(huì)被回收:
1、所有實(shí)例被回收

2计福、加載該類的ClassLoader 被回收

3跌捆、Class 對(duì)象無(wú)法通過(guò)任何途徑訪問(wèn)(包括反射)

所以一般來(lái)說(shuō) 靜態(tài)變量很少會(huì)被GC回收的;

參考鏈接:(準(zhǔn)確的來(lái)說(shuō)是copy鏈接~下面兩篇寫(xiě)的都很不錯(cuò))

https://zhuanlan.zhihu.com/p/25539690
http://blog.csdn.net/tonytfjing/article/details/44278233
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末象颖,一起剝皮案震驚了整個(gè)濱河市佩厚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌说订,老刑警劉巖抄瓦,帶你破解...
    沈念sama閱讀 211,948評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件潮瓶,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡钙姊,警方通過(guò)查閱死者的電腦和手機(jī)毯辅,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,371評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)煞额,“玉大人思恐,你說(shuō)我怎么就攤上這事〔不伲” “怎么了胀莹?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,490評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)婚温。 經(jīng)常有香客問(wèn)我嗜逻,道長(zhǎng),這世上最難降的妖魔是什么缭召? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,521評(píng)論 1 284
  • 正文 為了忘掉前任栈顷,我火速辦了婚禮,結(jié)果婚禮上嵌巷,老公的妹妹穿的比我還像新娘萄凤。我一直安慰自己,他們只是感情好搪哪,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,627評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布靡努。 她就那樣靜靜地躺著,像睡著了一般晓折。 火紅的嫁衣襯著肌膚如雪惑朦。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 49,842評(píng)論 1 290
  • 那天漓概,我揣著相機(jī)與錄音漾月,去河邊找鬼。 笑死胃珍,一個(gè)胖子當(dāng)著我的面吹牛梁肿,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播觅彰,決...
    沈念sama閱讀 38,997評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吩蔑,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了填抬?” 一聲冷哼從身側(cè)響起烛芬,我...
    開(kāi)封第一講書(shū)人閱讀 37,741評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后赘娄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體仆潮,經(jīng)...
    沈念sama閱讀 44,203評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,534評(píng)論 2 327
  • 正文 我和宋清朗相戀三年擅憔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了鸵闪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檐晕。...
    茶點(diǎn)故事閱讀 38,673評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡暑诸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出辟灰,到底是詐尸還是另有隱情个榕,我是刑警寧澤,帶...
    沈念sama閱讀 34,339評(píng)論 4 330
  • 正文 年R本政府宣布芥喇,位于F島的核電站西采,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏继控。R本人自食惡果不足惜械馆,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,955評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望武通。 院中可真熱鬧霹崎,春花似錦、人聲如沸冶忱。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,770評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)囚枪。三九已至派诬,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間链沼,已是汗流浹背默赂。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,000評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留括勺,地道東北人放可。 一個(gè)月前我還...
    沈念sama閱讀 46,394評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像朝刊,于是被迫代替她去往敵國(guó)和親耀里。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,562評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容