標籤:raw 生命週期 記憶體配置 ouya net roi 線程的生命週期 image 佔用
開篇廢話
今天我們一起來學習JVM的記憶體配置,主要目的是為我們Android記憶體最佳化打下基礎。
一直在想以什麼樣的方式來呈現這個知識點才能讓我們易於理解,最終決定使用方法為:圖解+原始碼分析。
歡迎訪問我的個人部落格: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虛擬機器棧
這個地區也是我們平時口中說的堆棧的棧,關於這個塊地區有如下要點:
1.屬於線程私人,與線程的生命週期相同2.每一個java方法被執行的時候,這個地區會產生一個棧幀4.棧幀中存放的局部變數有8種基礎資料型別 (Elementary Data Type),以及參考型別(對象的記憶體位址)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種基礎資料型別 (Elementary Data Type)),在棧區直接分配一塊記憶體儲存這個變數的值 */ 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類的執行個體bd1、bd2,在棧中分別存放了對應的指標指向各自的對象 ,他們在執行個體化時調用了有參數的構造方法,因此對象中有自訂初始值。
圖解如下:
記憶體配置調用示範一示範過程二
1.test1方法入棧幀,以date為參數2.value為局部變數,把value放在棧中,並且把date的值賦值給value3.把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記憶體配置小結
跟著上面四個步驟,走一遍,會發現其實也不會那麼複雜,掌握思想就能摸到門路了,我們平時注意區分一下基礎資料型別 (Elementary Data Type)的變數和引用資料類型變數,以下進行了幾點概括:
1.局部變數中的基礎資料型別 (Elementary Data Type)的值直接存棧中2.局部變數中的引用資料類型在棧中存的是參考型別的指標(地址)3.棧中的資料與堆中的資料記憶體回收並不是同步的,棧中的只要方法運行完,就會直接 銷毀局部變數,但堆中的對象不一定立即銷毀 4.類的成員變數在不同對象中各不相同,都有自己的儲存空間(成員變數在堆中的對象中 )。而類的方法卻是該類的所有對象共用的,只有一套,對象使用方法的時候方法才被 壓入棧,方法不使用則不佔用記憶體
乾貨總結
終於把JVM記憶體配置的分享寫完了,一路寫下來,確實對記憶體配置又深入瞭解了一次。期間參考了以下部落格:
Java之美[從菜鳥到高手演變]之JVM記憶體管理及記憶體回收
Java 記憶體配置全面淺析
Jvm記憶體模型
通過對JVM記憶體模型的認識後,下一章將進行JVM記憶體回收機制的探索。
Android記憶體最佳化1 瞭解java記憶體配置 1