<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國際加速解決方案。 廣告
                上一講我介紹了 JVM 內存區域的劃分,總結了相關的一些概念,今天我將結合 JVM 參數、工具等方面,進一步分析 JVM 內存結構,包括外部資料相對較少的堆外部分。 今天我要問你的問題是,如何監控和診斷 JVM 堆內和堆外內存使用? ## 典型回答 了解 JVM 內存的方法有很多,具體能力范圍也有區別,簡單總結如下: * 可以使用綜合性的圖形化工具,如 JConsole、VisualVM(注意,從 Oracle JDK 9 開始,VisualVM 已經不再包含在 JDK 安裝包中)等。這些工具具體使用起來相對比較直觀,直接連接到 Java 進程,然后就可以在圖形化界面里掌握內存使用情況。 以 JConsole 為例,其內存頁面可以顯示常見的**堆內存**和**各種堆外部分**使用狀態。 * 也可以使用命令行工具進行運行時查詢,如 jstat 和 jmap 等工具都提供了一些選項,可以查看堆、方法區等使用數據。 * 或者,也可以使用 jmap 等提供的命令,生成堆轉儲(Heap Dump)文件,然后利用 jhat 或 Eclipse MAT 等堆轉儲分析工具進行詳細分析。 * 如果你使用的是 Tomcat、Weblogic 等 Java EE 服務器,這些服務器同樣提供了內存管理相關的功能。 * 另外,從某種程度上來說,GC 日志等輸出,同樣包含著豐富的信息。 這里有一個相對特殊的部分,就是是堆外內存中的直接內存,前面的工具基本不適用,可以使用 JDK 自帶的 Native Memory Tracking(NMT)特性,它會從 JVM 本地內存分配的角度進行解讀。 ## 考點分析 今天選取的問題是 Java 內存管理相關的基礎實踐,對于普通的內存問題,掌握上面我給出的典型工具和方法就足夠了。這個問題也可以理解為考察兩個基本方面能力,第一,你是否真的理解了 JVM 的內部結構;第二,具體到特定內存區域,應該使用什么工具或者特性去定位,可以用什么參數調整。 對于 JConsole 等工具的使用細節,我在專欄里不再贅述,如果你還沒有接觸過,你可以參考[JConsole 官方教程](https://docs.oracle.com/javase/7/docs/technotes/guides/management/jconsole.html)。我這里特別推薦[Java Mission Control](http://www.oracle.com/technetwork/java/javaseproducts/mission-control/java-mission-control-1998576.html)(JMC),這是一個非常強大的工具,不僅僅能夠使用[JMX](https://en.wikipedia.org/wiki/Java_Management_Extensions)進行普通的管理、監控任務,還可以配合[Java Flight Recorder](https://docs.oracle.com/javacomponents/jmc-5-4/jfr-runtime-guide/about.htm#JFRUH171)(JFR)技術,以非常低的開銷,收集和分析 JVM 底層的 Profiling 和事件等信息。目前, Oracle 已經將其開源,如果你有興趣請可以查看 OpenJDK 的[Mission Control](http://openjdk.java.net/projects/jmc/)項目。 關于內存監控與診斷,我會在知識擴展部分結合 JVM 參數和特性,盡量從龐雜的概念和 JVM 參數選項中,梳理出相對清晰的框架: * 細化對各部分內存區域的理解,堆內結構是怎樣的?如何通過參數調整? * 堆外內存到底包括哪些部分?具體大小受哪些因素影響? ## 知識擴展 今天的分析,我會結合相關 JVM 參數和工具,進行對比以加深你對內存區域更細粒度的理解。 首先,堆內部是什么結構? 對于堆內存,我在上一講介紹了最常見的新生代和老年代的劃分,其內部結構隨著 JVM 的發展和新 GC 方式的引入,可以有不同角度的理解,下圖就是年代視角的堆結構示意圖。 ![](https://img.kancloud.cn/72/1e/721e97abc93449fbdb4c071f7b3b5289_941x351.png) 你可以看到,按照通常的 GC 年代方式劃分,Java 堆內分為: 1\. 新生代 新生代是大部分對象創建和銷毀的區域,在通常的 Java 應用中,絕大部分對象生命周期都是很短暫的。其內部又分為 Eden 區域,作為對象初始分配的區域;兩個 Survivor,有時候也叫 from、to 區域,被用來放置從 Minor GC 中保留下來的對象。 * JVM 會隨意選取一個 Survivor 區域作為“to”,然后會在 GC 過程中進行區域間拷貝,也就是將 Eden 中存活下來的對象和 from 區域的對象,拷貝到這個“to”區域。這種設計主要是為了防止內存的碎片化,并進一步清理無用對象。 * 從內存模型而不是垃圾收集的角度,對 Eden 區域繼續進行劃分,Hotspot JVM 還有一個概念叫做 Thread Local Allocation Buffer(TLAB),據我所知所有 OpenJDK 衍生出來的 JVM 都提供了 TLAB 的設計。這是 JVM 為每個線程分配的一個私有緩存區域,否則,多線程同時分配內存時,為避免操作同一地址,可能需要使用加鎖等機制,進而影響分配速度,你可以參考下面的示意圖。從圖中可以看出,TLAB 仍然在堆上,它是分配在 Eden 區域內的。其內部結構比較直觀易懂,start、end 就是起始地址,top(指針)則表示已經分配到哪里了。所以我們分配新對象,JVM 就會移動 top,當 top 和 end 相遇時,即表示該緩存已滿,JVM 會試圖再從 Eden 里分配一塊兒。 ![](https://img.kancloud.cn/f5/46/f546839e98ea5d43b595235849b0f2bd_707x559.png) 2\. 老年代 放置長生命周期的對象,通常都是從 Survivor 區域拷貝過來的對象。當然,也有特殊情況,我們知道普通的對象會被分配在 TLAB 上;如果對象較大,JVM 會試圖直接分配在 Eden 其他位置上;如果對象太大,完全無法在新生代找到足夠長的連續空閑空間,JVM 就會直接分配到老年代。 3\. 永久代 這部分就是早期 Hotspot JVM 的方法區實現方式了,儲存 Java 類元數據、常量池、Intern 字符串緩存,在 JDK 8 之后就不存在永久代這塊兒了。 那么,我們如何利用 JVM 參數,直接影響堆和內部區域的大小呢?我來簡單總結一下: * 最大堆體積 ~~~ -Xmx value ~~~ * 初始的最小堆體積 ~~~ -Xms value ~~~ * 老年代和新生代的比例 ~~~ -XX:NewRatio=value ~~~ 默認情況下,這個數值是 2,意味著老年代是新生代的 2 倍大;換句話說,新生代是堆大小的 1/3。 * 當然,也可以不用比例的方式調整新生代的大小,直接指定下面的參數,設定具體的內存大小數值。 ~~~ -XX:NewSize=value ~~~ * Eden 和 Survivor 的大小是按照比例設置的,如果 SurvivorRatio 是 8,那么 Survivor 區域就是 Eden 的 1/8 大小,也就是新生代的 1/10,因為 YoungGen=Eden + 2\*Survivor,JVM 參數格式是 ~~~ -XX:SurvivorRatio=value ~~~ * TLAB 當然也可以調整,JVM 實現了復雜的適應策略,如果你有興趣可以參考這篇[說明](https://blogs.oracle.com/jonthecollector/the-real-thing)。 不知道你有沒有注意到,我在年代視角的堆結構示意圖也就是第一張圖中,還標記出了 Virtual 區域,這是塊兒什么區域呢? 在 JVM 內部,如果 Xms 小于 Xmx,堆的大小并不會直接擴展到其上限,也就是說保留的空間(reserved)大于實際能夠使用的空間(committed)。當內存需求不斷增長的時候,JVM 會逐漸擴展新生代等區域的大小,所以 Virtual 區域代表的就是暫時不可用(uncommitted)的空間。 第二,分析完堆內空間,我們一起來看看 JVM 堆外內存到底包括什么? 在 JMC 或 JConsole 的內存管理界面,會統計部分非堆內存,但提供的信息相對有限,下圖就是 JMC 活動內存池的截圖。 ![](https://img.kancloud.cn/fa/49/fa491795ffe21c1f49982de8b7810c2e_930x260.png) 接下來我會依賴 NMT 特性對 JVM 進行分析,它所提供的詳細分類信息,非常有助于理解 JVM 內部實現。 首先來做些準備工作,開啟 NMT 并選擇 summary 模式, ~~~ -XX:NativeMemoryTracking=summary ~~~ 為了方便獲取和對比 NMT 輸出,選擇在應用退出時打印 NMT 統計信息 ~~~ -XX:+UnlockDiagnosticVMOptions -XX:+PrintNMTStatistics ~~~ 然后,執行一個簡單的在標準輸出打印 HelloWorld 的程序,就可以得到下面的輸出 ![](https://img.kancloud.cn/55/f1/55f1c7f0550adbbcc885c97a4dd426bb_642x821.png) 我來仔細分析一下,NMT 所表征的 JVM 本地內存使用: * 第一部分非常明顯是 Java 堆,我已經分析過使用什么參數調整,不再贅述。 * 第二部分是 Class 內存占用,它所統計的就是 Java 類元數據所占用的空間,JVM 可以通過類似下面的參數調整其大小: ~~~ -XX:MaxMetaspaceSize=value ~~~ 對于本例,因為 HelloWorld 沒有什么用戶類庫,所以其內存占用主要是啟動類加載器(Bootstrap)加載的核心類庫。你可以使用下面的小技巧,調整啟動類加載器元數據區,這主要是為了對比以加深理解,也許只有在 hack JDK 時才有實際意義。 ~~~ -XX:InitialBootClassLoaderMetaspaceSize=30720 ~~~ * 下面是 Thread,這里既包括 Java 線程,如程序主線程、Cleaner 線程等,也包括 GC 等本地線程。你有沒有注意到,即使是一個 HelloWorld 程序,這個線程數量竟然還有 25。似乎有很多浪費,設想我們要用 Java 作為 Serverless 運行時,每個 function 是非常短暫的,如何降低線程數量呢? 如果你充分理解了專欄講解的內容,對 JVM 內部有了充分理解,思路就很清晰了: JDK 9 的默認 GC 是 G1,雖然它在較大堆場景表現良好,但本身就會比傳統的 Parallel GC 或者 Serial GC 之類復雜太多,所以要么降低其并行線程數目,要么直接切換 GC 類型; JIT 編譯默認是開啟了 TieredCompilation 的,將其關閉,那么 JIT 也會變得簡單,相應本地線程也會減少。 我們來對比一下,這是默認參數情況的輸出: ![](https://img.kancloud.cn/97/d0/97d060b306e44af3a8443f932a0a4d42_607x89.png) 下面是替換了默認 GC,并關閉 TieredCompilation 的命令行 ![](https://img.kancloud.cn/b0/7d/b07d6da56f588cbfadbb7b381346213b_592x71.png) 得到的統計信息如下,線程數目從 25 降到了 17,消耗的內存也下降了大概 1/3。 ![](https://img.kancloud.cn/59/37/593735623f6917695602095fd249d527_634x85.png) * 接下來是 Code 統計信息,顯然這是 CodeCache 相關內存,也就是 JIT compiler 存儲編譯熱點方法等信息的地方,JVM 提供了一系列參數可以限制其初始值和最大值等,例如: ~~~ -XX:InitialCodeCacheSize=value ~~~ ~~~ -XX:ReservedCodeCacheSize=value ~~~ 你可以設置下列 JVM 參數,也可以只設置其中一個,進一步判斷不同參數對 CodeCache 大小的影響。 ![](https://img.kancloud.cn/94/57/945740c37433f783d2d877c67dcc1170_530x42.png) ![](https://img.kancloud.cn/82/d1/82d1fbc9ca09698c01ccff18fb97c8cd_641x60.png) 很明顯,CodeCache 空間下降非常大,這是因為我們關閉了復雜的 TieredCompilation,而且還限制了其初始大小。 * 下面就是 GC 部分了,就像我前面介紹的,G1 等垃圾收集器其本身的設施和數據結構就非常復雜和龐大,例如 Remembered Set 通常都會占用 20%~30% 的堆空間。如果我把 GC 明確修改為相對簡單的 Serial GC,會有什么效果呢? 使用命令: ~~~ -XX:+UseSerialGC ~~~ ![](https://img.kancloud.cn/6e/ee/6eeee6624c7dc6be54bfce5e93064233_641x216.png) 可見,不僅總線程數大大降低(25 → 13),而且 GC 設施本身的內存開銷就少了非常多。據我所知,AWS Lambda 中 Java 運行時就是使用的 Serial GC,可以大大降低單個 function 的啟動和運行開銷。 * Compiler 部分,就是 JIT 的開銷,顯然關閉 TieredCompilation 會降低內存使用。 * 其他一些部分占比都非常低,通常也不會出現內存使用問題,請參考[官方文檔](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr022.html#BABCBGFA)。唯一的例外就是 Internal(JDK 11 以后在 Other 部分)部分,其統計信息**包含著 Direct Buffer 的直接內存**,這其實是堆外內存中比較敏感的部分,很多堆外內存 OOM 就發生在這里,請參考專欄第 12 講的處理步驟。原則上 Direct Buffer 是不推薦頻繁創建或銷毀的,如果你懷疑直接內存區域有問題,通常可以通過類似 instrument 構造函數等手段,排查可能的問題。 JVM 內部結構就介紹到這里,主要目的是為了加深理解,很多方面只有在定制或調優 JVM 運行時才能真正涉及,隨著微服務和 Serverless 等技術的興起,JDK 確實存在著為新特征的工作負載進行定制的需求。 今天我結合 JVM 參數和特性,系統地分析了 JVM 堆內和堆外內存結構,相信你一定對 JVM 內存結構有了比較深入的了解,在定制 Java 運行時或者處理 OOM 等問題的時候,思路也會更加清晰。JVM 問題千奇百怪,如果你能快速將問題縮小,大致就能清楚問題可能出在哪里,例如如果定位到問題可能是堆內存泄漏,往往就已經有非常清晰的[思路和工具](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/memleaks004.html#CIHIEEFH)可以去解決了。 ## 一課一練 關于今天我們討論的題目你做到心中有數了嗎?今天的思考題是,如果用程序的方式而不是工具,對 Java 內存使用進行監控,有哪些技術可以做到?
                  <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>

                              哎呀哎呀视频在线观看