日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

JVM_06 垃圾回收相关概念[ 二 ]

發布時間:2025/3/15 编程问答 18 豆豆
生活随笔 收集整理的這篇文章主要介紹了 JVM_06 垃圾回收相关概念[ 二 ] 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

一、 System.gc()的理解

  • 在默認情況下,通過System.gc( )或者Runtime . getRuntime( ).gc( )的調用,會顯式觸發Full GC,同時對老年代和新生代進行回收,嘗試釋放被丟棄對象占用的內存。

  • 然而System.gc()調用附帶一個免責聲明,無法保證對垃圾收集器的調用(無法保證馬上觸發GC)。[不保證一定會發生垃圾收集,只是給jvm發出提示]

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

  • 以下代碼,如果注掉System.runFinalization( ); 那么控制臺不保證一定打印,證明了System.gc( )無法保證GC一定執行

  • public class SystemGCTest {public static void main(String[] args) {new SystemGCTest();System.gc();//提醒jvm的垃圾回收器執行gc,但是不確定是否馬上執行gc//與Runtime.getRuntime().gc();的作用一樣。System.runFinalization();//強制調用使用引用的對象的finalize()方法}@Overrideprotected void finalize() throws Throwable {super.finalize();System.out.println("SystemGCTest 重寫了finalize()");} }
  • 手動gc理解不可達對象的回收行為
  • public class LocalVarGC {public void localvarGC1() {byte[] buffer = new byte[10 * 1024 * 1024];//10MBSystem.gc();//輸出: 不會被回收, FullGC時被放入老年代//[GC (System.gc()) [PSYoungGen: 14174K->10736K(76288K)] 14174K->10788K(251392K), 0.0089741 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]//[Full GC (System.gc()) [PSYoungGen: 10736K->0K(76288K)] [ParOldGen: 52K->10649K(175104K)] 10788K->10649K(251392K), [Metaspace: 3253K->3253K(1056768K)], 0.0074098 secs] [Times: user=0.01 sys=0.02, real=0.01 secs]}public void localvarGC2() {byte[] buffer = new byte[10 * 1024 * 1024];buffer = null;System.gc();//輸出: 正常被回收//[GC (System.gc()) [PSYoungGen: 14174K->544K(76288K)] 14174K->552K(251392K), 0.0011742 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]//[Full GC (System.gc()) [PSYoungGen: 544K->0K(76288K)] [ParOldGen: 8K->410K(175104K)] 552K->410K(251392K), [Metaspace: 3277K->3277K(1056768K)], 0.0054702 secs] [Times: user=0.01 sys=0.00, real=0.01 secs]}public void localvarGC3() {{byte[] buffer = new byte[10 * 1024 * 1024];}System.gc();//輸出: 不會被回收, FullGC時被放入老年代//[GC (System.gc()) [PSYoungGen: 14174K->10736K(76288K)] 14174K->10784K(251392K), 0.0076032 secs] [Times: user=0.02 sys=0.00, real=0.01 secs]//[Full GC (System.gc()) [PSYoungGen: 10736K->0K(76288K)] [ParOldGen: 48K->10649K(175104K)] 10784K->10649K(251392K), [Metaspace: 3252K->3252K(1056768K)], 0.0096328 secs] [Times: user=0.01 sys=0.01, real=0.01 secs]}public void localvarGC4() {{byte[] buffer = new byte[10 * 1024 * 1024];}int value = 10;System.gc();//輸出: 正常被回收//[GC (System.gc()) [PSYoungGen: 14174K->496K(76288K)] 14174K->504K(251392K), 0.0016517 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]//[Full GC (System.gc()) [PSYoungGen: 496K->0K(76288K)] [ParOldGen: 8K->410K(175104K)] 504K->410K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0055183 secs] [Times: user=0.00 sys=0.00, real=0.01 secs]}public void localvarGC5() {localvarGC1();System.gc();//輸出: 正常被回收//[GC (System.gc()) [PSYoungGen: 14174K->10720K(76288K)] 14174K->10744K(251392K), 0.0121568 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]//[Full GC (System.gc()) [PSYoungGen: 10720K->0K(76288K)] [ParOldGen: 24K->10650K(175104K)] 10744K->10650K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0101068 secs] [Times: user=0.01 sys=0.02, real=0.01 secs]//[GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] 10650K->10650K(251392K), 0.0005717 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]//[Full GC (System.gc()) [PSYoungGen: 0K->0K(76288K)] [ParOldGen: 10650K->410K(175104K)] 10650K->410K(251392K), [Metaspace: 3279K->3279K(1056768K)], 0.0045963 secs] [Times: user=0.01 sys=0.00, real=0.00 secs]}public static void main(String[] args) {LocalVarGC local = new LocalVarGC();local.localvarGC5();} }

    二、內存溢出(out of Memory)

  • javadoc中對OutOfMemoryError的解釋是,沒有空閑內存,并且垃圾收集器也無法提供更多內存

  • 說明Java虛擬機的堆內存不夠。原因有二:

  • Java虛擬機的堆內存設置不夠(比如:可能存在內存泄漏問題;也很有可能就是堆的大小不合理,比如我們要處理比較可觀的數據量,但是沒有顯式指定JVM堆大小或者指定數值偏小。我們可以通過參數一Xms、一Xmx來調整)
  • 代碼中創建了大量大對象,并且長時間不能被垃圾收集器收集(存在被引用)
  • 這里面隱含著一層意思是,在拋出0utOfMemoryError之前,通常垃圾收集器會被觸發,盡其所能去清理出空間。
  • 例如:在引用機制分析中,涉及到JVM會去嘗試回收軟引用指向的對象等。
  • 在java.nio.BIts.reserveMemory()方法中,我們能清楚的看到,System.gc()會被調用,以清理空間。
  • 當然,也不是在任何情況下垃圾收集器都會被觸發的
  • (比如,我們去分配一一個超大對象,類似一個超大數組超過堆的最大值,JVM可以判斷出垃圾收集并不能解決這個問題,所以直接拋出OutOfMemoryError)

    三、 內存泄漏(Memory Leak)

  • 也稱作“存儲滲漏”。嚴格來說,只有對象不會再被程序用到了,但是GC又不能回收他們的情況,才叫內存泄漏。

  • 但實際情況很多時候一些不太好的實踐(或疏忽)會導致對象的生命周期變得很長甚至導致OOM,也可以叫做寬泛意義上的“內存泄漏

  • 盡管內存泄漏并不會立刻引起程序崩潰,但是一旦發生內存泄漏,程序中的可用內存就會被逐步蠶食,直至耗盡所有內存,最終出現0utOfMemory異常,導致程序崩潰。

  • 舉例

  • 單例模式(單例的生命周期和應用程序是一樣長的,所以單例程序中,如果持有對外部對象的引用的話,那么這個外部對象是不能被回收的,則會導致內存泄漏的產生。)
  • 一些提供close的資源未關閉導致內存泄漏 數據庫連接( dataSourse.
    getConnection()),網絡連接(socket)和io連接必須手動close,否則是不能被回收的。
  • 四、Stop The World

  • Stop一the一World,簡稱STW,指的是Gc事件發生過程中,會產生應用程序的停頓。停頓產生時整個應用程序線程都會被暫停,沒有任何響應,有點像卡死的感覺,這個停頓稱為STW

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

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

  • STW是JVM在后臺自動發起和自動完成的。在用戶不可見的情況下,把用戶正常的工作線程全部停掉

  • 開發中不要用System.gc();會導致Stop一the一world的發生。

  • 6.什么情況下會導致stop the world 記住

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

    • 進行gc的時候會發生STW現象(調用finalize()方法的時候會暫停用戶線程

    • System.gc( ) | 調用finalize( )方法

    五、多線程中的并行與并發

  • 并發(Concurrent)
    • 在操作系統中,是指一個時間段中有幾個程序都處于己啟動運行到運行完畢之間,且這幾個程序都是在同一個處理器_上運行
    • 并發不是真正意義上的“同時進行”,只是CPU把一個時間段劃分成幾個時間片段(時間區間),然后在這幾個時間區間之間來回切換,由于CPU處理的速度非常快,只要時間間隔處理得當,即可讓用戶感覺是多個應用程序同時在進行
    • 圖解:
  • 并行(Parallel)
    • 當系統有一個以上CPU時,當一個CPU執行一個進程時,另一個CPU可以執行另一個進程,兩個進程互不搶占CPU資源,可以同時進行,我們稱之為并行(Parallel)
    • 其實決定并行的因素不是CPU的數量,而是CPU的核心數量,比如一個CPU多個核也可以 并行
    • 圖解:

    六、 垃圾回收的并行、串行、并發

  • 并行(Parallel) :指多條垃圾收集線程并行工作,但此時用戶線程仍處于等待狀態。如ParNew、 Parallel Scavenge、 Parallel 0ld;

  • 串行(Serial)

    • 相較于并行的概念,單線程執行。
    • 如果內存不夠,則程序暫停,啟動JVM垃圾回收器進行垃圾回收。回收完,再啟動程序的線程。
    • 圖解:
  • 并發
    • 指用戶線程與垃圾收集線程同時執行(但不一定是并行的,可能會交替執行),垃圾回收線程在執行時不會停頓用戶程序的運行
    • 在同一個時間段,用戶線程和垃圾回收線程同時執行
    • 圖解

    七、 安全點(Safepoint)

  • 程序執行時并非在所有地方都能停頓下來開始GC,只有在特定的位置才能停頓下來開始GC,這些位置稱為 “安全點(Safepoint)”

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

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

    • 搶先式中斷: (目前沒有虛擬機采用了) 首先中斷所有線程。如果還有線程不在安全點,就恢復線程,讓線程跑到安全點。
    • 主動式中斷:設置一個中斷標志,各個線程運行到Safe Point的時候主動輪詢這個標志,如果中斷標志為真,則將自己進行中斷掛起。

    八、安全區域(Safe Region)

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

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

  • 實際執行時:

    • 當線程運行到Safe Region的代碼時,首先標識已經進入了Safe Region,如果這段時間內發生GC,JVM會忽略標識為Safe Region狀態的線程;
    • 當線程即將離開Safe Region時,會檢查JVM是否已經完成GC,如果完成了,則繼續運行,否則線程必須等待直到收到可以安全離開SafeRegion的信號為止;

    九、引用

  • 我們希望能描述這樣一類對象: 當內存空間還足夠時,則能保留在內存中;如果內存空間在進行垃圾收集后還是很緊張,則可以拋棄這些對象。 -【既偏門又非常高頻的面試題】強引用、軟引用、弱引用、虛引用有什么區別?具體使用.場景是什么?

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

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

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

  • 簡單介紹下強軟弱虛引用

    • 強引用(StrongReference):最傳統的“引用”的定義,是指在程序代碼之中普遍存在的引用賦值,即類似“0bject obj=new object( )”這種引用關系。無論任何情況下,只要強引用關系還存在,垃圾收集器就永遠不會回收掉被引用的對象。
    • 軟引用(SoftReference):在系統將要發生內存溢出之前,將會把這些對象列入回收范圍之中進行第二次回收。如果這次回收后還沒有足夠的內存,才會拋出內存溢出異常。內存不足即回收
    • 弱引用(WeakReference):被弱引用關聯的對象只能生存到下一次垃圾收集之前。當垃圾收集器工作時,無論內存空間是否足夠,都會回收掉被弱引用關聯的對象。發現即回收
    • 虛引用(PhantomReference):一個對象是否有虛引用的存在,完全不會對其生存時
      間構成影響,也無法通過虛引用來獲得一個對象的實例。為一個對象設置虛引用關聯的唯一目的就是能在這個對象被收集器回收時收到一個系統通知(回收跟蹤)

    ①. 強引用:不回收

    • 在Java程序中,最常見的引用類型是強引用(普通系統99%以上都是強引用),也就是我們最常見的普通對象引用,也是默認的引用類型。

    • 當在Java語言中使用new操作符創建一個新的對象, 并將其賦值給一個變量的時候,這個變量就成為指向該對象的一個強引用。

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

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

    • 相對的,軟引用、弱引用和虛引用的對象是軟可觸及、弱可觸及和虛可觸及的,在一定條件下,都是可以被回收的。所以,強引用是造成Java內存泄漏的主要原因之一。

    ②. 軟引用: 內存不足即回收

  • 軟引用是用來描述一
    些還有用,但非必需的對象。只被軟引用關聯著的對象,在系統將要發生內存溢出異常前,會把這些對象列進回收范圍之中進行第二次回收,如果這次回收還沒有足夠的內存,才會拋出內存溢出異常。
    注意:一次回收是回收強引用中沒有引用的對象

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

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

  • 軟引用: 當內存足夠: 不會回收軟用的可達對象 當內存不夠時: 會回收軟引用的可達對象

  • 在JDK 1. 2版之后提供了java.lang.ref.SoftReference類來實現軟引用。

  • Object obj = new object(); //聲明強引用 SoftReference<0bject> sf = new SoftReference<0bject>(obj); obj = null; //銷毀強引用
  • 測試代碼
  • /*** 軟引用的測試:內存不足即回收* -Xms10m -Xmx10m -XX:+PrintGCDetails*/ public class SoftReferenceTest {public static class User {public User(int id, String name) {this.id = id;this.name = name;}public int id;public String name;@Overridepublic String toString() {return "[id=" + id + ", name=" + name + "] ";}}public static void main(String[] args) {//創建對象,建立軟引用 // SoftReference<User> userSoftRef = new SoftReference<User>(new User(1, "songhk"));//上面的一行代碼,等價于如下的三行代碼User u1 = new User(1,"songhk");SoftReference<User> userSoftRef = new SoftReference<User>(u1);u1 = null;//取消強引用//從軟引用中重新獲得強引用對象System.out.println(userSoftRef.get());System.gc();System.out.println("After GC:"); // //垃圾回收之后獲得軟引用中的對象System.out.println(userSoftRef.get());//由于堆空間內存足夠,所有不會回收軟引用的可達對象。 //try {//讓系統認為內存資源緊張、不夠 // byte[] b = new byte[1024 * 1024 * 7];byte[] b = new byte[1024 * 7168 - 399 * 1024];//恰好能放下數組又放不下u1的內存分配大小 不會報OOM} catch (Throwable e) {e.printStackTrace();} finally {//再次從軟引用中獲取數據System.out.println(userSoftRef.get());//在報OOM之前,垃圾回收器會回收軟引用的可達對象。}} }

    ③. 弱引用: 發現即回收

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

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

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

  • 軟引用、弱引用都非常適合來保存那些可有可無的緩存數據。如果這么做,當系統內存不足時,這些緩存數據會被回收,不會導致內存溢出。而當內存資源充足時,這些緩存數據又可以存在相當長的時間,從而起到加速系統的作用

  • 在JDK1.2版之后提供了java.lang.ref.WeakReference類來實現弱引用

  • 面試題:你開發中使用過WeakHashMap嗎?
    • 通過查看WeakHashMap源碼,可以看到其內部類Entry使用的就是弱引用
    • line 702 -> private static class Entry<K,V> extends WeakReference implements Map.Entry<K,V> {…}
    public class WeakReferenceTest {public static class User {public User(int id, String name) {this.id = id;this.name = name;}public int id;public String name;@Overridepublic String toString() {return "[id=" + id + ", name=" + name + "] ";}}public static void main(String[] args) {//構造了弱引用WeakReference<User> userWeakRef = new WeakReference<User>(new User(1, "songhk"));//從弱引用中重新獲取對象System.out.println(userWeakRef.get());System.gc();// 不管當前內存空間足夠與否,都會回收它的內存System.out.println("After GC:");//重新嘗試從弱引用中獲取對象System.out.println(userWeakRef.get());} }

    ④. 虛引用: 對象回收跟蹤

  • 虛引用(Phantom Reference),也稱為“幽靈引用”或者“幻影引用”,是所有引用類型中最弱的一個。

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

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

  • 由于虛引用可以跟蹤對象的回收時間,因此,也可以將一些資源釋放操作放置在虛引用中執行和記錄‘’

  • 在JDK 1. 2版之后提供了PhantomReference類來實現虛引用。

  • object obj = new object(); ReferenceQueuephantomQueue = new ReferenceQueue( ) ; PhantomReference<object> pf = new PhantomReference<object>(obj, phantomQueue); obj = null;
  • 測試代碼
  • public class PhantomReferenceTest {public static PhantomReferenceTest obj;//當前類對象的聲明static ReferenceQueue<PhantomReferenceTest> phantomQueue = null;//引用隊列public static class CheckRefQueue extends Thread {@Overridepublic void run() {while (true) {if (phantomQueue != null) {PhantomReference<PhantomReferenceTest> objt = null;try {objt = (PhantomReference<PhantomReferenceTest>) phantomQueue.remove();} catch (InterruptedException e) {e.printStackTrace();}if (objt != null) {System.out.println("追蹤垃圾回收過程:PhantomReferenceTest實例被GC了");}}}}}@Overrideprotected void finalize() throws Throwable { //finalize()方法只能被調用一次!super.finalize();System.out.println("調用當前類的finalize()方法");obj = this;}public static void main(String[] args) {Thread t = new CheckRefQueue();t.setDaemon(true);//設置為守護線程:當程序中沒有非守護線程時,守護線程也就執行結束。t.start();phantomQueue = new ReferenceQueue<PhantomReferenceTest>();obj = new PhantomReferenceTest();//構造了 PhantomReferenceTest 對象的虛引用,并指定了引用隊列PhantomReference<PhantomReferenceTest> phantomRef = new PhantomReference<PhantomReferenceTest>(obj, phantomQueue);try {//不可獲取虛引用中的對象System.out.println(phantomRef.get());//將強引用去除obj = null;//第一次進行GC,由于對象可復活,GC無法回收該對象System.gc();Thread.sleep(1000);if (obj == null) {System.out.println("obj 是 null");} else {System.out.println("obj 可用");}System.out.println("第 2 次 gc");obj = null;System.gc(); //一旦將obj對象回收,就會將此虛引用存放到引用隊列中。Thread.sleep(1000);if (obj == null) {System.out.println("obj 是 null");} else {System.out.println("obj 可用");}} catch (InterruptedException e) {e.printStackTrace();}} }

    輸出:
    null
    調用當前類的finalize()方法
    obj 可用
    第 2 次 gc
    追蹤垃圾回收過程:PhantomReferenceTest實例被GC了
    obj 是 null

    public class PhantomReferenceDemo {public static void main(String[] args) {Object obj=new Object();ReferenceQueue<Object>queue=new ReferenceQueue<>();PhantomReference<Object>phantomReference=new PhantomReference<>(obj,queue);System.out.println(obj);System.out.println(queue.poll());//虛引用的get()方法得到的都是nullSystem.out.println(phantomReference.get());System.out.println("==========");obj=null;System.gc();System.out.println(obj);System.out.println(queue.poll());//虛引用的get()方法得到的都是nullSystem.out.println(phantomReference.get());/*輸出結果:java.lang.Object@424c0bc4nullnull==========nulljava.lang.ref.PhantomReference@3c679bdenull* */} }

    總結

    以上是生活随笔為你收集整理的JVM_06 垃圾回收相关概念[ 二 ]的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。