JVM_06 垃圾回收相关概念[ 二 ]
一、 System.gc()的理解
在默認(rèn)情況下,通過System.gc( )或者Runtime . getRuntime( ).gc( )的調(diào)用,會(huì)顯式觸發(fā)Full GC,同時(shí)對(duì)老年代和新生代進(jìn)行回收,嘗試釋放被丟棄對(duì)象占用的內(nèi)存。
然而System.gc()調(diào)用附帶一個(gè)免責(zé)聲明,無法保證對(duì)垃圾收集器的調(diào)用(無法保證馬上觸發(fā)GC)。[不保證一定會(huì)發(fā)生垃圾收集,只是給jvm發(fā)出提示]
JVM實(shí)現(xiàn)者可以通過system.gc( )調(diào)用來決定JVM的GC行為。而一般情況下,垃圾回收應(yīng)該是自動(dòng)進(jìn)行的,無須手動(dòng)觸發(fā),否則就太過于麻煩了。在一些特殊情況下,如我們正在編寫一個(gè)性能基準(zhǔn),我們可以在運(yùn)行之間調(diào)用System.gc( )
以下代碼,如果注掉System.runFinalization( ); 那么控制臺(tái)不保證一定打印,證明了System.gc( )無法保證GC一定執(zhí)行
二、內(nèi)存溢出(out of Memory)
javadoc中對(duì)OutOfMemoryError的解釋是,沒有空閑內(nèi)存,并且垃圾收集器也無法提供更多內(nèi)存
說明Java虛擬機(jī)的堆內(nèi)存不夠。原因有二:
(比如,我們?nèi)シ峙湟灰粋€(gè)超大對(duì)象,類似一個(gè)超大數(shù)組超過堆的最大值,JVM可以判斷出垃圾收集并不能解決這個(gè)問題,所以直接拋出OutOfMemoryError)
三、 內(nèi)存泄漏(Memory Leak)
也稱作“存儲(chǔ)滲漏”。嚴(yán)格來說,只有對(duì)象不會(huì)再被程序用到了,但是GC又不能回收他們的情況,才叫內(nèi)存泄漏。
但實(shí)際情況很多時(shí)候一些不太好的實(shí)踐(或疏忽)會(huì)導(dǎo)致對(duì)象的生命周期變得很長(zhǎng)甚至導(dǎo)致OOM,也可以叫做寬泛意義上的“內(nèi)存泄漏
盡管內(nèi)存泄漏并不會(huì)立刻引起程序崩潰,但是一旦發(fā)生內(nèi)存泄漏,程序中的可用內(nèi)存就會(huì)被逐步蠶食,直至耗盡所有內(nèi)存,最終出現(xiàn)0utOfMemory異常,導(dǎo)致程序崩潰。
舉例
getConnection()),網(wǎng)絡(luò)連接(socket)和io連接必須手動(dòng)close,否則是不能被回收的。
四、Stop The World
Stop一the一World,簡(jiǎn)稱STW,指的是Gc事件發(fā)生過程中,會(huì)產(chǎn)生應(yīng)用程序的停頓。停頓產(chǎn)生時(shí)整個(gè)應(yīng)用程序線程都會(huì)被暫停,沒有任何響應(yīng),有點(diǎn)像卡死的感覺,這個(gè)停頓稱為STW
STW事件和采用哪款GC無關(guān),所有的GC都有這個(gè)事件。
哪怕是G1也不能完全避免Stop一the一world情況發(fā)生,只能說垃圾回收器越來越優(yōu)秀,回收效率越來越高,盡可能地縮短了暫停時(shí)間。
STW是JVM在后臺(tái)自動(dòng)發(fā)起和自動(dòng)完成的。在用戶不可見的情況下,把用戶正常的工作線程全部停掉
開發(fā)中不要用System.gc();會(huì)導(dǎo)致Stop一the一world的發(fā)生。
6.什么情況下會(huì)導(dǎo)致stop the world 記住
-
可達(dá)性分析算法中枚舉根節(jié)點(diǎn)(GC Roots)會(huì)導(dǎo)致所有Java執(zhí)行線程停頓
-
進(jìn)行g(shù)c的時(shí)候會(huì)發(fā)生STW現(xiàn)象(調(diào)用finalize()方法的時(shí)候會(huì)暫停用戶線程
-
System.gc( ) | 調(diào)用finalize( )方法
五、多線程中的并行與并發(fā)
- 在操作系統(tǒng)中,是指一個(gè)時(shí)間段中有幾個(gè)程序都處于己?jiǎn)?dòng)運(yùn)行到運(yùn)行完畢之間,且這幾個(gè)程序都是在同一個(gè)處理器_上運(yùn)行
- 并發(fā)不是真正意義上的“同時(shí)進(jìn)行”,只是CPU把一個(gè)時(shí)間段劃分成幾個(gè)時(shí)間片段(時(shí)間區(qū)間),然后在這幾個(gè)時(shí)間區(qū)間之間來回切換,由于CPU處理的速度非常快,只要時(shí)間間隔處理得當(dāng),即可讓用戶感覺是多個(gè)應(yīng)用程序同時(shí)在進(jìn)行
- 圖解:
- 當(dāng)系統(tǒng)有一個(gè)以上CPU時(shí),當(dāng)一個(gè)CPU執(zhí)行一個(gè)進(jìn)程時(shí),另一個(gè)CPU可以執(zhí)行另一個(gè)進(jìn)程,兩個(gè)進(jìn)程互不搶占CPU資源,可以同時(shí)進(jìn)行,我們稱之為并行(Parallel)
- 其實(shí)決定并行的因素不是CPU的數(shù)量,而是CPU的核心數(shù)量,比如一個(gè)CPU多個(gè)核也可以 并行
- 圖解:
六、 垃圾回收的并行、串行、并發(fā)
并行(Parallel) :指多條垃圾收集線程并行工作,但此時(shí)用戶線程仍處于等待狀態(tài)。如ParNew、 Parallel Scavenge、 Parallel 0ld;
串行(Serial)
- 相較于并行的概念,單線程執(zhí)行。
- 如果內(nèi)存不夠,則程序暫停,啟動(dòng)JVM垃圾回收器進(jìn)行垃圾回收。回收完,再啟動(dòng)程序的線程。
- 圖解:
- 指用戶線程與垃圾收集線程同時(shí)執(zhí)行(但不一定是并行的,可能會(huì)交替執(zhí)行),垃圾回收線程在執(zhí)行時(shí)不會(huì)停頓用戶程序的運(yùn)行
- 在同一個(gè)時(shí)間段,用戶線程和垃圾回收線程同時(shí)執(zhí)行
- 圖解
七、 安全點(diǎn)(Safepoint)
程序執(zhí)行時(shí)并非在所有地方都能停頓下來開始GC,只有在特定的位置才能停頓下來開始GC,這些位置稱為 “安全點(diǎn)(Safepoint)”
Safe Point的選擇很重要,如果太少可能導(dǎo)致GC等待的時(shí)間太長(zhǎng),如果太頻繁可能導(dǎo)致運(yùn)行時(shí)的性能問題。大部分指令的執(zhí)行時(shí)間都非常短暫,通常會(huì)根據(jù)“是否具有讓程序長(zhǎng)時(shí)間執(zhí)行的特征”為標(biāo)準(zhǔn)。比如:選擇些執(zhí)行時(shí)間較長(zhǎng)的指令作為Safe Point, 如方法調(diào)用、循環(huán)跳轉(zhuǎn)和異常跳轉(zhuǎn)等。
如何在GC發(fā)生時(shí),檢查所有線程都跑到最近的安全點(diǎn)停頓下來呢?掌握
- 搶先式中斷: (目前沒有虛擬機(jī)采用了) 首先中斷所有線程。如果還有線程不在安全點(diǎn),就恢復(fù)線程,讓線程跑到安全點(diǎn)。
- 主動(dòng)式中斷:設(shè)置一個(gè)中斷標(biāo)志,各個(gè)線程運(yùn)行到Safe Point的時(shí)候主動(dòng)輪詢這個(gè)標(biāo)志,如果中斷標(biāo)志為真,則將自己進(jìn)行中斷掛起。
八、安全區(qū)域(Safe Region)
Safepoint機(jī)制保證了程序執(zhí)行時(shí),在不太長(zhǎng)的時(shí)間內(nèi)就會(huì)遇到可進(jìn)入GC的Safepoint 。但是,程序“不執(zhí)行”的時(shí)候呢?例如線程處于Sleep 狀態(tài)或Blocked狀態(tài),這時(shí)候線程無法響應(yīng)JVM的中斷請(qǐng)求,“走” 到安全點(diǎn)去中斷掛起,JVM也不太可能等待線程被喚醒。對(duì)于這種情況,就需要安全區(qū)域(Safe Region)來解決。
安全區(qū)域是指在一段代碼片段中,對(duì)象的引用關(guān)系不會(huì)發(fā)生變化,在這個(gè)區(qū)域中的任何位置開始GC都是安全的。我們也可以把Safe Region看做是被擴(kuò)展了的Safepoint。
實(shí)際執(zhí)行時(shí):
- 當(dāng)線程運(yùn)行到Safe Region的代碼時(shí),首先標(biāo)識(shí)已經(jīng)進(jìn)入了Safe Region,如果這段時(shí)間內(nèi)發(fā)生GC,JVM會(huì)忽略標(biāo)識(shí)為Safe Region狀態(tài)的線程;
- 當(dāng)線程即將離開Safe Region時(shí),會(huì)檢查JVM是否已經(jīng)完成GC,如果完成了,則繼續(xù)運(yùn)行,否則線程必須等待直到收到可以安全離開SafeRegion的信號(hào)為止;
九、引用
我們希望能描述這樣一類對(duì)象: 當(dāng)內(nèi)存空間還足夠時(shí),則能保留在內(nèi)存中;如果內(nèi)存空間在進(jìn)行垃圾收集后還是很緊張,則可以拋棄這些對(duì)象。 -【既偏門又非常高頻的面試題】強(qiáng)引用、軟引用、弱引用、虛引用有什么區(qū)別?具體使用.場(chǎng)景是什么?
在JDK 1.2版之后,Java對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strong
Reference)、軟引用(Soft Reference) 、弱引用(Weak Reference) 和虛引用(Phantom Reference) 4種,這4種引用強(qiáng)度依次逐漸減弱
除強(qiáng)引用外,其他3種引用均可以在java.lang.ref包中找到它們的身影。如下圖,顯示了這3種引用類型對(duì)應(yīng)的類,開發(fā)人員可以在應(yīng)用程序中直接使用它們。
Reference子類中只有終結(jié)器引用是包內(nèi)可見的,其他3種引用類型均為public,可以在應(yīng)用程序中直接使用
簡(jiǎn)單介紹下強(qiáng)軟弱虛引用
- 強(qiáng)引用(StrongReference):最傳統(tǒng)的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“0bject obj=new object( )”這種引用關(guān)系。無論任何情況下,只要強(qiáng)引用關(guān)系還存在,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。
- 軟引用(SoftReference):在系統(tǒng)將要發(fā)生內(nèi)存溢出之前,將會(huì)把這些對(duì)象列入回收范圍之中進(jìn)行第二次回收。如果這次回收后還沒有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。內(nèi)存不足即回收
- 弱引用(WeakReference):被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集之前。當(dāng)垃圾收集器工作時(shí),無論內(nèi)存空間是否足夠,都會(huì)回收掉被弱引用關(guān)聯(lián)的對(duì)象。發(fā)現(xiàn)即回收
- 虛引用(PhantomReference):一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)
間構(gòu)成影響,也無法通過虛引用來獲得一個(gè)對(duì)象的實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知(回收跟蹤)
①. 強(qiáng)引用:不回收
-
在Java程序中,最常見的引用類型是強(qiáng)引用(普通系統(tǒng)99%以上都是強(qiáng)引用),也就是我們最常見的普通對(duì)象引用,也是默認(rèn)的引用類型。
-
當(dāng)在Java語言中使用new操作符創(chuàng)建一個(gè)新的對(duì)象, 并將其賦值給一個(gè)變量的時(shí)候,這個(gè)變量就成為指向該對(duì)象的一個(gè)強(qiáng)引用。
-
強(qiáng)引用的對(duì)象是可觸及的,垃圾收集器就永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。
-
對(duì)于一個(gè)普通的對(duì)象,如果沒有其他的引用關(guān)系,只要超過了引用的作用域或者顯式地將相應(yīng)(強(qiáng))引用賦值為null,就是可以當(dāng)做垃圾被收集了,當(dāng)然具體回收時(shí)機(jī)還是要看垃圾收集策略。
-
相對(duì)的,軟引用、弱引用和虛引用的對(duì)象是軟可觸及、弱可觸及和虛可觸及的,在一定條件下,都是可以被回收的。所以,強(qiáng)引用是造成Java內(nèi)存泄漏的主要原因之一。
②. 軟引用: 內(nèi)存不足即回收
軟引用是用來描述一
些還有用,但非必需的對(duì)象。只被軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常前,會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收,如果這次回收還沒有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。
注意:一次回收是回收強(qiáng)引用中沒有引用的對(duì)象
軟引用通常用來實(shí)現(xiàn)內(nèi)存敏感的緩存。比如:高速緩存就有用到軟引用。如果還有空閑內(nèi)存,就可以暫時(shí)保留緩存,當(dāng)內(nèi)存不足時(shí)清理掉,這樣就保證了使用緩存的同時(shí),不會(huì)耗盡內(nèi)存
類似弱引用,只不過Java虛擬機(jī)會(huì)盡量讓軟引用的存活時(shí)間長(zhǎng)一些,迫不得.已才清理
軟引用: 當(dāng)內(nèi)存足夠: 不會(huì)回收軟用的可達(dá)對(duì)象 當(dāng)內(nèi)存不夠時(shí): 會(huì)回收軟引用的可達(dá)對(duì)象
在JDK 1. 2版之后提供了java.lang.ref.SoftReference類來實(shí)現(xiàn)軟引用。
③. 弱引用: 發(fā)現(xiàn)即回收
弱引用也是用來描述那些非必需對(duì)象,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生為止。在系統(tǒng)GC時(shí),只要發(fā)現(xiàn)弱引用,不管系統(tǒng)堆空間使用是否充足,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象
但是,由于垃圾回收器的線程通常優(yōu)先級(jí)很低,因此,并不一 定能很快地發(fā)現(xiàn)持有弱引用的對(duì)象。在這種情況下,弱引用對(duì)象可以存在較長(zhǎng)的時(shí)間。
弱引用和軟引用一樣,在構(gòu)造弱引用時(shí),也可以指定一個(gè)引用隊(duì)列,當(dāng)弱引用對(duì)象被回收時(shí),就會(huì)加入指定的引用隊(duì)列,通過這個(gè)隊(duì)列可以跟蹤對(duì)象的回收情況。
軟引用、弱引用都非常適合來保存那些可有可無的緩存數(shù)據(jù)。如果這么做,當(dāng)系統(tǒng)內(nèi)存不足時(shí),這些緩存數(shù)據(jù)會(huì)被回收,不會(huì)導(dǎo)致內(nèi)存溢出。而當(dāng)內(nèi)存資源充足時(shí),這些緩存數(shù)據(jù)又可以存在相當(dāng)長(zhǎng)的時(shí)間,從而起到加速系統(tǒng)的作用
在JDK1.2版之后提供了java.lang.ref.WeakReference類來實(shí)現(xiàn)弱引用
- 通過查看WeakHashMap源碼,可以看到其內(nèi)部類Entry使用的就是弱引用
- line 702 -> private static class Entry<K,V> extends WeakReference implements Map.Entry<K,V> {…}
④. 虛引用: 對(duì)象回收跟蹤
虛引用(Phantom Reference),也稱為“幽靈引用”或者“幻影引用”,是所有引用類型中最弱的一個(gè)。
為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的在于跟蹤垃圾回收過程。比如:能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。
虛引用必須和引用隊(duì)列一起使用。虛引用在創(chuàng)建時(shí)必須提供一個(gè)引用隊(duì)列作為參數(shù)。當(dāng)垃圾回收器準(zhǔn)備回收一個(gè)對(duì)象時(shí),如果發(fā)現(xiàn)它還有虛引用,就會(huì)在回收對(duì)象后,將這個(gè)虛引用加入引用隊(duì)列,以通知應(yīng)用程序?qū)ο蟮幕厥涨闆r
由于虛引用可以跟蹤對(duì)象的回收時(shí)間,因此,也可以將一些資源釋放操作放置在虛引用中執(zhí)行和記錄‘’
在JDK 1. 2版之后提供了PhantomReference類來實(shí)現(xiàn)虛引用。
輸出:
null
調(diào)用當(dāng)前類的finalize()方法
obj 可用
第 2 次 gc
追蹤垃圾回收過程:PhantomReferenceTest實(shí)例被GC了
obj 是 null
總結(jié)
以上是生活随笔為你收集整理的JVM_06 垃圾回收相关概念[ 二 ]的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面试题,你什么时候可以入职?回答不好,容
- 下一篇: 一句话证明你是产品经理