<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>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                我發現,目前不少外部資料對 G1 的介紹大多還停留在 JDK 7 或更早期的實現,很多結論已經存在較大偏差,甚至一些過去的 GC 選項已經不再推薦使用。所以,今天我會選取新版 JDK 中的默認 G1 GC 作為重點進行詳解,并且我會從調優實踐的角度,分析典型場景和調優思路。下面我們一起來更新下這方面的知識。 今天我要問你的問題是,談談你的 GC 調優思路? ## 典型回答 談到調優,這一定是針對特定場景、特定目的的事情, 對于 GC 調優來說,首先就需要清楚調優的目標是什么?從性能的角度看,通常關注三個方面,內存占用(footprint)、延時(latency)和吞吐量(throughput),大多數情況下調優會側重于其中一個或者兩個方面的目標,很少有情況可以兼顧三個不同的角度。當然,除了上面通常的三個方面,也可能需要考慮其他 GC 相關的場景,例如,OOM 也可能與不合理的 GC 相關參數有關;或者,應用啟動速度方面的需求,GC 也會是個考慮的方面。 基本的調優思路可以總結為: * 理解應用需求和問題,確定調優目標。假設,我們開發了一個應用服務,但發現偶爾會出現性能抖動,出現較長的服務停頓。評估用戶可接受的響應時間和業務量,將目標簡化為,希望 GC 暫停盡量控制在 200ms 以內,并且保證一定標準的吞吐量。 * 掌握 JVM 和 GC 的狀態,定位具體的問題,確定真的有 GC 調優的必要。具體有很多方法,比如,通過 jstat 等工具查看 GC 等相關狀態,可以開啟 GC 日志,或者是利用操作系統提供的診斷工具等。例如,通過追蹤 GC 日志,就可以查找是不是 GC 在特定時間發生了長時間的暫停,進而導致了應用響應不及時。 * 這里需要思考,選擇的 GC 類型是否符合我們的應用特征,如果是,具體問題表現在哪里,是 Minor GC 過長,還是 Mixed GC 等出現異常停頓情況;如果不是,考慮切換到什么類型,如 CMS 和 G1 都是更側重于低延遲的 GC 選項。 * 通過分析確定具體調整的參數或者軟硬件配置。 * 驗證是否達到調優目標,如果達到目標,即可以考慮結束調優;否則,重復完成分析、調整、驗證這個過程。 ## 考點分析 今天考察的 GC 調優問題是 JVM 調優的一個基礎方面,很多 JVM 調優需求,最終都會落實在 GC 調優上或者與其相關,我提供的是一個常見的思路。 真正快速定位和解決具體問題,還是需要對 JVM 和 GC 知識的掌握,以及實際調優經驗的總結,有的時候甚至是源自經驗積累的直覺判斷。面試官可能會繼續問項目中遇到的真實問題,如果你能清楚、簡要地介紹其上下文,然后將診斷思路和調優實踐過程表述出來,會是個很好的加分項。 專欄雖然無法提供具體的項目經驗,但是可以幫助你掌握常見的調優思路和手段,這不管是面試還是在實際工作中都是很有幫助的。另外,我會還會從下面不同角度進行補充: * [上一講](http://time.geekbang.org/column/article/10513)中我已經談到,涉及具體的 GC 類型,JVM 的實際表現要更加復雜。目前,G1 已經成為新版 JDK 的默認選擇,所以值得你去深入理解。 * 因為 G1 GC 一直處在快速發展之中,我會側重它的演進變化,尤其是行為和配置相關的變化。并且,同樣是因為 JVM 的快速發展,即使是收集 GC 日志等方面也發生了較大改進,這也是為什么我在上一講留給你的思考題是有關日志相關選項,看完講解相信你會很驚訝。 * 從 GC 調優實踐的角度,理解通用問題的調優思路和手段。 ## 知識擴展 首先,先來整體了解一下 G1 GC 的內部結構和主要機制。 從內存區域的角度,G1 同樣存在著年代的概念,但是與我前面介紹的內存結構很不一樣,其內部是類似棋盤狀的一個個 region 組成,請參考下面的示意圖。 ![](https://img.kancloud.cn/a6/62/a662fda0de8af087c37c40a86a9cf3f1_669x322.png) region 的大小是一致的,數值是在 1M 到 32M 字節之間的一個 2 的冪值數,JVM 會盡量劃分 2048 個左右、同等大小的 region,這點可以從源碼[heapRegionBounds.hpp](http://hg.openjdk.java.net/jdk/jdk/file/fa2f93f99dbc/src/hotspot/share/gc/g1/heapRegionBounds.hpp)中看到。當然這個數字既可以手動調整,G1 也會根據堆大小自動進行調整。 在 G1 實現中,年代是個邏輯概念,具體體現在,一部分 region 是作為 Eden,一部分作為 Survivor,除了意料之中的 Old region,G1 會將超過 region 50% 大小的對象(在應用中,通常是 byte 或 char 數組)歸類為 Humongous 對象,并放置在相應的 region 中。邏輯上,Humongous region 算是老年代的一部分,因為復制這樣的大對象是很昂貴的操作,并不適合新生代 GC 的復制算法。 你可以思考下 region 設計有什么副作用? 例如,region 大小和大對象很難保證一致,這會導致空間的浪費。不知道你有沒有注意到,我的示意圖中有的區域是 Humongous 顏色,但沒有用名稱標記,這是為了表示,特別大的對象是可能占用超過一個 region 的。并且,region 太小不合適,會令你在分配大對象時更難找到連續空間,這是一個長久存在的情況,請參考[OpenJDK 社區的討論](http://mail.openjdk.java.net/pipermail/hotspot-gc-use/2017-November/002726.html)。這本質也可以看作是 JVM 的 bug,盡管解決辦法也非常簡單,直接設置較大的 region 大小,參數如下: ~~~ -XX:G1HeapRegionSize=<N, 例如 16>M ~~~ 從 GC 算法的角度,G1 選擇的是復合算法,可以簡化理解為: * 在新生代,G1 采用的仍然是并行的復制算法,所以同樣會發生 Stop-The-World 的暫停。 * 在老年代,大部分情況下都是并發標記,而整理(Compact)則是和新生代 GC 時捎帶進行,并且不是整體性的整理,而是增量進行的。 我在[上一講](http://time.geekbang.org/column/article/10513)曾經介紹過,習慣上人們喜歡把新生代 GC(Young GC)叫作 Minor GC,老年代 GC 叫作 Major GC,區別于整體性的 Full GC。但是現代 GC 中,這種概念已經不再準確,對于 G1 來說: * Minor GC 仍然存在,雖然具體過程會有區別,會涉及 Remembered Set 等相關處理。 * 老年代回收,則是依靠 Mixed GC。并發標記結束后,JVM 就有足夠的信息進行垃圾收集,Mixed GC 不僅同時會清理 Eden、Survivor 區域,而且還會清理部分 Old 區域。可以通過設置下面的參數,指定觸發閾值,并且設定最多被包含在一次 Mixed GC 中的 region 比例。 ~~~ –XX:G1MixedGCLiveThresholdPercent –XX:G1OldCSetRegionThresholdPercent ~~~ 從 G1 內部運行的角度,下面的示意圖描述了 G1 正常運行時的狀態流轉變化,當然,在發生逃逸失敗等情況下,就會觸發 Full GC。 ![](https://img.kancloud.cn/47/dd/47dddbd91ad0e0adbd164632eb9facec_585x358.png) G1 相關概念非常多,有一個重點就是 Remembered Set,用于記錄和維護 region 之間對象的引用關系。為什么需要這么做呢?試想,新生代 GC 是復制算法,也就是說,類似對象從 Eden 或者 Survivor 到 to 區域的“移動”,其實是“復制”,本質上是一個新的對象。在這個過程中,需要必須保證老年代到新生代的跨區引用仍然有效。下面的示意圖說明了相關設計。 ![](https://img.kancloud.cn/eb/50/eb50bb2b270478bc6f525aa615d4a3d3_776x537.png) G1 的很多開銷都是源自 Remembered Set,例如,它通常約占用 Heap 大小的 20% 或更高,這可是非常可觀的比例。并且,我們進行對象復制的時候,因為需要掃描和更改 Card Table 的信息,這個速度影響了復制的速度,進而影響暫停時間。 描述 G1 內部的資料很多,我就不重復了,如果你想了解更多內部結構和算法等,我建議參考一些具體的[介紹](https://www.infoq.com/articles/G1-One-Garbage-Collector-To-Rule-Them-All),書籍方面我推薦 Charlie Hunt 等撰寫的《Java Performance Companion》。 接下來,我介紹下大家可能還不了解的 G1 行為變化,它們在一定程度上解決了專欄其他講中提到的部分困擾,如類型卸載不及時的問題。 * 上面提到了 Humongous 對象的分配和回收,這是很多內存問題的來源,Humongous region 作為老年代的一部分,通常認為它會在并發標記結束后才進行回收,但是在新版 G1 中,Humongous 對象回收采取了更加激進的策略。 我們知道 G1 記錄了老年代 region 間對象引用,Humongous 對象數量有限,所以能夠快速的知道是否有老年代對象引用它。如果沒有,能夠阻止它被回收的唯一可能,就是新生代是否有對象引用了它,但這個信息是可以在 Young GC 時就知道的,所以完全可以在 Young GC 中就進行 Humongous 對象的回收,不用像其他老年代對象那樣,等待并發標記結束。 * 我在[專欄第 5 講](http://time.geekbang.org/column/article/7349),提到了在 8u20 以后字符串排重的特性,在垃圾收集過程中,G1 會把新創建的字符串對象放入隊列中,然后在 Young GC 之后,并發地(不會 STW)將內部數據(char 數組,JDK 9 以后是 byte 數組)一致的字符串進行排重,也就是將其引用同一個數組。你可以使用下面參數激活: ~~~ -XX:+UseStringDeduplication ~~~ 注意,這種排重雖然可以節省不少內存空間,但這種并發操作會占用一些 CPU 資源,也會導致 Young GC 稍微變慢。 * 類型卸載是個長期困擾一些 Java 應用的問題,在[專欄第 25 講](http://time.geekbang.org/column/article/10192)中,我介紹了一個類只有當加載它的自定義類加載器被回收后,才能被卸載。元數據區替換了永久代之后有所改善,但還是可能出現問題。 G1 的類型卸載有什么改進嗎?很多資料中都談到,G1 只有在發生 Full GC 時才進行類型卸載,但這顯然不是我們想要的。你可以加上下面的參數查看類型卸載: ~~~ -XX:+TraceClassUnloading ~~~ 幸好現代的 G1 已經不是如此了,8u40 以后,G1 增加并默認開啟下面的選項: ~~~ -XX:+ClassUnloadingWithConcurrentMark ~~~ 也就是說,在并發標記階段結束后,JVM 即進行類型卸載。 * 我們知道老年代對象回收,基本要等待并發標記結束。這意味著,如果并發標記結束不及時,導致堆已滿,但老年代空間還沒完成回收,就會觸發 Full GC,所以觸發并發標記的時機很重要。早期的 G1 調優中,通常會設置下面參數,但是很難給出一個普適的數值,往往要根據實際運行結果調整 ~~~ -XX:InitiatingHeapOccupancyPercent ~~~ 在 JDK 9 之后的 G1 實現中,這種調整需求會少很多,因為 JVM 只會將該參數作為初始值,會在運行時進行采樣,獲取統計數據,然后據此動態調整并發標記啟動時機。對應的 JVM 參數如下,默認已經開啟: ~~~ -XX:+G1UseAdaptiveIHOP ~~~ * 在現有的資料中,大多指出 G1 的 Full GC 是最差勁的單線程串行 GC。其實,如果采用的是最新的 JDK,你會發現 Full GC 也是并行進行的了,在通用場景中的表現還優于 Parallel GC 的 Full GC 實現。 當然,還有很多其他的改變,比如更快的 Card Table 掃描等,這里不再展開介紹,因為它們并不帶來行為的變化,基本不影響調優選擇。 前面介紹了 G1 的內部機制,并且穿插了部分調優建議,下面從整體上給出一些調優的建議。 首先,**建議盡量升級到較新的 JDK 版本**,從上面介紹的改進就可以看到,很多人們常常討論的問題,其實升級 JDK 就可以解決了。 第二,掌握 GC 調優信息收集途徑。掌握盡量全面、詳細、準確的信息,是各種調優的基礎,不僅僅是 GC 調優。我們來看看打開 GC 日志,這似乎是很簡單的事情,可是你確定真的掌握了嗎? 除了常用的兩個選項, ~~~ -XX:+PrintGCDetails -XX:+PrintGCDateStamps ~~~ 還有一些非常有用的日志選項,很多特定問題的診斷都是要依賴這些選項: ~~~ -XX:+PrintAdaptiveSizePolicy // 打印 G1 Ergonomics 相關信息 ~~~ 我們知道 GC 內部一些行為是適應性的觸發的,利用 PrintAdaptiveSizePolicy,我們就可以知道為什么 JVM 做出了一些可能我們不希望發生的動作。例如,G1 調優的一個基本建議就是避免進行大量的 Humongous 對象分配,如果 Ergonomics 信息說明發生了這一點,那么就可以考慮要么增大堆的大小,要么直接將 region 大小提高。 如果是懷疑出現引用清理不及時的情況,則可以打開下面選項,掌握到底是哪里出現了堆積。 ~~~ -XX:+PrintReferenceGC ~~~ 另外,建議開啟選項下面的選項進行并行引用處理。 ~~~ -XX:+ParallelRefProcEnabled ~~~ 需要注意的一點是,JDK 9 中 JVM 和 GC 日志機構進行了重構,其實我前面提到的**PrintGCDetails 已經被標記為廢棄**,而**PrintGCDateStamps 已經被移除**,指定它會導致 JVM 無法啟動。可以使用下面的命令查詢新的配置參數。 ~~~ java -Xlog:help ~~~ 最后,來看一些通用實踐,理解了我前面介紹的內部結構和機制,很多結論就一目了然了,例如: * 如果發現 Young GC 非常耗時,這很可能就是因為新生代太大了,我們可以考慮減小新生代的最小比例。 ~~~ -XX:G1NewSizePercent ~~~ 降低其最大值同樣對降低 Young GC 延遲有幫助。 ~~~ -XX:G1MaxNewSizePercent ~~~ 如果我們直接為 G1 設置較小的延遲目標值,也會起到減小新生代的效果,雖然會影響吞吐量。 * 如果是 Mixed GC 延遲較長,我們應該怎么做呢? 還記得前面說的,部分 Old region 會被包含進 Mixed GC,減少一次處理的 region 個數,就是個直接的選擇之一。 我在上面已經介紹了 G1OldCSetRegionThresholdPercent 控制其最大值,還可以利用下面參數提高 Mixed GC 的個數,當前默認值是 8,Mixed GC 數量增多,意味著每次被包含的 region 減少。 ~~~ -XX:G1MixedGCCountTarget ~~~ 今天的內容算是拋磚引玉,更多內容你可以參考[G1 調優指南](https://docs.oracle.com/javase/9/gctuning/garbage-first-garbage-collector-tuning.htm#JSGCT-GUID-4914A8D4-DE41-4250-B68E-816B58D4E278)等,遠不是幾句話可以囊括的。需要注意的是,也要避免過度調優,G1 對大堆非常友好,其運行機制也需要浪費一定的空間,有時候稍微多給堆一些空間,比進行苛刻的調優更加實用。 今天我梳理了基本的 GC 調優思路,并對 G1 內部結構以及最新的行為變化進行了詳解。總的來說,G1 的調優相對簡單、直觀,因為可以直接設定暫停時間等目標,并且其內部引入了各種智能的自適應機制,希望這一切的努力,能夠讓你在日常應用開發時更加高效。 ## 一課一練 關于今天我們討論的題目你做到心中有數了嗎?今天的思考題是,定位 Full GC 發生的原因,有哪些方式?
                  <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>

                              哎呀哎呀视频在线观看