android 記憶體分哪些區

來源:互聯網
上載者:User

標籤:ant   初學   靜態   封裝   強制   private   大小   二進位   zll   

韓夢飛沙 yue31313 韓亞飛 han_meng_fei_sha [email protected]

android 記憶體分哪些區

記憶體分哪些區

============

記憶體分為的5大區 1、棧區(stack)— 由編譯器自動分配釋放 ,存放函數的參數值,局部變數的值等。其操作方式類似於資料結構中的棧。2、堆區(heap) — 一般由程式員分配釋放, 若程式員不釋放,程式結束時可能由OS回收 。注意它與資料結構中的堆是兩回事,分配方式倒是類似於鏈表。3、全域區(靜態區)(static storage)—,全域變數和靜態變數的儲存是放在一塊的,初始化的全域變數和靜態變數在一塊地區, 未初始化的全域變數和未初始化的靜態變數在相鄰的另 一塊地區。 - 程式結束後由系統釋放。4、文字常量區(constant storage) —常量字串就是放在這裡的。常量儲存位於唯讀記憶體ROM中程式結束後由系統釋放 。5、程式碼區—存放函數體的二進位代碼。 例子程式:這是一個前輩寫的,非常詳細int a = 0; 全域初始化區char * p1; 全域未初始化區main() {int b; 棧 char s[] = "abc"; 棧 char *p2; 棧 char *p3 = "123456"; 123456\0在常量區,p3在棧上。 static int c =0; 全域(靜態)初始化區 p1 = (char *)malloc(10); p2 = (char *)malloc(20); // 分配得來得10和20位元組的地區就在堆區。 strcpy(p1, "123456"); //123456\0放在常量區,編譯器可能會將它與p3所指向的"123456" 最佳化成一個地方。 }
三、 堆和棧的區別從定義上看,最明顯的區別是手動分配和自動分配的策略不同。用如下的比喻來看:使用棧就象我們去飯館裡吃飯,只管點菜(發出申請)、吃(使用),吃飽了就 走,不必理會切菜、洗菜等準備工作和洗碗、刷鍋等掃尾工作,他的好 處是快捷,但是自 由度小。 使用堆就象是自己動手做喜歡吃的菜肴,比較麻煩,但是比較符合自己的口味,而且自由 度大。 棧與堆的區別:在方法體內定義的(局部變數)一些基本類型的變數和對象的引用變數都是在方法的棧記憶體中分配的。當在一段方法塊中定義一個變數時,Java 就會在棧中為該變數分配記憶體空間,當超過該變數的範圍後,該變數也就無效了,分配給它的記憶體空間也將被釋放掉,該記憶體空間可以被重新使用。堆記憶體用來存放所有由 new 建立的對象(包括該對象其中的所有成員變數)和數組。在堆中分配的記憶體,將由 Java 記憶體回收行程來自動管理。在堆中產生了一個數組或者對象後,還可以在棧中定義一個特殊的變數,這個變數的取值等於數組或者對象在堆記憶體中的首地址,這個特殊的變數就是我們上面說的引用變數。我們可以通過這個引用變數來訪問堆中的對象或者數組。  Android記憶體機制,瞭解Android堆和棧1、dalvik的Heap和Stack

這裡說的只是dalvik java部分的記憶體,實際上除了dalvik部分,還有native。

    下面針對上面列出的資料類型進行說明,只有瞭解了我們申請的資料在哪裡,才能更好掌控我們自己的程式。  2、對象執行個體資料

實際上是儲存對象執行個體的屬性,屬性的類型和對象本身的類型標記等,但是不儲存執行個體的方法。執行個體的方法是屬於資料指令,是儲存在Stack裡面,也就是上面表格裡面的類方法。

對象執行個體在Heap中分配好以後,會在stack中儲存一個4位元組的Heap記憶體位址,用來尋找對象的執行個體。因為在Stack裡面會用到Heap的執行個體,特別是調用執行個體的時候需要傳入一個this指標。

3、方法內部變數

  類方法的內部變數分為兩種情況:簡單類型儲存在Stack中;物件類型在Stack中儲存地址,在Heap 中儲存值。

4、非靜態方法和靜態方法

  非靜態方法有一個隱含的傳入參數,這個參數是dalvik虛擬機器傳進去的,這個隱含參數就是對象執行個體在Stack中的地址指標。因此非靜態方法(在Stack中的指令代碼)總是可以找到自己的專用資料(在Heap 中的對象屬性值)。

  當然非靜態方法也必須獲得該隱含參數,因此非靜態方法在調用前,必須先new一個對象執行個體,獲得Stack中的地址指標,否則dalvik虛擬機器將無法將隱含參數傳給非靜態方法。

  靜態方法沒有隱含參數,因此也不需要new對象,只要class檔案被ClassLoader load進入JVM的Stack,該靜態方法即可被調用。所以我們可以直接使用類名調用類的方法。當然此時靜態方法是存取不到Heap 中的對象屬性的。

5、靜態屬性和動態屬性

  靜態屬性是儲存在Stack中的,而不同於動態屬性儲存在Heap 中。正因為都是在Stack中,而Stack中指令和資料都是定長的,因此很容易算出位移量,所以類方法(靜態和非靜態)都可以訪問到類的靜態屬性。也正因為靜態屬性被儲存在Stack中,所以具有了全域屬性。

6、小結

  Java的堆是一個運行時資料區,類的(對象從中分配空間。這些對象通過new、newarray、anewarray和multianewarray等指令建立,它們不需要程式碼來顯式的釋放。

  堆是由記憶體回收來負責的,堆的優勢是可以動態地分配記憶體大小,生存期也不必事先告訴編譯器,因為它是在運行時動態分配記憶體的,Java的垃圾收集器會自動收走這些不再使用的資料。但缺點是,由於要在運行時動態分配記憶體,存取速度較慢。

  棧的優勢是,存取速度比堆要快,僅次於寄存器,棧資料可以共用。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。棧中主要存放一些基本類型的變數(,int, short, long, byte, float, double, boolean, char)和物件控點。

  對比上面的解析可以看出,其實Java處理Heap和Stack的大致原理跟C++是一樣的。只是多了一個記憶體回收機制,讓程式員不用主動調用delete釋放記憶體。就像在C++裡面,一般使用new申請的記憶體才會放到堆裡面,而一般的臨時變數都是放到棧裡面去。

7、APP預設分配記憶體大小

  在Android裡,程式記憶體被分為2部分:native和dalvik,dalvik就是我們普通的java使用記憶體,也就是剛分析堆棧的時候使用的記憶體。

  我們建立的對象是在這裡面分配的,對於記憶體的限制是 native+dalvik 不能超過最大限制。

  android程式記憶體一般限制在16M,也有的是24M(早期的Android系統G1,就是只有16M)。具體看定製系統的設定,在Linux初始化代碼裡面Init.c,可以查到到預設的記憶體大小。有興趣的朋友,可以分析一下虛擬機器啟動相關代碼。

  1. gDvm.heapSizeStart =2*1024*1024;// heap初始化大小為2MgDvm.heapSizeMax =16*1024*1024;// 最大的heap為16M
8、Android的GC如何回收記憶體

  Android的一個應用程式的記憶體泄露對別的應用程式影響不大。為了能夠使得Android應用程式安全且快速的運行,Android的每個應用程式都會使用一個專有的Dalvik虛擬機器執行個體來運行,它是由Zygote服務進程孵化出來的,也就是說每個應用程式都是在屬於自己的進程中啟動並執行。

  Android為不同類型的進程分配了不同的記憶體使用量上限,如果程式在運行過程中出現了記憶體流失的而造成應用進程使用的記憶體超過了這個上限,則會被系統視為記憶體流失,從而被kill掉,這使得僅僅自己的進程被kill掉,而不會影響其他進程(如果是system_process等系統進程出問題的話,則會引起系統重啟)。

  做應用開發的時候,你需要瞭解系統的GC(記憶體回收)機制是如何啟動並執行,Android裡面使用有向圖作為遍曆回收記憶體的機制。

  Java將參考關聯性考慮為圖的有向邊,有向邊從引用者指向引用對象。線程對象可以作為有向圖的起始頂點,就是從起始頂點開始的一棵樹,根頂點可以到達的對象都是有效對象,GC不會回收這些對象。如果某個對象 (連通子圖)與這個根頂點不可達(注意,該圖為有向圖),那麼我們認為這個(這些)對象不再被引用,可以被GC回收。

  因此對於我們已經不需要使用的對象,我們可以把它設定為null,這樣當GC啟動並執行時候,就好遍曆到你這個對象已經沒有引用,會自動把該對象佔用的記憶體回收。我們沒法像C++那樣馬上釋放不需要的記憶體,但是我們可以主動告訴系統,哪些記憶體可以回收了。

9、查看應用記憶體使用量情況

  下面我們看看如何在開發過程中查看我們程式運行時記憶體使用量情況。我們可以通過ADB的一個命令查看:

  1. //$package_name:應用程式套件名//$pid:應用進程ID,可以用PS命令查看adb shell dumpsys meminfo $package_name or $pid
    上面是我使用包名查看的記憶體使用量情況圖,裡面資訊很多,不過我們主要關注的是native和Davilk的使用方式。  Android底層核心是基於Linux的,而Linux裡面相對Window來說,有一點很特別的是,會盡量使用系統記憶體載入一些快取資料或者進程間共用資料。  Linux本著不用白不用的原則,會盡量使用系統記憶體,加快我們應用的運行速度。當然,如果我們期待某個需要大記憶體的應用,系統也能馬上釋放出一定的記憶體使用量,這是系統內部調度實現。因此嚴格來說,我們要準備計算Linux下某個進程記憶體大小比較困難。 因為有paging out to disk(換頁),所以如果你把所有映射到進程的記憶體相加,它可能大於你的記憶體的實際物理大小。
  • dalvik:是指dalvik所使用的記憶體。
  • native:是被native堆使用的記憶體。應該指使用C\C++在堆上分配的記憶體。
  • other:是指除dalvik和native使用的記憶體。但是具體是指什麼呢?至少包括在C\C++分配的非堆記憶體,比如分配在棧上的記憶體。puzlle!
  • Pss:它是把共用記憶體根據一定比例分攤到共用它的各個進程來計算所得到進程使用記憶體。網上又說是比例分配共用庫佔用的記憶體,也就是上面所說的進程共用問題。
  • PrivateDirty:它是指非共用的,又不能換頁出去(can not be paged to disk )的記憶體的大小。比如Linux為了提高分配記憶體速度而緩衝的小對象,即使你的進程結束,該記憶體也不會釋放掉,它只是又重新回到緩衝中而已。
  • SharedDirty:參照PrivateDirty我認為它應該是指共用的,又不能換頁出去(can not be paged to disk )的記憶體的大小。比如Linux為了提高分配記憶體速度而緩衝的小對象,即使所有共用它的進程結束,該記憶體也不會釋放掉,它只是又重新回到緩衝中而已。
10、程式中擷取記憶體資訊

通過ActivityManager擷取相關資訊,下面是一個例子代碼:

  1. 1 privatevoid displayBriefMemory()2 {3     finalActivityManager activityManager =(ActivityManager) getSystemService(ACTIVITY_SERVICE);4     ActivityManager.MemoryInfo info =newActivityManager.MemoryInfo();5     activityManager.getMemoryInfo(info);6     Log.i(tag,"系統剩餘記憶體:"+(info.availMem >>10)+"k");7     Log.i(tag,"系統是否處於低記憶體運行:"+info.lowMemory);8     Log.i(tag,"當系統剩餘記憶體低於"+info.threshold+"時就看成低記憶體運行");9 }

    1 privatevoid displayBriefMemory()2 {3     finalActivityManager activityManager =(ActivityManager) getSystemService(ACTIVITY_SERVICE);4     ActivityManager.MemoryInfo info =newActivityManager.MemoryInfo();5     activityManager.getMemoryInfo(info);6     Log.i(tag,"系統剩餘記憶體:"+(info.availMem >>10)+"k");7     Log.i(tag,"系統是否處於低記憶體運行:"+info.lowMemory);8     Log.i(tag,"當系統剩餘記憶體低於"+info.threshold+"時就看成低記憶體運行");9 }
  另外通過Debug的getMemoryInfo(Debug.MemoryInfo memoryInfo)可以得到更加詳細的資訊。跟我們在ADB Shell看到的資訊一樣比較詳細。 ====記憶體最佳化方式 善用WeakReference
Exception裡面要釋放資源
介面不可見時,停止所有動畫和相關線程
少用幀間動畫,真要用的時候,用SurfaceView做
非同步任務隊列一定要用有界隊列
讀SqlLite的時候請盡量不要在UI線程,雖然你這樣做也不會Exception
善用LeakCanary
=======jvm中堆棧以及記憶體地區分配堆棧這個概念存在於資料結構中,也存在於jvm虛擬機器中,在這兩個環境中是截然不同的意思。 
在資料結構中,堆棧是:堆 和棧兩種資料結構,堆是完全二叉樹,堆中各元素是有序的。在這個二叉樹中所有的雙親節點和孩子節點存在著大小關係,如所有的雙親節點都大於孩子節點則 為大頭堆,如果所有的雙親節點都小於其孩子節點說明這是一個小頭堆,建堆的過程就是一個排序的過程,堆得查詢效率也很高。棧是一種先進後出的線性表。 
在jvm虛擬機器中得堆棧對應記憶體的不同地區,和資料結構中所說的堆棧是兩碼事。  JVM的體繫結構包含幾個主要的子系統和記憶體區: 

類裝載子系統 ,負責把類從檔案系統中裝入記憶體 

GC子系統 ,垃圾收集器的主要工作室自動回收不再啟動並執行程式引用對象所佔用的記憶體,此外,它還可能負責那些還在使用的對象,以減少的堆片段。 

記憶體區 ,用於儲存位元組碼,程式運行時建立的對象,傳遞給方法的參數,傳回值,局部變數和中間計算結果。 ----1. 棧(stack)與堆(heap)都是Java用來在Ram中存放資料的地方 。與C++不同,Java自動管理棧和堆,程式員不能直接地設定棧或堆。 

2. 棧的優勢是,存取速度比堆要快 ,僅次於直接位於CPU中的寄存器。但缺點是,存在棧中的資料大小與生存期必須是確定的,缺乏靈活性。另外,棧資料可以共用,詳見第3點。堆的優勢是可以動態地分配記憶體大小,生存期也不必事先告訴編譯器,Java的垃圾收集器會自動收走這些不再使用的資料。但缺點是,由於要在運行時動態分配記憶體,存取速度較慢。 ========Java程式 運行時資料區域

 

 Java虛擬機器在執行java程式的過程中,會把它所管理的記憶體劃分成若干個不同的資料區域(每當運行一個java程式都會啟動一個虛擬機器)

  

其中方法區和堆是由所有線程共用的,例如使用ThreadPoolExecutor建立多個線程時,堆與方法區都可以被多個線程讀取。

程式計數器 學過電腦群組成原理的人都會知道在CPU的寄存器中有一個PC寄存器,存放下一條指令地址,這裡,虛擬機器不使用CPU的程式計數器,自己在記憶體中設立一片地區來類比CPU的程式計數器。只有一個程式計數器是不夠的,當多個線程切換執行時,那就單個程式計數器就沒辦法了,虛擬機器規範中指出,每一條線程都有一個獨立的程式計數器。注意,Java虛擬機器中的程式計數器指向正在執行的位元組碼地址,而不是下一條。

虛擬機器棧 是線程私人的,它的生命週期與線程相同。虛擬機器棧描述的是Java方法執行的記憶體模型:每個方法執行的時候都會建立一個棧幀(我覺得可以把它看作是一個快照,記錄下進入方法前的一些參數,實際上是方法運行時的基礎資料結構),用於存放局部變數表,運算元棧,動態連結,方法出口等資訊。每一個方法從調用直到執行完成的過程都對應著一個棧幀在虛擬機器中的入棧到出棧的過程。我們平時把記憶體分為堆記憶體和棧記憶體,其中的棧記憶體就指的是虛擬機器棧的局部變數表部分。局部變數表存放了編譯期可以知道的基礎資料型別 (Elementary Data Type),對象引用,和返回後所指向的位元組碼的地址。

本地方法區 與 虛擬機器棧 所發揮的作用很類似,但是要注意一下,虛擬機器規範中沒有對本地方法區中的方法作強制規定,虛擬機器可以自由實現,即可以不是位元組碼。但是也可以是位元組碼,這樣虛擬機器棧和本地方法區就可以合二為一,事實上,OpenJDKSunJDK所內建的HotSpot虛擬機器就直接將虛擬機器棧和本地方法區合二為一。

 這個概念應該很多人都很熟悉,例如初學C語言的時候,老師就會講malloc方法會在堆中分配空間,這裡也一樣。這個地區是用來存放對象執行個體的,幾乎所有對象執行個體都會在這裡分配記憶體,虛擬機器規範中講:所有對象的執行個體以及數組都要在堆上分配。但是隨著JIT(Just-in-time) 編譯期的發展,有些時候也有可能在棧上分配(這裡我也不是很明白其中的道理)。堆是java垃圾收集器管理的主要區域(很多時候會稱為GC堆,不叫垃圾堆),垃圾收集器實現了對象的自動銷毀。

方法區 也是各個線程共用的地區,它用於儲存已經被虛擬機器載入過的類資訊,常量,靜態變數,及時編譯期編譯後的代碼(類方法)等資料。這裡要講一下運行時常量池,它是方法區的一部分,用於存放編譯期產生的各種字面量和符號引用(其實就是八大基本類型的封裝類型和String類型資料)。

 

對象的建立

對於物件導向的一門語言,我們無時不在通過new關鍵字建立對象,那麼這個過程又是怎樣的呢?

當虛擬機器遇到一條new指令的時候,首先會去檢查所new的類是否已經被載入,在哪裡檢查?當然在方法區,方法區存放了載入過的類資訊。如果沒有載入,那麼先執行類的載入。

通過類載入檢查後,虛擬機器開始為新生對象分配記憶體,對象所需要的記憶體大小在類載入完成後已經可以確定,這時候只要在堆中分配空間即可。分配記憶體有兩種方式,第一種,我們假設記憶體絕對規整,那麼只要在用過的記憶體和沒用過的記憶體間放置一個指標即可,每次分配空間的時候只要把指標向空閑空間移動相應距離即可。第二種,我們假設空閑記憶體和非空閑記憶體夾雜在一起,實際上就是這種情況,那麼就需要一個列表,去記錄堆記憶體的使用方式,作業系統對記憶體的管理就是這樣的。

那麼,我們還要考慮一個問題,即在多線程的情況下,只有一個指標怎麼能確保一個線程分配了記憶體指標沒修改的時候另一個線程又分配記憶體不會覆蓋之前的記憶體呢?這裡有一種方法,讓每一個線程在堆中先預分配一小塊記憶體(TLAB本地線程分配緩衝),每個線程只在自己的記憶體中分配記憶體。

最後,對象被成功分配記憶體。我們知道通過一個對象,我們可以通過getClass()方法擷取類,預設比較兩個對象實際比較的是對象記憶體的雜湊值,這又是怎麼實現的呢?其實在分配完記憶體後,虛擬機器會對對象進行必要的設定,對象的類,對象的雜湊碼等資訊都存放在對象的對象頭中,所以分配的記憶體大小絕不止屬性的總和。

 

對象的記憶體布局

對象在堆中的布局分為三個地區:對象頭執行個體資料對齊填充

  • 對象頭 包括兩個部分,第一部分用於儲存自身運行時的資料例如GC標誌位,MonirGC次數,雜湊碼,鎖狀態,哪個線程可以擁有等被稱為MarkWord(標記字)。第二部分存放指向方法區類資料的指標。在32位系統中,class指標大小為4位元組,標記字大小為4位元組。在64位系統中標記字大小為8位元組。

  • 執行個體資料 存放類的屬性資訊,包括父類的屬性資訊。數組的執行個體部分還包括數組的長度。執行個體資訊按類分別4位元組對齊。

  • 對齊填充 這是虛擬機器要求對象起始地址必須是8位元組的整數倍,可以說對齊填充沒有什麼特別的含義。

對象的訪問定位

我們知道,引用是引用,對象執行個體是對象執行個體。引用存放在虛擬機器棧中,資料類型為reference,對象執行個體存放在堆中。那麼引用是如何指向對象執行個體的呢?

主流的訪問方式有兩種,第一種是通過控制代碼池,如果使用控制代碼池,那麼java堆中將會劃分出一部分記憶體作為控制代碼池,控制代碼包含物件類型指標指向方法區的類型資訊,還有對象執行個體指標,指向堆中的執行個體地址。

第二種是reference引用直接指向堆中的對象執行個體,對象執行個體的對象頭存放物件類型指標。

兩種方法各有優勢,第一種可以在對象執行個體在GC時移動的時候只改變控制代碼池中的對象執行個體指標,而不用改變reference引用本身。第二種方法就是訪問速度快,減少了一次指標定位的時間開銷。目前HotSpot虛擬機器就採用的第二種方式。

=====

  • java中的記憶體被分成以下四部分:

       ①、代碼區  ②、棧區  ③、堆區   ④、靜態地區

  • 棧區:由編譯器自動分配釋放,存放函數的參數值、局部變數的值等;具體方法執行結束後,系統自動釋放JVM記憶體資源
  • 堆區:一般由程式員分配釋放,存放new分配的對象和數組,JVM不定時查看這個對象,如果沒有引用指向這個對象就回收
  • 靜態區:存放全域變數、靜態變數和字串常量,不釋放
  • 代碼區:存放程式中方法的二進位代碼,而且是多個對象共用一個代碼空間地區。

==========

 

 

android 記憶體分哪些區

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.