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

                ??碼云GVP開源項目 12k star Uniapp+ElementUI 功能強大 支持多語言、二開方便! 廣告
                今天,我將從內存管理的角度,進一步探索 Java 虛擬機(JVM)。垃圾收集機制為我們打理了很多繁瑣的工作,大大提高了開發的效率,但是,垃圾收集也不是萬能的,懂得 JVM 內部的內存結構、工作機制,是設計高擴展性應用和診斷運行時問題的基礎,也是 Java 工程師進階的必備能力。 今天我要問你的問題是,談談 JVM 內存區域的劃分,哪些區域可能發生 OutOfMemoryError? ## 典型回答 通常可以把 JVM 內存區域分為下面幾個方面,其中,有的區域是以線程為單位,而有的區域則是整個 JVM 進程唯一的。 首先,**程序計數器**(PC,Program Counter Register)。在 JVM 規范中,每個線程都有它自己的程序計數器,并且任何時間一個線程都只有一個方法在執行,也就是所謂的當前方法。程序計數器會存儲當前線程正在執行的 Java 方法的 JVM 指令地址;或者,如果是在執行本地方法,則是未指定值(undefined)。 第二,**Java 虛擬機棧**(Java Virtual Machine Stack),早期也叫 Java 棧。每個線程在創建時都會創建一個虛擬機棧,其內部保存一個個的棧幀(Stack Frame),對應著一次次的 Java 方法調用。 前面談程序計數器時,提到了當前方法;同理,在一個時間點,對應的只會有一個活動的棧幀,通常叫作當前幀,方法所在的類叫作當前類。如果在該方法中調用了其他方法,對應的新的棧幀會被創建出來,成為新的當前幀,一直到它返回結果或者執行結束。JVM 直接對 Java 棧的操作只有兩個,就是對棧幀的壓棧和出棧。 棧幀中存儲著局部變量表、操作數(operand)棧、動態鏈接、方法正常退出或者異常退出的定義等。 第三,**堆**(Heap),它是 Java 內存管理的核心區域,用來放置 Java 對象實例,幾乎所有創建的 Java 對象實例都是被直接分配在堆上。堆被所有的線程共享,在虛擬機啟動時,我們指定的“Xmx”之類參數就是用來指定最大堆空間等指標。 理所當然,堆也是垃圾收集器重點照顧的區域,所以堆內空間還會被不同的垃圾收集器進行進一步的細分,最有名的就是新生代、老年代的劃分。 第四,**方法區**(Method Area)。這也是所有線程共享的一塊內存區域,用于存儲所謂的元(Meta)數據,例如類結構信息,以及對應的運行時常量池、字段、方法代碼等。 由于早期的 Hotspot JVM 實現,很多人習慣于將方法區稱為永久代(Permanent Generation)。Oracle JDK 8 中將永久代移除,同時增加了元數據區(Metaspace)。 第五,**運行時常量池**(Run-Time Constant Pool),這是方法區的一部分。如果仔細分析過反編譯的類文件結構,你能看到版本號、字段、方法、超類、接口等各種信息,還有一項信息就是常量池。Java 的常量池可以存放各種常量信息,不管是編譯期生成的各種字面量,還是需要在運行時決定的符號引用,所以它比一般語言的符號表存儲的信息更加寬泛。 第六,**本地方法棧**(Native Method Stack)。它和 Java 虛擬機棧是非常相似的,支持對本地方法的調用,也是每個線程都會創建一個。在 Oracle Hotspot JVM 中,本地方法棧和 Java 虛擬機棧是在同一塊兒區域,這完全取決于技術實現的決定,并未在規范中強制。 ## 考點分析 這是個 JVM 領域的基礎題目,我給出的答案依據的是[JVM 規范](https://docs.oracle.com/javase/specs/jvms/se9/html/jvms-2.html#jvms-2.5)中運行時數據區定義,這也和大多數書籍和資料解讀的角度類似。 JVM 內部的概念龐雜,對于初學者比較晦澀,我的建議是在工作之余,還是要去閱讀經典書籍,比如我推薦過多次的《深入理解 Java 虛擬機》。 今天這一講作為 Java 虛擬機內存管理的開篇,我會側重于: * 分析廣義上的 JVM 內存結構或者說 Java 進程內存結構。 * 談到 Java 內存模型,不可避免的要涉及 OutOfMemory(OOM)問題,那么在 Java 里面存在哪些種 OOM 的可能性,分別對應哪個內存區域的異常狀況呢? 注意,具體 JVM 的內存結構,其實取決于其實現,不同廠商的 JVM,或者同一廠商發布的不同版本,都有可能存在一定差異。我在下面的分析中,還會介紹 Oracle Hotspot JVM 的部分設計變化。 ## 知識擴展 首先,為了讓你有個更加直觀、清晰的印象,我畫了一個簡單的內存結構圖,里面展示了我前面提到的堆、線程棧等區域,并從數量上說明了什么是線程私有,例如,程序計數器、Java 棧等,以及什么是 Java 進程唯一。另外,還額外劃分出了直接內存等區域。 ![](https://img.kancloud.cn/36/0b/360b8f453e016cb641208a6a8fb589bc_706x494.png) 這張圖反映了實際中 Java 進程內存占用,與規范中定義的 JVM 運行時數據區之間的差別,它可以看作是運行時數據區的一個超集。畢竟理論上的視角和現實中的視角是有區別的,規范側重的是通用的、無差別的部分,而對于應用開發者來說,只要是 Java 進程在運行時會占用,都會影響到我們的工程實踐。 我這里簡要介紹兩點區別: * 直接內存(Direct Memory)區域,它就是我在[專欄第 12 講](http://time.geekbang.org/column/article/8393)中談到的 Direct Buffer 所直接分配的內存,也是個容易出現問題的地方。盡管,在 JVM 工程師的眼中,并不認為它是 JVM 內部內存的一部分,也并未體現 JVM 內存模型中。 * JVM 本身是個本地程序,還需要其他的內存去完成各種基本任務,比如,JIT Compiler 在運行時對熱點方法進行編譯,就會將編譯后的方法儲存在 Code Cache 里面;GC 等功能需要運行在本地線程之中,類似部分都需要占用內存空間。這些是實現 JVM JIT 等功能的需要,但規范中并不涉及。 如果深入到 JVM 的實現細節,你會發現一些結論似乎有些模棱兩可,比如: * Java 對象是不是都創建在堆上的呢? 我注意到有一些觀點,認為通過[逃逸分析](https://en.wikipedia.org/wiki/Escape_analysis),JVM 會在棧上分配那些不會逃逸的對象,這在理論上是可行的,但是取決于 JVM 設計者的選擇。據我所知,Oracle Hotspot JVM 中并未這么做,這一點在逃逸分析相關的[文檔](https://docs.oracle.com/javase/8/docs/technotes/guides/vm/performance-enhancements-7.html#escapeAnalysis)里已經說明,所以可以明確所有的對象實例都是創建在堆上。 * 目前很多書籍還是基于 JDK 7 以前的版本,JDK 已經發生了很大變化,Intern 字符串的緩存和靜態變量曾經都被分配在永久代上,而永久代已經被元數據區取代。但是,Intern 字符串緩存和靜態變量并不是被轉移到元數據區,而是直接在堆上分配,所以這一點同樣符合前面一點的結論:對象實例都是分配在堆上。 接下來,我們來看看什么是 OOM 問題,它可能在哪些內存區域發生? 首先,OOM 如果通俗點兒說,就是 JVM 內存不夠用了,javadoc 中對[OutOfMemoryError](https://docs.oracle.com/javase/9/docs/api/java/lang/OutOfMemoryError.html)的解釋是,沒有空閑內存,并且垃圾收集器也無法提供更多內存。 這里面隱含著一層意思是,在拋出 OutOfMemoryError 之前,通常垃圾收集器會被觸發,盡其所能去清理出空間,例如: * 我在[專欄第 4 講](http://time.geekbang.org/column/article/6970)的引用機制分析中,已經提到了 JVM 會去嘗試回收軟引用指向的對象等。 * 在[java.nio.BIts.reserveMemory()](http://hg.openjdk.java.net/jdk/jdk/file/9f62267e79df/src/java.base/share/classes/java/nio/Bits.java)方法中,我們能清楚的看到,System.gc() 會被調用,以清理空間,這也是為什么在大量使用 NIO 的 Direct Buffer 之類時,通常建議不要加下面的參數,畢竟是個最后的嘗試,有可能避免一定的內存不足問題。 ~~~ -XX:+DisableExplictGC ~~~ 當然,也不是在任何情況下垃圾收集器都會被觸發的,比如,我們去分配一個超大對象,類似一個超大數組超過堆的最大值,JVM 可以判斷出垃圾收集并不能解決這個問題,所以直接拋出 OutOfMemoryError。 從我前面分析的數據區的角度,除了程序計數器,其他區域都有可能會因為可能的空間不足發生 OutOfMemoryError,簡單總結如下: * 堆內存不足是最常見的 OOM 原因之一,拋出的錯誤信息是“java.lang.OutOfMemoryError:Java heap space”,原因可能千奇百怪,例如,可能存在內存泄漏問題;也很有可能就是堆的大小不合理,比如我們要處理比較可觀的數據量,但是沒有顯式指定 JVM 堆大小或者指定數值偏小;或者出現 JVM 處理引用不及時,導致堆積起來,內存無法釋放等。 * 而對于 Java 虛擬機棧和本地方法棧,這里要稍微復雜一點。如果我們寫一段程序不斷的進行遞歸調用,而且沒有退出條件,就會導致不斷地進行壓棧。類似這種情況,JVM 實際會拋出 StackOverFlowError;當然,如果 JVM 試圖去擴展棧空間的的時候失敗,則會拋出 OutOfMemoryError。 * 對于老版本的 Oracle JDK,因為永久代的大小是有限的,并且 JVM 對永久代垃圾回收(如,常量池回收、卸載不再需要的類型)非常不積極,所以當我們不斷添加新類型的時候,永久代出現 OutOfMemoryError 也非常多見,尤其是在運行時存在大量動態類型生成的場合;類似 Intern 字符串緩存占用太多空間,也會導致 OOM 問題。對應的異常信息,會標記出來和永久代相關:“java.lang.OutOfMemoryError: PermGen space”。 * 隨著元數據區的引入,方法區內存已經不再那么窘迫,所以相應的 OOM 有所改觀,出現 OOM,異常信息則變成了:“java.lang.OutOfMemoryError: Metaspace”。 * 直接內存不足,也會導致 OOM,這個已經[專欄第 11 講](http://time.geekbang.org/column/article/8369)介紹過。 今天是 JVM 內存部分的第一講,算是我們先進行了熱身準備,我介紹了主要的內存區域,以及在不同版本 Hotspot JVM 內部的變化,并且分析了各區域是否可能產生 OutOfMemoryError,以及 OOME 發生的典型情況。 ## 一課一練 關于今天我們討論的題目你做到心中有數了嗎?今天的思考題是,我在試圖分配一個 100M bytes 大數組的時候發生了 OOME,但是 GC 日志顯示,明明堆上還有遠不止 100M 的空間,你覺得可能問題的原因是什么?想要弄清楚這個問題,還需要什么信息呢?
                  <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>

                              哎呀哎呀视频在线观看