深刻理解java虛擬機學習筆記(一)JVM內存模型

        上週末搬家後,家裏的寬帶一直沒弄好,跟電信客服反映了N遍了終於約了個師傅明天早上來遷移寬帶,能夠結束一個多星期沒網的痛苦日子了。這段時間也是各類忙,都一個星期沒更新博客了,再不寫以前那種狀態和激情都要慢慢褪去了,總以爲內心慌的一逼,這怎麼行呢?!趁明天週末,在公司電腦上記錄下這周的一些學習內容。近期在看一本很經典的java書籍:《深刻理解java虛擬機 第二版》,幾年前也翻過,但那時候功力不夠,不太能看懂就沒看了。如今回過頭來看,發現確實寫的很好,不少知識點都能理解了,並且講的也頗有深度,收穫頗多。後期計劃按照這本書的內容寫出一系列博客,來深刻學習和複習下java虛擬機相關的知識。java

       1、JVM內存模型概述算法

              JVM內存模型其實也挺簡單的,這裏先提2個知識點:數組

                    一、組成:java堆,java棧(即虛擬機棧),本地方法棧,方法區和程序計數器。數據結構

                    二、是否共享:其中方法區和堆區是線程共享的,虛擬機棧,本地方法棧和程序計數器是線程私有的,也稱線程隔離的,每一個區域存儲不一樣的內容。這2個知識點必須牢記,是掌握JVM內存模型的基礎。學習

                           

       2、程序計數器spa

              JVM中的程序計數器是一塊很小的內存區域,可是這塊內存區域挺有意思的。主要特性有3個:.net

              一、存儲內容:對於java普通方法(即沒用native關鍵字修飾的方法),存儲的是執行過程當中當前指令的地址,而對於native方法,這裏是空的(undefined),爲啥呢?由於調用本地方法的時候可能已經超出了JVM虛擬機的內存地址了。線程

              二、線程私有的:爲何程序計數器是線程私有的?根據存儲內容也好理解,假如是線程共享的,那多個線程執行的時候,都不知道本身當前線程執行的地址是哪一個了,有的線程快,有的線程慢,快的執行完就進入下一步,等慢的線程執行完回來發現本身的地址都變了,豈不亂套?3d

              三、是JVM中惟一不會報內存溢出(OutOfMemoryError)的區域。對象

       3、虛擬機棧

              虛擬機棧主要存儲的是一個個棧幀,每一個棧幀中存儲的是局部變量表,操做數棧,動態連接和方法出口信息等。其中局部變量表中存儲的是方法中定義的一些局部變量,對象的引用,參數,和方法的返回地址等。局部變量表所佔用的空間大小在編譯期就能肯定,在方法運行的時候,並不會改變局部變量表的空間大小,這結合局部變量表存儲的內容就很好理解。操做數棧能夠理解爲對當前操做的數據入出棧,對於64位長度的long和double類型,每一個操做數佔用2個字寬(slot),其餘類型的操做數佔用一個字寬(slot)。每一個方法調用時都會建立一個棧幀,執行的過程對應的就是一個棧幀在虛擬機棧中從入棧到出棧的過程。有關棧幀的內容能夠參考一個網友寫的一篇博客:https://blog.csdn.net/xtayfjpk/article/details/41924283,講的很好很詳細。這裏放個棧幀的圖,看了一目瞭然。

            

           關於虛擬機棧內存溢出有2種狀況:

           一、線程請求的棧深度 超過了虛擬機容許的深度,會拋出StackOverflowError,因此當咱們在代碼中看到這個異常時,就應該想到多是虛擬機棧出了問題。

           二、若是虛擬機棧能夠動態擴展(當前大部分JVM均可以動態擴展,不過JVM也容許固定長度的虛擬機棧),當擴展時沒法申請到足夠的內存時,會拋出OutOfMemoryError異常。

       4、本地方法棧

           這塊知識點比較簡單,本地方法棧和虛擬機棧的功能相似,只不過是爲JVM調用native方法時服務的,並且JVM對本地方法使用的語言(好比Java調用C語言實現的功能,就須要定義native方法來實現)、使用方式和數據結構都沒有強制規定,所以不一樣的虛擬機能夠自由實現。並且HotSpot虛擬機直接把本地方法棧和虛擬機棧合二爲一。與虛擬機棧相似,本地方法棧也會拋出StackOverflowError和OutOfMemoryError。

       5、方法區

           方法區是一個比較重要的區域,java虛擬機規範中把方法區描述爲堆的一個邏輯部分,可是爲了和Heap(堆區)對應,也稱Non-Heap(非堆區)。主要存儲的是靜態變量,常量(包括運行時常量),類的加載信息和java編譯後的代碼。這部分空間不須要連續,能夠選擇固定大小和可擴展,一般在這部分是沒有GC的,由於GC回收的都是些靜態變量,常量和類的加載信息,這些對象回收效果一般不盡人意,所以能夠選擇不實現垃圾回收。這塊區域也稱爲持久代,當這塊內存不足時,也會報OutOfMemoryError異常。

       6、堆區

         Java堆區是JVM內存中最胖的一塊區域,由於這裏存儲的都是對象的實例和數組對象。這塊區域是線程共享的,在JVM啓動時就會建立,想一想若是這麼大的空間是線程私有的,那內存不得爆掉嗎?按照java虛擬機規範,堆區的內容能夠物理上不連續,只要邏輯上連續便可,在實現時能夠是固定大小的,也能夠是可擴展的,並且一般都是可擴展的,咱們經常使用的內存參數-Xms和-Xmx就是用來調節堆大小的。java堆區按生命週期不一樣,分爲新生代和老年代。新生代又能夠細分爲Eden和Survivor區,而Survivor又能夠細分爲Survivor1和Survivor2,這二者一般只使用其中一塊,另外一塊用來GC時保留存活的對象。大部分的new出來的對象都是存放在Eden區,若是是大對象,好比一個很大的數組或者List對象,能夠經過JVM參數-XX:PretenureSizeThreshold將超過指定大小的對象直接存入到老年代,須要注意的是,寫程序時應該儘可能避免朝生夕死的大對象進入老年代,由於相比年輕代的GC,老年代GC的成本更大。Eden和Survivor的默認大小比值的8:1:1,新生代默認的GC算法是複製算法。老年代的默認GC算法是標記整理法。關於這2種GC算法,會在下篇博客講解。

當堆中沒有足夠內存時,會拋出OutOfMemoryError異常。關於堆區的內存模型,能夠參考下面的圖片:

       

相關文章
相關標籤/搜索