天天看點

第一篇:自動記憶體管理機制第一章 Java記憶體區域與記憶體溢出異常第二章 垃圾收集器與記憶體配置設定政策

第一章 Java記憶體區域與記憶體溢出異常

一、概述:

        對于java程式員來說,在虛拟機的自動記憶體管理機制下,不再需要為每一個new操作去配置設定對應的delete/free操作,不再容易出現記憶體洩漏和記憶體溢出問題。不過,也正是因為java把記憶體管理交給了java虛拟機,一旦出現了記憶體洩漏和記憶體溢出的問題,問題就不是那麼好解決了。

二、運作時資料區域:

        java虛拟機在執行java程式的過程中會把它所管理的記憶體劃分為若幹個不同的資料區域,不同的資料區域發揮着不同的作用。下面呈現運作時的資料區。

第一篇:自動記憶體管理機制第一章 Java記憶體區域與記憶體溢出異常第二章 垃圾收集器與記憶體配置設定政策

橙色部分:生命周期與線程同步,與線程同生同滅。

灰色部分:所有線程公用。

2.1 資料區詳細介紹:

1、程式計數器:

         程式計數器的作用可以看作是 目前線程所執行的位元組碼的行号訓示器,位元組碼解釋器的作用就是通過改變這個位元組碼的值來選取下一條要執行的位元組碼指令。這樣就可以實作分支、循環、跳轉、異常處理、線程回複等基本職能。

        因為每個線程執行時涉及到線程之間的切換,是以每個線程目前的行号執行資訊必須記錄下來,這樣就決定了每個線程都擁有一個程式計數器。各條線程之間的程式計數器互不影響、獨立存儲。

2、Java虛拟機棧:

         java虛拟機棧描述的是java 方法執行的記憶體模型:每個方法執行的時候都會建立一個棧幀,這個棧幀用于存儲 局部變量、方法出口、操作棧、動态連結等。每一個方法從執行到完成的過程,就對應着每個方法從進入虛拟機棧到出棧的過程。

         在這個虛拟機規範中,對這個區域規定了兩種異常狀況:

                  (1)、如果線程請求的棧深度大于目前虛拟機棧的深度,将抛出StackOverflowError異常。

                  (2)、如果請求深度不夠,目前虛拟機進行深度擴充後記憶體還是不夠,就會抛出:OutOfMemoryError。

3、本地方法棧:

         本地方法棧與java虛拟機棧的作用相似,隻是它是為虛拟機使用到的本地方法服務的。

         在這個虛拟機規範中,對這個區域規定了兩種異常狀況:(與虛拟機棧一樣)

                  (1)、如果線程請求的棧深度大于當本地方法棧的深度,将抛出StackOverflowError異常。

                  (2)、如果請求深度不夠,目前虛拟機進行深度擴充後記憶體還是不夠,就會抛出:OutOfMemoryError。

4、方法區:

          它用于存儲已被虛拟機加載的類資訊、常量、靜态變量、即時編譯器編譯後的代碼等資料。

           方法區的特點:(1)、和堆一樣可以不需要連續配置設定的實體存儲空間。

                                       (2)、可以選擇不對本區域産生的垃圾進行回收。

           方法區有一部分叫做:常量池。class檔案中除了類的版本、字段、方法、接口等會存儲這個資訊。例如String類的intern()方法。

           異常:

                   當方法區無法滿足記憶體配置設定要求時,OutOfMemoryError

5、堆:

           java堆是被所有記憶體共享的一塊區域,此記憶體區域唯一存儲的就是對象執行個體。

           java堆是垃圾收集器管理的主要區域。堆有可能在某些标準中繼續劃分,劃分實質是為了更好地回收記憶體,或者更快地配置設定記憶體。

           java堆可以處在實體上不連續的記憶體空間中,隻要邏輯上是連續的就是可以的。就像我們的磁盤一樣,可以是固定的,也可以是擴充的。

       異常抛出:

                如果堆中執行個體配置設定不夠用,并且堆也無法再進行擴充的時候,将會抛出OutOfMemoryError。

2.2 對象通路

       說明:一個最簡單的對象的建立,都會涉及到記憶體中的棧、堆、方法區。

       例如:Object c = new Object();

Object c 這個首先會映射到記憶體的本地方法棧、new Object()毫無疑問映射到堆中。

因為java中的引用類型(reference)隻是規定了一個指向對象的引用,卻并沒有規定怎麼去定位,以通路java堆中的對象的具體位置,不同的虛拟機使用的方式是不一樣的,主流的方法分為兩種:

          (1)、通過句柄通路對象:

第一篇:自動記憶體管理機制第一章 Java記憶體區域與記憶體溢出異常第二章 垃圾收集器與記憶體配置設定政策

          (2)、通過指針通路對象:

第一篇:自動記憶體管理機制第一章 Java記憶體區域與記憶體溢出異常第二章 垃圾收集器與記憶體配置設定政策

2.3 java堆溢出

不管是哪個區發生了OOM問題,都可以在eclipse/myeclipse中安裝 eclipse/myeclipseMemory Analyzer插件進行分析。 myeclipse安裝memory analyzer: http://blog.csdn.net/yanghongchang_/article/details/7711911

java堆記憶體是發生OOM的最常見的情況,要分析 這個區的OOM問題,首先要搞清楚是發生了 記憶體洩漏 還是記憶體溢出。

     1、記憶體洩漏和記憶體溢出:

           記憶體洩漏:記憶體中的對象都死了。(使用工具檢視記憶體洩漏對象到GC Roots的引用鍊)

           記憶體溢出:記憶體中的對象還都活着。(嘗試減少記憶體消耗)

第二章 垃圾收集器與記憶體配置設定政策

一、概述:

垃圾回收(GC操作)需要完成的三種操作:

         (1)哪些記憶體需要回收

         (2)什麼時候回收

         (3)怎樣回收

1.1  哪些記憶體需要回收:

         根據上面資料區運作時的記憶體劃分圖,我們知道 程式計數器、虛拟機棧、本地方法棧都與線程的生命周期是相同的,是以說方法結束或者是線程結束後,這些記憶體區域也就跟着回收了。但是方法區和堆卻不是這樣,對方法區和堆來說,一個接口中的多個實作類需要的記憶體可能不一樣,一個方法中的多個分支需要的記憶體也可能不一樣,我們隻有在程式運作期的時候才知道程式需要建立的對象是哪些,這部分記憶體的配置設定和回收都是動态的,因為垃圾收集器與回收器關注的也就是這部分内容。

1.2  什麼時候回收?

      1.2.1  在判斷一個對象是否應該被回收的時候一般先要判斷對象是活的還是死的。下面兩個算法就是用來判斷對象是活的還是死的。

       算法一:引用計數算法

               基本思想: 給每一個對象添加一個引用計數器,當有地方調用它時就加一,引用失效時就減一。任何時刻計數器都為0的對象就是死了的。

               缺陷:很難解決對象之間的循環引用。

       算法二:根搜尋算法(在主流語言java、c#等都是使用這個算法)

              基本思想:當一個對象到GC Roots沒有任何引用鍊相連(從圖論來說就是從GC Roots到這個對象不可達時),就證明次對象是不可用的。

第一篇:自動記憶體管理機制第一章 Java記憶體區域與記憶體溢出異常第二章 垃圾收集器與記憶體配置設定政策
第一篇:自動記憶體管理機制第一章 Java記憶體區域與記憶體溢出異常第二章 垃圾收集器與記憶體配置設定政策

   如上圖所示,object5、object6、object7是可以回收的對象。

   可以作為GC Roots對象的有以下幾種:

             (1) 虛拟機棧

             (2)方法區中的類靜态屬性引用的對象

             (3)方法區中的常量引用的對象

             (4)本地方法棧中的JNI的引用的對象。

1.2.2  關于引用的補充(這個也是蠻重要的知識點,android網絡圖檔批量下載下傳顯示的時候用過)

JDK 1.2之前:

         java中的引用定義是這樣的:如果reference類型的資料中存儲的數值代表的是另外一塊記憶體的起始位址,就稱這塊記憶體代表着一個引用。但是這樣一種情況是很常見的:當記憶體足夠時,對象是可以儲存在記憶體中的,但是當記憶體進行GC(垃圾回收)之後,記憶體還是很緊張,這個時候抛棄這些對象。很多系統的緩存功能都符合這樣的應用場景。

JDK 1.2之後:

         java對引用的概念進行了擴充,将引用分為強引用、軟引用、弱引用、虛引用。這四種引用強度依次遞減。

         強引用:是最普遍存在的,例如Object c= new Object(),隻要強引用一直存在,那垃圾回收器永遠都不會回收掉這個對象。

         軟引用:軟引用并不是必須的對象。對于軟引用關聯着的對象,在報出記憶體溢出異常之前,垃圾回收器将會把這些對象列在回收範圍之内進行第二次回收。如果這次回收還是沒有足夠的記憶體,才會抛出記憶體溢出錯誤。

         弱引用:弱引用也是用來描述非必需對象的,被弱引用引用的對象隻能生存道下一次垃圾收集發生之前。當垃圾收集器工作時,無論記憶體是否夠用,都會回收掉隻被弱引用關聯的對象。

         虛引用:為一個對象設定虛引用關聯的唯一目的就是希望這個對象被垃圾回收時收到一個系統通知。

上面的補充明顯讓我們知道JDK 1.2之後一個對象的生存和死亡并沒有那麼簡單。(也就是說不止判斷一次)

1.2.3  真正判斷對象是否是生存還是死亡。(前提:使用算法二:根搜尋算法)

要真正宣告一個對象死亡,至少要經曆兩次标記過程,過程如下所示:

  (1)如果對象在 根搜尋之後沒有發現與GC Roots相連接配接的引用鍊,進入第一次标記并進行篩選。

  (2)篩選條件:該對象有沒有必要執行finalize()方法,當對象沒有覆寫finalize方法、或是對象的finalize方法已經被虛拟機調用過,則篩選結果為沒必要執行。

  (3)如果篩選結果finanize()方法标記為有必要執行,這些對象被放在 一個F-Queue隊列中,之後在虛拟機自己建立的一個線程中去執行。如果一個對象的finalize()方法在隊列中長時間執行或是循環執行,是以來了第二次标記。

  (4)如果對象要在第二次标記中成功拯救自己------隻要重新将自己連結到 GC Roots鍊中即可。具體方法例如:把自己指派給某個變量或是對象的成員對象。

 注意:任何一個對象的finalize方法隻能被系統自動調用一次,如果面臨下一次回收,它的finalize方法不會再被執行。在java中,這些都不用特别去做,java中的try-finally就可以很好地利用這個原理。

1.3 如何回收垃圾:(具體我們找書就可以了)

 1.3.1  垃圾收集算法:

           (1)标記-清除算法

                     原理:首先标記出所有要收集的對象,然後統一清除掉被标記的對象。(會産生空間碎片)

           (2)複制算法

                     原理:首先将記憶體分為兩部分,第一部分用完之後,将第一部分活着的對象複制到第二部分,然後統一清除掉第一部分的對象。

           (3)标記-整理算法

                      原理:首先标記出所有要收集的對象,然後将所有存活的對象向一端移動,順序排列,接着直接清除掉端邊界以外的記憶體。(不會産生空間碎片)

           (4)分代收集算法

                      當代虛拟機都采用分代收集算法。

                       原理:根據對象的存活周期的不同将記憶體劃分為幾塊,一般是吧java堆劃分為 新生代和 老生代。然後對不同的代采用不同的垃圾回收算法。一般:新生代采用複制算法,老生代采用标記-清楚 或是标記-整理算法。

 1.3.2  垃圾收集器:(因為不同廠商提供的垃圾收集器不同,至今有太多版本,但沒有一種是達到所有人的統一的)

            (1)G1收集器等(标記-整理算法)

                        将java堆劃分為多個固定大小的塊,先收集垃圾産生最多的區域(優先級),保證效率。

            (2)Serial收集器:(采用标記-整理算法)

                      特點:是一個單線程收集器,隻會使用一個CPU或是一個垃圾收集線程去回收垃圾。

                                  執行時必須暫停掉其他所有的工作線程,知道它收集結束。

            (3)CMS收集器等(采用 标記-清除算法)

                      特點:是一種以擷取最短停頓時間為目标的收集器。

             and so on...

繼續閱讀