<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                企業??AI智能體構建引擎,智能編排和調試,一鍵部署,支持知識庫和私有化部署方案 廣告
                在前面幾個課時中,我們不止一次提到了堆(heap),堆是一個巨大的對象池。在這個對象池中管理著數量巨大的對象實例。 而池中對象的引用層次,有的是很深的。一個被頻繁調用的接口,每秒生成對象的速度,也是非常可觀的。對象之間的關系,形成了一張巨大的網。雖然 Java 一直在營造一種無限內存的氛圍,但對象不能只增不減,所以需要垃圾回收。 那 JVM 是如何判斷哪些對象應該被回收?哪些應該被保持呢? 在古代,刑罰中有誅九族一說。指的是有些人犯大事時,皇上殺一人不足以平復內心的憤怒時,會對親朋好友產生連帶責任。誅九族時首先需要追溯到一個共同的祖先,再往下細數連坐。堆上的垃圾回收也有同樣的思路。我們接下來就具體分析 JVM 中是如何進行垃圾回收的。 JVM 的 GC 動作,是不受程序控制的,它會在滿足條件的時候,自動觸發。 在發生 GC 的時候,一個對象,JVM 總能夠找到引用它的祖先。找到最后,如果發現這個祖先已經名存實亡了,它們都會被清理掉。而能夠躲過垃圾回收的那些祖先,比較特殊,它們的名字就叫作 GC Roots。 從 GC Roots 向下追溯、搜索,會產生一個叫作 Reference Chain 的鏈條。當一個對象不能和任何一個 GC Root 產生關系時,就會被無情的誅殺掉。 如圖所示,Obj5、Obj6、Obj7,由于不能和 GC Root 產生關聯,發生 GC 時,就會被摧毀。 ![](https://img.kancloud.cn/17/74/1774889dc07cfda6a0075812265253ad_907x437.png) 垃圾回收就是圍繞著 GC Roots 去做的。同時,它也是很多內存泄露的根源,因為其他引用根本沒有這樣的權利。 那么,什么樣的對象,才會是 GC Root 呢?這不在于它是什么樣的對象,而在于它所處的位置。 ### GC Roots 有哪些 GC Roots 是一組必須活躍的引用。用通俗的話來說,就是程序接下來通過直接引用或者間接引用,能夠訪問到的潛在被使用的對象。 GC Roots 包括: * Java 線程中,當前所有正在被調用的方法的引用類型參數、局部變量、臨時值等。也就是與我們棧幀相關的各種引用。 * 所有當前被加載的 Java 類。 * Java 類的引用類型靜態變量。 * 運行時常量池里的引用類型常量(String 或 Class 類型)。 * JVM 內部數據結構的一些引用,比如 sun.jvm.hotspot.memory.Universe 類。 * 用于同步的監控對象,比如調用了對象的 wait() 方法。 * JNI handles,包括 global handles 和 local handles。 這些 GC Roots 大體可以分為三大類,下面這種說法更加好記一些: * 活動線程相關的各種引用。 * 類的靜態變量的引用。 * JNI 引用。 ![](https://img.kancloud.cn/fa/60/fa608d2f1623bfd72f7bac04b65bceac_813x373.png) 有兩個注意點: * 我們這里說的是活躍的引用,而不是對象,對象是不能作為 GC Roots 的。 * GC 過程是找出所有活對象,并把其余空間認定為“無用”;而不是找出所有死掉的對象,并回收它們占用的空間。所以,哪怕 JVM 的堆非常的大,基于 tracing 的 GC 方式,回收速度也會非常快。 #### 引用級別 接下來的一道面試題就有意思多了:能夠找到 Reference Chain 的對象,就一定會存活么? 我在面試的時候,經常會問這些問題,比如“弱引用有什么用處”?令我感到奇怪的是,即使是一些工作多年的 Java 工程師,對待這個問題也是一知半解,錯失了很多機會。 對象對于另外一個對象的引用,要看關系牢靠不牢靠,可能在鏈條的其中一環,就斷掉了。 ![](https://img.kancloud.cn/e4/0f/e40fb91c953f62b82c25e3fbefc619f1_560x463.png) 根據發生 GC 時,這條鏈條的表現,可以對這個引用關系進行更加細致的劃分。 它們的關系,可以分為強引用、軟引用、弱引用、虛引用等。 #### 強引用 Strong references 當內存空間不足,系統撐不住了,JVM 就會拋出 OutOfMemoryError 錯誤。即使程序會異常終止,這種對象也不會被回收。這種引用屬于最普通最強硬的一種存在,只有在和 GC Roots 斷絕關系時,才會被消滅掉。 這種引用,你每天的編碼都在用。例如:new 一個普通的對象。 ``` Object obj = new Object() ``` 這種方式可能是有問題的。假如你的系統被大量用戶(User)訪問,你需要記錄這個 User 訪問的時間。可惜的是,User 對象里并沒有這個字段,所以我們決定將這些信息額外開辟一個空間進行存放。 ``` static Map<User,Long> userVisitMap = new HashMap<>(); ... userVisitMap.put(user, time); ``` 當你用完了 User 對象,其實你是期望它被回收掉的。但是,由于它被 userVisitMap 引用,我們沒有其他手段 remove 掉它。這個時候,就發生了內存泄漏(memory leak)。 這種情況還通常發生在一個沒有設定上限的 Cache 系統,由于設置了不正確的引用方式,加上不正確的容量,很容易造成 OOM。 #### 軟引用 Soft references 軟引用用于維護一些可有可無的對象。在內存足夠的時候,軟引用對象不會被回收,只有在內存不足時,系統則會回收軟引用對象,如果回收了軟引用對象之后仍然沒有足夠的內存,才會拋出內存溢出異常。 可以看到,這種特性非常適合用在緩存技術上。比如網頁緩存、圖片緩存等。 Guava 的 CacheBuilder,就提供了軟引用和弱引用的設置方式。在這種場景中,軟引用比強引用安全的多。 軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收,Java 虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。 我們可以看一下它的代碼。軟引用需要顯式的聲明,使用泛型來實現。 ``` // 偽代碼 Object object = new Object(); SoftReference<Object> softRef = new SoftReference(object); ``` 這里有一個相關的 JVM 參數。它的意思是:每 MB 堆空閑空間中 SoftReference 的存活時間。這個值的默認時間是1秒(1000)。 ``` -XX:SoftRefLRUPolicyMSPerMB=<N> ``` 這里要特別說明的是,網絡上一些流傳的優化方法,即把這個值設置成 0,其實是錯誤的,這樣容易引發故障,感興趣的話你可以自行搜索一下。 這種比較偏門的優化手段,除非在你對其原理相當了解的情況下,才能設置一些比較特殊的值。比如 0 值,無限大等,這種值在 JVM 的設置中,最好不要發生。 #### 弱引用 Weak references 弱引用對象相比較軟引用,要更加無用一些,它擁有更短的生命周期。 當 JVM 進行垃圾回收時,無論內存是否充足,都會回收被弱引用關聯的對象。弱引用擁有更短的生命周期,在 Java 中,用 java.lang.ref.WeakReference 類來表示。 它的應用場景和軟引用類似,可以在一些對內存更加敏感的系統里采用。它的使用方式類似于這段的代碼: ``` // 偽代碼 Object object = new Object(); WeakReference<Object> softRef = new WeakReference(object); ``` #### 虛引用 Phantom References 這是一種形同虛設的引用,在現實場景中用的不是很多。虛引用必須和引用隊列(ReferenceQueue)聯合使用。如果一個對象僅持有虛引用,那么它就和沒有任何引用一樣,在任何時候都可能被垃圾回收。 實際上,虛引用的 get,總是返回 null。 ``` Object object = new Object(); ReferenceQueue queue = new ReferenceQueue(); // 虛引用,必須與一個引用隊列關聯 PhantomReference pr = new PhantomReference(object, queue); ``` 虛引用主要用來跟蹤對象被垃圾回收的活動。 當垃圾回收器準備回收一個對象時,如果發現它還有虛引用,就會在回收對象之前,把這個虛引用加入到與之關聯的引用隊列中。 程序如果發現某個虛引用已經被加入到引用隊列,那么就可以在所引用的對象的內存被回收之前采取必要的行動。 下面的方法,就是一個用于監控 GC 發生的例子。 ``` private static void startMonitoring(ReferenceQueue<MyObject> referenceQueue, Reference<MyObject> ref) { ExecutorService ex = Executors.newSingleThreadExecutor(); ex.execute(() -> { while (referenceQueue.poll()!=ref) { //don't hang forever if(finishFlag){ break; } } System.out.println("-- ref gc'ed --"); }); ex.shutdown(); } ``` 基于虛引用,有一個更加優雅的實現方式,那就是 Java 9 以后新加入的 Cleaner,用來替代 Object 類的 finalizer 方法。 ### 典型 OOM 場景 OOM 的全稱是 Out Of Memory,那我們的內存區域有哪些會發生 OOM 呢?我們可以從內存區域劃分圖上,看一下彩色部分。 ![](https://img.kancloud.cn/7d/fe/7dfe1cdf23462b1fe461bd46fe8484aa_853x310.png) 可以看到除了程序計數器,其他區域都有OOM溢出的可能。但是最常見的還是發生在堆上。 ![](https://img.kancloud.cn/c8/9e/c89e191215584f1c1d6da45c18bfb8cb_705x260.png) 所以 OOM 到底是什么引起的呢?有幾個原因: * 內存的容量太小了,需要擴容,或者需要調整堆的空間。 * 錯誤的引用方式,發生了內存泄漏。沒有及時的切斷與 GC Roots 的關系。比如線程池里的線程,在復用的情況下忘記清理 ThreadLocal 的內容。 * 接口沒有進行范圍校驗,外部傳參超出范圍。比如數據庫查詢時的每頁條數等。 * 對堆外內存無限制的使用。這種情況一旦發生更加嚴重,會造成操作系統內存耗盡。 典型的內存泄漏場景,原因在于對象沒有及時的釋放自己的引用。比如一個局部變量,被外部的靜態集合引用。 ![](https://img.kancloud.cn/ca/72/ca725fdbb8fe8699f6a067850d5d7216_1050x322.png) 你在平常寫代碼時,一定要注意這種情況,千萬不要為了方便把對象到處引用。即使引用了,也要在合適時機進行手動清理。關于這部分的問題根源排查,我們將在實踐課程中詳細介紹。 ### 小結 你可以注意到 GC Roots 的專業叫法,就是可達性分析法。另外,還有一種叫作引用計數法的方式,在判斷對象的存活問題上,經常被提及。 因為有循環依賴的硬傷,現在主流的 JVM,沒有一個是采用引用計數法來實現 GC 的,所以我們大體了解一下就可以。引用計數法是在對象頭里維護一個 counter 計數器,被引用一次數量 +1,引用失效記數 -1。計數器為 0 時,就被認為無效。你現在可以忘掉引用計數的方式了。 本課時,我們詳細介紹了 GC Roots 都包含哪些內容。HostSpot 采用 tracing 的方式進行 GC,內存回收的速度與處于 living 狀態的對象數量有關。 這部分涉及的內容較多,如果面試被問到,你可以采用白話版的方式進行介紹,然后舉例深入。 接下來,我們了解到四種不同強度的引用類型,尤其是軟引用和虛引用,在平常工作中使用還是比較多的。這里面最不常用的就是虛引用,但是它引申出來的 Cleaner 類,是用來替代 finalizer 方法的,這是一個比較重要的知識點。 本課時最后討論了幾種典型的 OOM 場景,你可能現在對其概念比較模糊。接下來的課時,我們將詳細介紹幾個常見的垃圾回收算法,然后對這些 OOM 的場景逐個擊破。 ### 課后問答 * 1、為什么虛引用在回收之前必須加入到與之關聯的隊列中,而其他引用可以不需要? 答案:其他的如果有需要也可以加入;但虛引用的唯一用途就是這個,所以要加。 * 2、對于軟引用存活時間為1秒不理解,軟引用不是在內存不足時才清除嗎? 答案:SoftReference里面,有一個timestamp,記錄了上次GC的時間。對某個軟引用來說,GC時空間足不足,通過以下規則計算: ``` now - timestamp <= 空閑空間 * SoftRefLRUPolicyMSPerMB ``` 屬于內部實現細節,不建議改動。 * 3、"軟引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收,Java 虛擬機就會把這個軟引用加入到與之關聯的引用隊列中"請問這個隊列有什么作用呢?加入之后干什么呢? 答案:當一個對象被gc掉的時候通知用戶線程,進行額外的處理時,就需要使用引用隊列了。比如反向操作,其他數據清理(非GC) * 4、能夠找到 Reference Chain 的對象,就一定會存活么?應該怎么回答呢?圖中“如果A和B引用關系不牢靠就會斷開”,怎么判斷引用關系是否牢靠?這個算法執行期間 【所有的引用關系必須都是 牢靠的】才可以嗎? 答案:不一定,還要看reference類型。弱引用會在GC時會被回收,軟引用會在內存不足的時候被回收。但沒有Reference Chain的對象就一定會被回收。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看