Android內存優化1 瞭解java內存分配 1

開篇廢話

今天咱們一塊兒來學習JVM的內存分配,主要目的是爲咱們Android內存優化打下基礎。java

一直在想以什麼樣的方式來呈現這個知識點才能讓咱們易於理解,最終決定使用方法爲:圖解+源代碼分析git

歡迎訪問個人我的博客:senduo's blog程序員

但願能在咱們平時開發寫代碼的時候,可以知道當前寫的這段代碼,內存方面是如何分配的。

咱們深知,一個Java程序員在不少時候根本不用操心內存的釋放,而是依靠JVM去管理,之前寫C++代碼的時候,卻要時刻記着new的空間要及時釋放掉,否則程序很容易出現內存溢出的狀況。由於,Java在這方面確實方便了許多,讓咱們有更多精力去考慮業務方面的實現。可是,這並不意味着咱們就能肆無忌憚的使用內存,由於:數組

1.JVM並不會及時的去清理內存

2.咱們沒法經過代碼去控制JVM去清理內存

這就要求咱們平時在開發過程當中,要了解JVM的垃圾回收機制,合理安排內存。緩存

那麼怎麼樣才能合理安排內存呢?那麼就須要咱們瞭解JVM的內存分配機制,然後才能真正控制好,讓程序運行在咱們鼓掌之中。性能優化


技術詳情

1.JVM內存模型

平時咱們對於Java內存都有一個比較粗略的概念,就是分堆和棧,但實際上仍是複雜得多,如下給出完整內存模型:性能

內存模型
內存模型

相對應區域的內容爲:學習

內容模型
內容模型

1.1程序計數器PC

這一個區域我歸納瞭如下幾個要點:優化

1.這一區域不會出現OOM(Out Of Memory)錯誤的狀況

2.屬於線程私有,由於每個線程都有本身的一個程序計數器,來表示當前線程執行的字節碼行號

3.標識Java方法的字節碼地址,而不是Native方法

4.處於CPU上,咱們沒法直接操做這塊區域

1.2虛擬機棧

這個區域也是咱們平時口中說的堆棧的棧,關於這個塊區域有以下要點:spa

1.屬於線程私有,與線程的生命週期相同

2.每個java方法被執行的時候,這個區域會生成一個棧幀

4.棧幀中存放的局部變量有8種基本數據類型,以及引用類型(對象的內存地址)

5.java方法的運行過程就是棧幀在虛擬機棧中入棧和出棧的過程

6.當線程請求的棧的深度超出了虛擬機棧容許的深度時,會拋出StackOverFlow的錯誤

7.當Java虛擬機動態擴展到沒法申請足夠內存時會拋出OutOfMemory的錯誤

1.3本地方法棧

這個區域,屬於線程私有,顧名思義,區別於虛擬機棧,這裏是用來處理Native方法(Java本地方法)的,而虛擬機棧是處理Java方法的。對於Native方法,Object中就有很多的Native的方法,hashCode,wait等,這些方法的執行不少時候都是藉助於操做系統。

這一區域也有可能拋出StackOverFlowError 和 OutOfMemoryError

1.4 Java堆

咱們平時說得最多,關注得最多的一個區域,就是他了。咱們後期進行的性能優化主要針對這部份內存,GC的主戰場,這個地方存放的幾乎全部的對象實例和數組數據。這裏我大概進行了以下歸納:

1.Java堆屬於線程共享區域,全部的線程共享這一塊內存區域

2.從內存回收角度,Java堆可被分爲新生代和老年代,這樣分可以更快的回收內存

3.從內存分配角度,Java堆可劃分出線程私有的分配緩存區(Thread Local Allocation Buffer,TLAB),這樣可以更快的分配內存

4.當Java虛擬機動態擴展到沒法申請足夠內存時會拋出OutOfMemory的錯誤

1.5 方法區

方法區主要存放的是已被虛擬機加載的類信息、常量、靜態變量、編譯器編譯後的代碼等數據。GC在該區域出現的比較少。歸納以下:

1.方法區屬於線程共享區域

2.習慣性加他永久代

3.垃圾回收不多光顧這個區域,不過也是須要回收的,主要針對常量池回收,類型卸載

4.常量池用於存放編譯期生成的各類字節碼和符號引用,常量池具備必定的動態性,
  裏面能夠存放編譯期生成的常量

5.運行期間的常量也能夠添加進入常量池中,好比string的intern()方法。

1.6 運行時常量池

運行時常量池也是方法區的一部分,用於存放編譯器生成的各類字面量和符號引用。單獨拿出來講明一下,是由於咱們平時使用String比價多,涉及到這一塊的知識,但這一塊區域不會拋出OutOfMemoryError

2.JVM內存源碼示例說明

首先寫了一個main方法,來作演示,代碼以下:

package senduo.com.memory.allocate;

/**
 * *****************************************************************
 * * 文件做者:ouyangshengduo
 * * 建立時間:2017/8/11
 * * 文件描述:內存分配調用過程演示代碼
 * * 修改歷史:2017/8/11 9:39*************************************
 **/
public class MemoryAllocateDemo {
    public static void main(String[] args){ //JVM自動尋找main方法
        /**
         * 執行第一句代碼,建立一個Test實例test,在棧中分配一塊內存,存放一個指向堆區實例對象的指針
         */
        Test test = new Test();
        
        /**
         * 執行第二句代碼,聲明定義一個int型變量(8種基本數據類型),在棧區直接分配一塊內存存儲這個變量的值
         */
        int date = 9;
        
        /**
         * 執行第三句代碼,建立一個BirthDate實例bd1,在棧中分配一塊內存,存放一個指向堆區實例對象的指針
         */
        BirthDate bd1 = new BirthDate(13,6,1991);
        
        /**
         * 執行第四句代碼,建立一個BirthDate實例bd2,在棧中分配一塊內存,存放一個指向堆區實例對象的指針
         */
        BirthDate bd2 = new BirthDate(30,4,1991);
        
        /**
         * 執行第五句代碼,方法test1入棧幀,執行完出棧
         */
        test.test1(date);
        
        /**
         * 執行第六句代碼,方法test2入棧幀,執行完出棧
         */
        test.test2(bd1);
        
        /**
         * 執行第七句代碼,方法test3入棧幀,執行完出棧
         */
        test.test3(bd2);

    }
}

演示過程一

1.JVM自動尋找main方法,執行第一句代碼,建立一個Test類的實例test,
  在棧中分配一塊內存,存放一個指向堆區對象的指針110925。

2.建立一個int型的變量date,因爲是基本類型,直接在棧中存放date對應的值9。

3.建立兩個BirthDate類的實例bd一、bd2,在棧中分別存放了對應的指針指向各自的對象
  ,他們在實例化時調用了有參數的構造方法,所以對象中有自定義初始值。

圖解以下:

內存分配調用演示一
內存分配調用演示一

演示過程二

1.test1方法入棧幀,以date爲參數

2.value爲局部變量,把value放在棧中,而且把date的值賦值給value

3.把123456賦值給value局部變量

4.test1方法執行完,value內存被釋放,test1方法出棧
內存分配調用演示二
內存分配調用演示二
內存分配調用演示二
內存分配調用演示二
內存分配調用演示二
內存分配調用演示二

演示過程三

1.test2方法入棧幀,以實例bd1爲參數

2.birthDate爲局部變量,把birthDate放在棧中,把bd1的引用的值賦值給birthDate,
  也就是bd1與birthDate的地址都是指向同一個堆區的實例
3.在堆區new了一個對象,而且把這個堆區的指針保存在棧區中birthDate對應的內存空
  間,這個時候,bd1與birthDate指向了不一樣的堆區,那麼birthDate的改變,並不會對
  bd1形成影響

4.test2方法執行完,棧中的birthDate空間被釋放,test2方法出棧,但堆區的內存空間
  則要等待系統自動回收
內存分配調用演示三
內存分配調用演示三
內存分配調用演示三
內存分配調用演示三
內存分配調用演示三
內存分配調用演示三

演示過程四

1.test3方法入棧幀,以實例bd2爲參數

2.birthDate爲局部變量,把birthDate放在棧中,把bd2的引用的值賦值給birthDate,
  也就是bd2與birthDate的地址都是指向同一個堆區的實例
3.調用birthDate的setDay方法,由於birthDate與bd2指向的是同一個對象,也就是bd2調用了setDay方法,因此,也會bd2形成影響

4.test3方法執行完,棧中的birthDate空間被釋放,test3方法出棧
內存分配調用演示四
內存分配調用演示四
內存分配調用演示四
內存分配調用演示四
內存分配調用演示四
內存分配調用演示四

3.JVM內存分配小結

跟着上面四個步驟,走一遍,會發現其實也不會那麼複雜,掌握思想就能摸到門路了,咱們平時注意區分一下基本數據類型的變量和引用數據類型變量,如下進行了幾點歸納:

1.局部變量中的基本數據類型的值直接存棧中

2.局部變量中的引用數據類型在棧中存的是引用類型的指針(地址)

3.棧中的數據與堆中的數據內存回收並非同步的,棧中的只要方法運行完,就會直接
  銷燬局部變量,但堆中的對象不必定當即銷燬
  
4.類的成員變量在不一樣對象中各不相同,都有本身的存儲空間(成員變量在堆中的對象中
  )。而類的方法倒是該類的全部對象共享的,只有一套,對象使用方法的時候方法才被
  壓入棧,方法不使用則不佔用內存

乾貨總結

終於把JVM內存分配的分享寫完了,一路寫下來,確實對內存分配又深刻了解了一次。期間參考瞭如下博客:

Java之美[從菜鳥到高手演變]之JVM內存管理及垃圾回收

Java 內存分配全面淺析

Jvm內存模型

經過對JVM內存模型的認識後,下一章將進行JVM垃圾回收機制的探索。

相關文章
相關標籤/搜索