天天看點

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

本文是對《【硬剛大資料之學習路線篇】從零到大資料專家的學習指南(全面更新版)》的JVM部分補充。

1.在預設情況下,通過System.gc()或者Runtime. getRuntime().gc()的調用,會顯式觸發Full GC,同時對老年代和新生代進行回收,嘗試釋放被丢棄對象占用的記憶體。

2.然而System.gc()調用附帶一個免責聲明,無法保證對垃圾收集器的調用(僅僅是提醒垃圾回收,會不會回收不一定)。

3.JVM實作者可以通過System.gc()調用來決定JVM的GC行為。而一般情況下,垃圾回收應該是自動進行的,無須手動觸發,否則就太過于麻煩了。在一些特殊情況下,如我們正在編寫一個性能基準,我們可以在運作之間調用System.gc()。

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

2.1 記憶體溢出

1.記憶體溢出相對于記憶體洩漏來說,盡管更容易被了解,但是同樣的,記憶體溢出也是引發程式崩潰的罪魁禍首之一。

2.由于GC一直在發展,所有一般情況下,除非應用程式占用的記憶體增長速度非常快,造成垃圾回收已經跟不上記憶體消耗的速度,否則不太容易出現OOM的情況。

3.大多數情況下,GC會進行各種年齡段的垃圾回收,實在不行了就放大招,來一次獨占式的Full GC操作,這時候會回收大量的記憶體,供應用程式繼續使用。

4.javadoc中對OutOfMemoryError的解釋是,沒有空閑記憶體,并且垃圾收集器也無法提供更多記憶體。

首先說沒有空閑記憶體的情況:說明Java虛拟機的堆記憶體不夠。原因有二:

Java虛拟機的堆記憶體設定不夠。

比如:可能存在記憶體洩漏問題也很有可能就是堆的大小不合理,比如我們要處理比較可觀的資料量,但是沒有顯式指定JVM堆大小或者指定數值偏小。我們可以通過參數-Xmg、Xmx來調整。

代碼中建立了大量大對象,并且長時間不能被垃圾收集器收集(存在被引用)

對于老版本的Oracle JDK、因為永久代的大小是有限的、并且對永久代垃圾回收(如常量池回收、解除安裝不再需要的類型)非常不積極,是以當我們不斷添加新類型的時候,永久代出現OutOfMemoryError也非常多見,尤其是在運作時存在大量動态類型生成的場合;類似intern字元串緩存占用太多空間,也會導緻OOM問題。對應的異常資訊,會标記出來和永久代相關:"java.lang. OutOfMemoryError: Permgen space"。

随着中繼資料區的引入,方法區記憶體已經不再那麼窘迫,是以相應的OOM有所改觀,出現OOM,異常資訊則變成了:"java.lang.OutOfMemoryError: Metaspace"。直接記憶體不足,也會導緻OOM

5.這裡面隐含着一層意思是,在抛出OutOfMemoryError之前,通常垃圾收集器會被觸發,盡其所能去清理出空間。

例如:在引用機制分析中,涉及到JVM會去嘗試回收軟引用指向的對象等。

在java.nio.BIts.reservememory()方法中,我們能清楚的看到, System.gc()會被調用,以清理空間。

6.當然,也不是在任何情況下垃圾收集器都會被觸發的

比如,我們去配置設定一個超大對象,類似一個超大數組超過堆的最大值,JVM可以判斷出垃圾收集并不能解決這個問題,是以直接抛出OutofmemoryError。

2.2 記憶體洩漏

1.也稱作"存儲滲漏"。嚴格來說,隻有對象不會再被程式用到了,但是GC又不能回收他們的情況,才叫記憶體洩漏。

2.但實際情況很多時候一些不太好的實踐(或疏忽)會導緻對象的生命周期變得很長甚至導緻OOM,也可以叫做寬泛意義上的"記憶體洩漏"

3.盡管記憶體洩漏并不會立刻引起程式崩潰,但是一旦發生記憶體洩漏,程式中的可用記憶體就會被逐漸蠶食,直至耗盡所有記憶體,最終出現OutofmemoryError異常,導緻程式崩潰。

4.注意,這裡的存儲空間并不是指實體記憶體,而是指虛拟記憶體大小,這個虛拟記憶體大小取決于磁盤交換區設定的大小。

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

5.舉例:

單例模式

單例的生命周期和應用程式是一樣長的,是以單例程式中,如果持有對外部對象的引用的話,那麼這個外部對象是不能被回收的,則會導緻記憶體洩漏的産生。

一些提供close的資源未關閉導緻記憶體洩漏

資料庫連接配接(dataSourse. getConnection(),網絡連接配接(socket)和io連接配接必須手close,否則是不能被回收的。

1.Stop-the-World,簡稱STW,指的是GC事件發生過程中,會産生應用程式的停頓。停頓産生時整個應用程式線程都會被暫停,沒有任何響應,有點像卡死的感覺,這個停頓稱為STW

可達性分析算法中枚舉根節點(GC Roots)會導緻所有Java執行線程停頓。

分析工作必須在一個能確定一緻性的快照中進行

如果出現分析過程中對象引用關系還在不斷變化,則分析結果的準确性無法保證

一緻性指整個分析期間整個執行系統看起來像被當機在某個時間點上

2.被STW中斷的應用程式線程會在完成GC之後恢複,頻繁中斷會讓使用者感覺像是網速不快造成電影卡帶一樣,是以我們需要減少STW的發生。

3.STW事件和采用哪款GC無關,所有的GC都有這個事件

4.哪怕是G1也不能完全避免Stop-the-world情況發生,隻能說垃圾回收器越來越優秀,回收效率越來越高,盡可能地縮短了暫停時間。

5.STW是JVM在背景自動發起和自動完成的。在使用者不可見的情況下,把使用者正常的工作線程全部停掉。

6.開發中不要用System.gc();會導緻Stop-the-world的發生

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

4.1 并發(Concurrent)

1.在作業系統中,是指一個時間段中有幾個程式都處于已啟動運作到運作完畢之間,且這幾個程式都是在同章個處理器上運作。

2.并發不是真正意義上的"同時進行",隻是CPU把一個時間段劃分成幾個時間片段(時間區間),然後在這幾個時間區間之間來回切換,由于CPU處理的速度非常快,隻要時間間隔處理得當,即可讓使用者感覺是多個應用程式同時在進行。

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

4.2 并行

1.當系統有一個以上CPU時,當一個CPU執行一個程序時,另一個CPU可以執行另一個程序兩個程序互不搶占CPU資源,可以同時進行,我們稱之為并行(Parallel)。

2.其實決定并行的因素不是CPU的數量,而是CPU的核心數量,比如一個CPU多個核也可以并行

3.适合科學計算,背景處理等弱互動場景

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

4.3 對比

1.并發,指的是多個事情,在同一時間段内同時發生了。并行,指的是多個事情,在同一時間點上同時發生了。

2.并發的多個任務之間是互相搶占資源的。并行的多個任務之間是不互相搶占資源的。

3.隻有在多CPU或者一個CPU多核的情況中,オ會發生并行。否則,看似同時發生的事情,其實都是并發執行的。

4.4 垃圾回收的并發與并行

1.并發和并行,在談論垃圾收集器的上下文語境中,它們可以解釋如下:

并行(Parallel):指多條垃圾收集線程并行工作,但此時使用者線程仍處于等待狀态。

    | 如 Pardew、 Parallel Scavenge、 Parallel Old

串行(Serial)

相較于并行的概念,單線程執行。

如果記憶體不夠,則程式暫停,啟動JVM垃圾回收器進行垃圾回收。回收完,再啟動程式的線程。

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

并發( Concurrent):指使用者線程與垃圾收集線程同時執行(但不一定是并行的,可能會交替執行),垃圾回收線程在執行時不會停頓使用者程式的運作。

|  使用者程式在繼續運作,而垃圾收集程式線程運作于另ー個CPU上;

|  如:CMS、G1

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

5.1 安全點

1.程式執行時并非在所有地方都能停頓下來開始GC,隻有在特定的位置才能停頓下來開始GC,這些位置稱為"安全點( Safepoint)"。

2.Safe Point的選擇很重要,如果太少可能導緻GC等待的時間太長,如果太頻繁可能導緻運作時的性能問題。大部分指令的執行時間都非常短暫通常會根據"是否具有讓程式長時間執行的特征"為标準。比如:選擇些執行時間較長的指令作為 Safe Point,如方法調用、循環跳轉和異常跳轉等。

3.如何在GC發生時,檢查所有線程都跑到最近的安全點停頓下來呢?

搶先式中斷:(目前沒有虛拟機采用了)

首先中斷所有線程。如果還有線程不在安全點,就恢複線程,讓線程跑到安全點。

主動式中斷:

設定一個中斷标志,各個線程運作到Safe Point的時候主動輪詢這個标志,如果中斷标志為真,則将自己進行中斷挂起。

5.2 安全區域

1.Safepoint機制保證了程式執行時,在不太長的時間内就會遇到可進入GC的Safepoint。但是,程式"不執行"的時候呢?例如線程處于Sleep狀态或Blocked狀态,這時候線程無法響應JVM的中斷請求,"走"到安全點去中斷挂起,JVM也不太可能等待線程被喚醒。對于這種情況,就需要安全區域(Safe Region)來解決。

2.安全區域是指在一段代碼片段中,對象的引用關系不會發生變化,在這個區域中的任何位置開始GC都是安全的。我們也可以把Safe Region看做是被擴充了的 Safepoint

3.實際執行時

當線程運作到Safe Region的代碼時,首先辨別已經進入了Safe Region,如果這段時間内發生GC,JVM會忽略辨別為Safe Region狀态的線程

當線程即将離開Safe Region時,會檢查JVM是否已經完成GC,如果完成了,則繼續運作,否則線程必須等待直到收到可以安全離開Safe Region的信号為止

1.概述

我們希望能描述這樣一類對象:當記憶體空間還足夠時,則能保留在記憶體中;如果記憶體空間在進行垃圾收集後還是很緊張,則可以抛棄這些對象。

在JDK1.2版之後,Java對引用的概念進行了擴充,将引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)和虛引用(Phantom Reference)4種,這4種引用強度依次逐漸減弱

除強引用外,其他3種引用均可以在java.lang.ref包中找到它們的身影。如下圖,顯示了這3種引用類型對應的類,開發人員可以在應用程式中直接使用它們

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

Reference子類中隻有終結器引用是包内可見的,其他3種引用類型均為public,可以在應用程式中直接使用

2.【既偏門又非常高頻的面試題】強引用、軟引用、弱引用、虛引用有什麼差別?具體使用場景是什麼?

強引用(Strong Reference):最傳統的"引用"的定義,是指在程式代碼之中普遍存在的引用指派,即類似"Object obj= new Object()"這種引用關系。無論任何情況下,隻要強引用關系還存在,垃圾收集器就永遠不會回收掉被引用的對象。

軟引用(Soft Reference):在系統将要發生記憶體溢出之前,将會把這些對象列入回收範圍之中進行第二次回收。如果這次回收後還沒有足夠的記憶體,才會抛出記憶體溢出異常

弱引用(Weak Reference):被弱引用關聯的對象隻能生存到下一次垃圾收集之前。當垃圾收集器工作時,無論記憶體空間是否足夠,都會回收掉被弱引用關聯的對象。

虛引用(Phantom Reference):一個對象是否有虛引用的存在,完全不會對其生存時間構成影響,也無法通過虛引用來獲得一個對象的執行個體。為一個對象設定虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知

6.1 強引用-不回收

1.在Java程式中,最常見的引用類型是強引用(普通系統99%以上都是強引用),也就是我們最常見的普通對象引用,也是預設的引用類型。

2.當在Java語言中使用new操作符建立一個新的對象,并将其指派給一個變量的時候,這個變量就成為指向該對象的一個強引用。

3.強引用的對象是可觸及的,垃圾收集器就永遠不會回收掉被引用的對象。

4.對于一個普通的對象,如果沒有其他的引用關系,隻要超過了引用的作用域或者顯式地将相應(強)引用指派為null,就是可以當做垃圾被收集了,當然具體回收時機還是要看垃圾收集策珞。

5.相對的,軟引用、弱引用和虛引用的對象是軟可觸及、弱可觸及和虛可觸及的,在一定條件下,都是可以被回收的。是以,強引用是造成Java記憶體洩漏的主要原因之ー。

6.強引用例子:

局部變量str指向StringBuffer執行個體所在堆空間,通過str可以操作該執行個體,那麼str就是StringBuffer執行個體的強引用

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

對應記憶體結構:

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

對str1進行指派str,對應記憶體結構:

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

6.2 軟引用-記憶體不足即回收

1.軟引用是用來描述一些還有用,但非必需的對象。隻被軟引用關聯着的對象在系統将要發生記憶體溢出異常前,會把這些對象列進回收範圍之中進行第二次回收(一次回收,不觸及的對象),如果這次回收還沒有足夠的記憶體,オ會抛出記憶體溢出異常。

2.軟引用通常用來實作記憶體敏感的緩存。比如:高速緩存就有用到軟引用。如果還有空閑記憶體,就可以暫時保留緩存,當記憶體不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡記憶體。

3.垃圾回收器在某個時刻決定回收軟可達的對象的時候,會清理軟引用,并可選地把引用存放到一個引用隊列(Reference Queue)

4.類似弱引用,隻不過Java虛拟機會盡量讓軟引用的存活時間長一些,迫不得已才清理。

5.在JDK1.2版之後提供了java.lang.ref. Softreference類來實作軟引用

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

6.代碼測試

當記憶體足夠,不會回收軟引用的可達對象;當記憶體不夠,會回收軟引用的可達對象

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

6.3 弱引用-發現即回收

1.弱引用也是用來描述那些非必需對象,隻被弱引用關聯的對象隻能生存到下一次垃圾收集發生為止。在系統GC時,隻要發現弱引用,不管系統堆空間使用是否充足,都會回收掉隻被弱引用關聯的對象。

2.但是,由于垃圾回收器的線程通常優先級很低,是以,并不一定能很快地發現持有弱引用的對象。在這種情況下,弱引用對象可以存在較長的時間。

3.弱引用和軟引用一樣,在構造弱引用時,也可以指定一個引用隊列,當弱引用對象被回收時,就會加入指定的引用隊列,通過這個隊列可以跟蹤對象的回收情況

4.軟引用、弱引用都非常适合來儲存那些可有可無的緩存資料。如果這麼做,當系統記憶體不足時,這些緩存資料會被回收,不會導緻記憶體溢出。而當記憶體資源充足時,這些緩存資料又可以存在相當長的時間,進而起到加速系統的作用。

5.弱引用對象與軟引用對象的最大不同就在于,當GC在進行回收時,需要通過算法檢査是否回收軟引用對象,而對于弱引用對象,GC總是進行回收。弱引用對象更容易、更快被GC回收

6.在JDK1.2版之後提供了java.lang.ref. WeakReference類來實作軟引用

7.代碼測試

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

|  你開發中使用過WeakHashMap嗎?

 WeakHashMap的Entry方法繼承了WeakReference

【硬剛JVM】JVM 專題十九:垃圾回收(三)垃圾回收相關概念

6.4 虛引用-對象回收跟蹤

1.也稱為"幽靈引用"或者"幻影引用",是所有引用類型中最弱的

2.一個對象是否有虛引用的存在,完全不會決定對象的生命周期。如果一個對象僅持有虛引用,那麼它和沒有引用幾乎是一樣的,随時都可能被垃圾回收器回收

3.它不能單獨使用,也無法通過虛引用來擷取被引用的對象。當試圖通過虛引用的get()方法取得對象時,總是null。

4.為一個對象設定虛引用關聯的唯一目的在于跟蹤垃圾回收過程。比如:能在這個對象被收集器回收時收到一個系統通知。

5.虛引用必須和引用隊列一起使用。虛引用在建立時必須提供一個引用隊列作為參數。當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象後,将這個虛引用加入引用隊列,以通知應用程式對象的回收情況。

6.由于虛引用可以跟蹤對象的回收時間,是以,也可以将一些資源釋放操作放置在虛引用中執行和記錄。

7.在JDK1.2版之後提供了Phantomre ference類來實作虛引用

8.虛引用的代碼測試

6.5 終結器引用

1.它用以實作對象的finalize()方法,也可以稱為終結器引用

2.無需手動編碼,其内部配合引用隊列使用

3.在GC時,終結器引用入隊。由Finalizer線程通過終結器引用找到被引用對象并調用它的finalize()方法,第二次GC時才能回收被引用對象