為什么需要垃圾回收
如果不進(jìn)行垃圾回收搁胆,內(nèi)存遲早都會(huì)被消耗空,因?yàn)槲覀冊(cè)诓粩嗟姆峙鋬?nèi)存空間而不進(jìn)行回收渠旁。除非內(nèi)存無(wú)限大船逮,我們可以任性的分配而不回收,但是事實(shí)并非如此挖胃。所以,垃圾回收是必須的酱鸭。
哪些內(nèi)存需要回收?
哪些內(nèi)存需要回收是垃圾回收機(jī)制第一個(gè)要考慮的問(wèn)題烁登,所謂“要回收的垃圾”無(wú)非就是那些不可能再被任何途徑使用的對(duì)象。那么如何找到這些對(duì)象饵沧?
可達(dá)性分析法赌躺,這個(gè)算法的基本思想是通過(guò)一系列稱(chēng)為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)向下搜索礼患,搜索所走過(guò)的路徑稱(chēng)為引用鏈,當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈(即GC Roots到對(duì)象不可達(dá))時(shí)讶泰,則證明此對(duì)象是不可用的。
如何選取GCRoots對(duì)象呢码泞?在Java語(yǔ)言中,可以作為GCRoots的對(duì)象包括下面幾種:
(1) 虛擬機(jī)棧(棧幀中的局部變量區(qū)余寥,也叫做局部變量表)中引用的對(duì)象。
(2) 方法區(qū)中的類(lèi)靜態(tài)屬性引用的對(duì)象宋舷。
(3) 方法區(qū)中常量引用的對(duì)象。
(4) 本地方法棧中JNI(Native方法)引用的對(duì)象祝蝠。
下面給出一個(gè)GCRoots的例子,如下圖绎狭,為GCRoots的引用鏈。
由圖可知喇聊,obj8蹦狂、obj9、obj10都沒(méi)有到GCRoots對(duì)象的引用鏈凯楔,即便obj9和obj10之間有引用鏈,他們還是會(huì)被當(dāng)成垃圾處理啊研,可以進(jìn)行回收。
四種引用狀態(tài)
在JDK1.2之前,Java中引用的定義很傳統(tǒng):如果引用類(lèi)型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另一塊內(nèi)存的起始地址削解,就稱(chēng)這塊內(nèi)存代表著一個(gè)引用。這種定義很純粹氛驮,但是太過(guò)于狹隘,一個(gè)對(duì)象只有被引用或者沒(méi)被引用兩種狀態(tài)盏缤。我們希望描述這樣一類(lèi)對(duì)象:當(dāng)內(nèi)存空間還足夠時(shí),則能保留在內(nèi)存中唉铜;如果內(nèi)存空間在進(jìn)行垃圾收集后還是非常緊張律杠,則可以拋棄這些對(duì)象竞惋。很多系統(tǒng)的緩存功能都符合這樣的應(yīng)用場(chǎng)景灰嫉。在JDK1.2之后,Java對(duì)引用的概念進(jìn)行了擴(kuò)充讼撒,將引用分為強(qiáng)引用、軟引用根盒、弱引用、虛引用4種郑象,這4種引用強(qiáng)度依次減弱。
1厂榛、強(qiáng)引用
代碼中普遍存在的類(lèi)似"Object obj = new Object()"這類(lèi)的引用,只要強(qiáng)引用還存在辈双,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。
2湃望、軟引用
描述有些還有用但并非必需的對(duì)象痰驱。在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍進(jìn)行二次回收担映。如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常蝇完。Java中的類(lèi)SoftReference表示軟引用。
3氢架、弱引用
描述非必需對(duì)象。被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾回收之前岖研,垃圾收集器工作之后警检,無(wú)論當(dāng)前內(nèi)存是否足夠硬纤,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。Java中的類(lèi)WeakReference表示弱引用筝家。
4邻辉、虛引用
這個(gè)引用存在的唯一目的就是在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知,被虛引用關(guān)聯(lián)的對(duì)象值骇,和其生存時(shí)間完全沒(méi)關(guān)系。Java中的類(lèi)PhantomReference表示虛引用道伟。
JAVA什么時(shí)候執(zhí)行垃圾回收
JAVA的垃圾回收分為三個(gè)區(qū)域:新生代使碾,老年代,永久代票摇。
1、新生代:
- Eden(伊甸園)區(qū)是新對(duì)象分配內(nèi)存的地方矢门,由于堆是所有線程共享的,因此在堆上分配內(nèi)存需要加鎖隔躲。而Sun JDK為提升效率,會(huì)為每個(gè)新建的線程在Eden上分配一塊獨(dú)立的空間由該線程獨(dú)享蹭越,這塊空間稱(chēng)為T(mén)LAB(Thread Local Allocation Buffer)教届。在TLAB上分配內(nèi)存不需要加鎖驾霜,因此JVM在給線程中的對(duì)象分配內(nèi)存時(shí)會(huì)盡量在TLAB上分配。如果對(duì)象過(guò)大或TLAB用完强霎,則仍然在堆上進(jìn)行分配。如果Eden區(qū)內(nèi)存也用完了城舞,則會(huì)進(jìn)行一次Minor GC(young GC)。
-
Survivor from to
Survivor 區(qū)有兩塊家夺,一塊稱(chēng)為from區(qū),另一塊為to區(qū)拉馋,這兩個(gè)區(qū)是相對(duì)的,在發(fā)生一次Minor GC后煌茴,from區(qū)就會(huì)和to區(qū)互換。在發(fā)生Minor GC時(shí)蔓腐,Eden區(qū)和Survivor from區(qū)會(huì)把一些仍然存活的對(duì)象復(fù)制進(jìn)Survivor to區(qū),并清除內(nèi)存散罕。Survivor to區(qū)會(huì)把一些存活得足夠舊的對(duì)象移至年老代。
2笨使、老年代:
老年代里存放的都是存活時(shí)間較久的僚害,大小較大的對(duì)象,因此年老代使用標(biāo)記整理算法萨蚕。當(dāng)年老代容量滿的時(shí)候,會(huì)觸發(fā)一次Major GC(full GC)岳遥,回收年老代和年輕代中不再被使用的對(duì)象資源。
3派继、 永久代
在JDK8之前的HotSpot虛擬機(jī)中,類(lèi)的這些“永久的”數(shù)據(jù)存放在一個(gè)叫做永久代的區(qū)域驾窟。永久代一段連續(xù)的內(nèi)存空間认轨,我們?cè)贘VM啟動(dòng)之前可以通過(guò)設(shè)置-XX:MaxPermSize的值來(lái)控制永久代的大小,32位機(jī)器默認(rèn)的永久代的大小為64M,64位的機(jī)器則為85M杉畜。永久代的垃圾回收和老年代的垃圾回收是綁定的,一旦其中一個(gè)區(qū)域被占滿此叠,這兩個(gè)區(qū)都要進(jìn)行垃圾回收匾荆。但是有一個(gè)明顯的問(wèn)題,由于我們可以通過(guò)?XX:MaxPermSize 設(shè)置永久代的大小牙丽,一旦類(lèi)的元數(shù)據(jù)超過(guò)了設(shè)定的大小,程序就會(huì)耗盡內(nèi)存烤芦,并出現(xiàn)內(nèi)存溢出錯(cuò)誤OutOfMemoryError(OOM)。這里值得注意的是铜涉,JDK8移除了永久代。
總結(jié)一下:
- 一個(gè)對(duì)象實(shí)例化時(shí)芙代,先去看伊甸園有沒(méi)有足夠的空間;
- 如果有纹烹,不進(jìn)行垃圾回收召边,對(duì)象直接在伊甸園存儲(chǔ);
- 如果伊甸園內(nèi)存已滿隧熙,會(huì)進(jìn)行一次minor gc;然后再進(jìn)行判斷伊甸園中的內(nèi)存是否足夠;
- 如果不足贞盯,則去看存活區(qū)的內(nèi)存是否足夠;如果內(nèi)存足夠,把伊甸園部分活躍對(duì)象保存在存活區(qū)躏敢,然后把對(duì)象保存在伊甸園。
- 如果內(nèi)存不足,向老年代發(fā)送請(qǐng)求,查詢老年代的內(nèi)存是否足夠蛾扇;
- 如果老年代內(nèi)存足夠,將部分存活區(qū)的活躍對(duì)象存入老年代坟漱。然后把伊甸園的活躍對(duì)象放入存活區(qū),對(duì)象依舊保存在伊甸園芋齿。
- 如果老年代內(nèi)存不足成翩,會(huì)進(jìn)行一次full gc,之后老年代會(huì)再進(jìn)行判斷內(nèi)存是否足夠麻敌。如果足夠,同上术羔;如果不足,會(huì)拋出OutOfMemory Error级历。
需要注意的是,GC雖然可以進(jìn)行內(nèi)存空間的釋放玩讳,但同時(shí)頻繁的GC一定會(huì)影響性能,GC發(fā)生的頻率越低锋边,你的系統(tǒng)就越高效。