Java內(nèi)存分配

在java語言中,可作為GCRoot的對象包括以下幾種:
- 虛擬機(jī)棧中引用的對象,主要是指棧幀中的本地變量
- 本地方法棧中Native方法引用的對象
- 方法區(qū)中類靜態(tài)屬性引用的對象
- 方法區(qū)中常量引用的對象
方法區(qū):
又叫
靜態(tài)區(qū),與Java堆一樣,是所有線程共享的內(nèi)存區(qū)域。?。。?!
方法區(qū)包含所有的class文件和static變量/方法?。?!方法區(qū)中包含的都是在
整個(gè)程序中永遠(yuǎn)唯一的元素,如class,static變量。用于存儲
已被虛擬機(jī)加載的
類信息、常量、靜態(tài)變量、即時(shí)編譯器編譯后的代碼/Java Class文件等數(shù)據(jù)。人們更愿意把這個(gè)區(qū)域稱為“
永久代”(Permanent Generation),
在發(fā)布的JDK1.7的HotSpot中,已經(jīng)把原本放在永久代的字符串常量池移出。
它還有個(gè)別名叫做Non-Heap(非堆)。除了和
Java堆一樣,
不需要連續(xù)的內(nèi)存和
可以選擇固定大小或可擴(kuò)展外,
還可選擇不實(shí)現(xiàn)GC。在
Java虛擬機(jī)規(guī)范中,
當(dāng)方法區(qū)無法滿足內(nèi)存分配需求時(shí),將拋出OutOfMemoryError異常。
棧
每個(gè)線程包含一個(gè)
棧區(qū),
棧中只保存基礎(chǔ)數(shù)據(jù)類型的對象引用及其對應(yīng)的值以及基礎(chǔ)數(shù)據(jù)的引用
(Java語言提供了八種基本數(shù)據(jù)類型:
六種數(shù)字類型(四個(gè)整數(shù)型long、int、short、byte,兩個(gè)浮點(diǎn)型float、double),
一種字符類型String,還有一種布爾型)每個(gè)棧中的數(shù)據(jù)(
基礎(chǔ)數(shù)據(jù)類型和對象引用)都是私有的,
其他棧不能訪問。棧分為3個(gè)部分:
基本類型變量區(qū)、執(zhí)行環(huán)境上下文、操作指令區(qū)(存放操作指令)。
虛擬機(jī)棧
每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)
棧幀,
用于存儲局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。每一個(gè)
方法從調(diào)用直至執(zhí)行完成的過程,
就對應(yīng)著一個(gè)棧幀在虛擬機(jī)棧中入棧到出棧的過程。
局部變量表存放了編譯期可知的
各種基本數(shù)據(jù)類型、對象引用類型和returnAddress類型,
它所需的內(nèi)存空間在編譯期間完成分配。
是
線程私有的內(nèi)存,與線程生命周期相同。!?。。?/strong>一般把Java內(nèi)存區(qū)分為
堆內(nèi)存(Heap)和棧內(nèi)存(Stack),
其中『棧』指的是虛擬機(jī)棧,『堆』指的是Java堆。-
在
Java虛擬機(jī)規(guī)范中,對這個(gè)區(qū)域規(guī)定了兩種異常狀況:如果
線程請求的棧深度大于虛擬機(jī)所允許的深度,
將拋出StackOverflowError異常;如果虛擬機(jī)棧
可動(dòng)態(tài)擴(kuò)展且擴(kuò)展時(shí)無法申請到足夠的內(nèi)存,
將拋出OutOfMemoryError異常。
本地方法棧
存儲局部變量表、操作數(shù)棧等;
是虛擬機(jī)使用到的
Native方法服務(wù)。
在虛擬機(jī)規(guī)范中,對這個(gè)區(qū)域無強(qiáng)制規(guī)定,由具體的虛擬機(jī)自由實(shí)現(xiàn)。
與虛擬機(jī)棧一樣,
本地方法棧區(qū)域也會(huì)拋出StackOverflowError和OutOfMemoryError異常。虛擬機(jī)棧是為Java方法服務(wù)的;
本地方法棧是為Native方法服務(wù)的;
-
當(dāng)然還要注意String的特殊性
-
一個(gè)例子:
-
還有一例:
-
一個(gè)例子:
堆
存儲的全部是對象,
每個(gè)對象都包含一個(gè)與之對應(yīng)的class的信息。
(class的目的是得到操作指令)jvm只有一個(gè)堆區(qū)(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放
對象本身被所有線程共享的一塊內(nèi)存區(qū)域,在虛擬機(jī)啟動(dòng)時(shí)創(chuàng)建;
包含一切new出來的對象;每一個(gè)對象的
實(shí)際分配內(nèi)存都是在堆上進(jìn)行分配的;
用于存放幾乎所有的對象實(shí)例和數(shù)組。
在Java堆中,
可能劃分出多個(gè)線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer,TLAB),
但無論哪個(gè)區(qū)域,存儲的都仍然是對象實(shí)例,
進(jìn)一步劃分的目的是
為了更好地回收內(nèi)存,
或者更快地分配內(nèi)存。
在
虛擬機(jī)棧中,分配的只是引用,
虛擬機(jī)棧當(dāng)中的引用,會(huì)指向在堆中真正創(chuàng)建的對象;是
GC主要作用、管理的區(qū)域,因?yàn)樗純?nèi)存最大,最有可能產(chǎn)生垃圾,也被稱做“GC堆”;
經(jīng)常說的內(nèi)存泄漏也是發(fā)生在此區(qū)域;是Java虛擬機(jī)所管理的
內(nèi)存中最大的一塊。可處于
物理上不連續(xù)的內(nèi)存空間中,只要邏輯上是連續(xù)的即可。在
Java虛擬機(jī)規(guī)范中,
如果在堆中沒有內(nèi)存完成實(shí)例分配,且堆也無法再擴(kuò)展時(shí),
將會(huì)拋出OutOfMemoryError異常。
程序計(jì)數(shù)器(Program Counter Register)
-
是
當(dāng)前線程所執(zhí)行的字節(jié)碼的行號指示器。如果
線程正在執(zhí)行的是一個(gè)Java方法,
那么計(jì)數(shù)器記錄的是
正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址;如果
線程正在執(zhí)行的是一個(gè)Native方法,
那么計(jì)數(shù)器的值則為空。
**注意:?。。。。。。?/p>
計(jì)數(shù)器的值代表著下一條需要執(zhí)行的字節(jié)碼指令,!??!
字節(jié)碼解釋器工作時(shí),
就是通過改變這個(gè)計(jì)數(shù)器的值來選取下一條需要執(zhí)行的字節(jié)碼指令,?。。?!
分支、循環(huán)、跳轉(zhuǎn)、異常處理、線程恢復(fù)等基礎(chǔ)功能
都需要依賴這個(gè)計(jì)數(shù)器來完成。**
為了
線程切換后能恢復(fù)到正確的執(zhí)行位置,
每條線程都需要有一個(gè)獨(dú)立的程序計(jì)數(shù)器,
各條線程之間計(jì)數(shù)器互不影響,獨(dú)立存儲,
因此它是線程私有的內(nèi)存。?。。。。。?!在
Java虛擬機(jī)規(guī)范中,
是唯一一個(gè)沒有規(guī)定任何OutOfMemoryError情況的區(qū)域。
JVM垃圾回收算法
回收算法有以下四種:
分代收集算法(1):是當(dāng)前商業(yè)虛擬機(jī)都采用的一種算法,根據(jù)對象存活周期的不同,將Java堆劃分為新生代和老年代,并根據(jù)各個(gè)年代的特點(diǎn)采用最適當(dāng)?shù)氖占惴ā?br>
新生代:大批對象死去,只有少量存活。使用『復(fù)制算法』,只需復(fù)制少量存活對象即可。
復(fù)制算法(2):把可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。
當(dāng)這一塊的內(nèi)存用盡后,把還存活著的對象『復(fù)制』到另外一塊上面,
再將這一塊內(nèi)存空間一次清理掉。
老年代:對象存活率高。使用『標(biāo)記—清理算法』或者『標(biāo)記—整理算法』,只需標(biāo)記較少的回收對象即可。
標(biāo)記-清除算法(3):首先『標(biāo)記』出所有需要回收的對象,然后統(tǒng)一『清除』所有被標(biāo)記的
標(biāo)記-整理算法(4):首先『標(biāo)記』出所有需要回收的對象,然后進(jìn)行『整理』,使得存活的對象都向一端移動(dòng),最后直接清理掉端邊界以外的內(nèi)存。
-
標(biāo)記-清除算法效率其實(shí)不高,
它需要從頭到尾對內(nèi)存中的每一個(gè)對象做標(biāo)記;
并且會(huì)產(chǎn)生大量的不連續(xù)的內(nèi)存碎片;如上的第四行內(nèi)存,可能兩塊藍(lán)色之間的那一塊內(nèi)存都是用不了的,
只能用后面的三塊來分配,
即前面出現(xiàn)了內(nèi)存空洞; -
復(fù)制算法的相較于標(biāo)記-清除算法,效率是高一點(diǎn)的,
每一次只需對二分之一的內(nèi)存進(jìn)行標(biāo)記,
同時(shí)避免內(nèi)存空洞;
但是浪費(fèi)了一半空間,代價(jià)大; -
標(biāo)記-整理算法
避免標(biāo)記-清理導(dǎo)致的內(nèi)存碎片(及內(nèi)存空洞);
避免復(fù)制算法的空間浪費(fèi);
Android內(nèi)存管理機(jī)制
內(nèi)存(按需)彈性分配
分配值與最大值受具體設(shè)備影響;
不同配置的手機(jī),其單個(gè)APP可以使用的內(nèi)存是不同的;
比如多者有單個(gè)APP可以使用512M的內(nèi)存的,少者128M甚至更甚;
OOM場景:
OOM有時(shí)候是APP自己的原因,有時(shí)候也可能是整個(gè)系統(tǒng)的原因;
APP使用內(nèi)存真正不足,超限:
比如某一個(gè)手機(jī),其單個(gè)APP 最大可以使用的內(nèi)存 是512M,
假設(shè)有一個(gè)APP 已經(jīng)使用了510M了,這時(shí)候如果還要再申請一個(gè)3M的空間,
這時(shí)候內(nèi)存是真正不足了,超過了最大限制,要拋出OOM內(nèi)存溢出異常;系統(tǒng)可用內(nèi)存不足:
就是,
即使 APP使用的內(nèi)存 沒有超過 系統(tǒng)規(guī)定的最大限制,
但是整個(gè)系統(tǒng)的內(nèi)存已經(jīng)不夠用了,AMS回收了別的進(jìn)程 也不夠分了,
沒辦法多分配給APP內(nèi)存了,
這時(shí)候也會(huì)拋出OOM 內(nèi)存溢出異常;
如某一個(gè)手機(jī),其單個(gè)APP 最大可以使用的內(nèi)存 是512M,
一個(gè)APP只用了200M,再要申請一個(gè)幾十M的內(nèi)存時(shí),
系統(tǒng)也拋出OOM內(nèi)存溢出異常;
Dalvik 和 ART的區(qū)別(關(guān)注點(diǎn):程序運(yùn)行時(shí)、GC算法)
參考鏈接:
Android 4.4之前,Android系統(tǒng)一直都是在Dalvik 虛擬機(jī)上的,
從Android 4.4開始開始引入ART,到5.0已經(jīng)成為默認(rèn)選擇。
-
Dalvik僅固定一種回收算法,?。。?!
手機(jī)出廠之前已經(jīng)設(shè)定好了,運(yùn)行期間無法改變;
另外,
應(yīng)用程序每次運(yùn)行時(shí),?。。?!
都需要將程序內(nèi)的代碼即使轉(zhuǎn)變?yōu)闄C(jī)器碼才能運(yùn)行,這無形中多附加了一道手續(xù),
這就造成了耗電相對較快、占用內(nèi)存大、即使是旗艦機(jī)用久了也會(huì)卡頓嚴(yán)重的現(xiàn)象。
ART,Android Runtime 的簡稱。
優(yōu)點(diǎn):
- 通過在安裝應(yīng)用程序時(shí),自動(dòng)對程序進(jìn)行代碼預(yù)讀取編譯,
讓程序直接編譯成機(jī)器語言,運(yùn)行時(shí)直接運(yùn)行 無需再做轉(zhuǎn)化,?。。?!
免去了Dalvik模式運(yùn)行時(shí)要時(shí)時(shí)轉(zhuǎn)換代碼,- 實(shí)現(xiàn)高效率、省電、占用更低的系統(tǒng)內(nèi)存、手機(jī)運(yùn)行流暢。
缺點(diǎn):
- 占用略高一些的存儲空間;
- 安裝程序時(shí)要相比普通 Dalvik 模式要長一些時(shí)間來實(shí)現(xiàn)預(yù)編譯;
- Android5.0之后都是默認(rèn)使用
ART虛擬機(jī),
其回收算法,是可以在APP運(yùn)行期間進(jìn)行選擇的,?。。?!
可以在不同的情況下,選擇合適的垃圾回收算法;
如果,
APP正跑在前臺,和用戶正在交互,
此時(shí)此景,自然響應(yīng)速度最重要!
對于用戶來說,需要APP能夠及時(shí)響應(yīng),
此時(shí)應(yīng)該選擇一種簡單的算法——標(biāo)記-清除算法;
如果,
APP切到了后臺,
則可以選擇標(biāo)記-整理算法,作為補(bǔ)充;
(也就是說,ART 相對于 Dalvik 而言,
具備內(nèi)存整理能力,減少內(nèi)存空洞)
Low Memory Killer 機(jī)制
機(jī)制目的:保證大多數(shù)情況下,不會(huì)出現(xiàn)內(nèi)存不足的情境;
針對所有進(jìn)程;
當(dāng)手機(jī)內(nèi)存不足,Low Memory Killer 機(jī)制就會(huì) 針對所有進(jìn)程 進(jìn)行回收;
進(jìn)程分類:
Android系統(tǒng)將進(jìn)程分為以下幾類:
(進(jìn)程優(yōu)秀級從前往后,從高到低)
前臺進(jìn)程,可見進(jìn)程,服務(wù)進(jìn)程,后臺進(jìn)程,空進(jìn)程;
(Foreground進(jìn)程、Visible進(jìn)程、Service進(jìn)程、Background進(jìn)程、Empty進(jìn)程)
如果用戶按Home鍵返回桌面,那么該app成為Background進(jìn)程;
如果按Back返回,則成為Empty進(jìn)程。
RAM(內(nèi)存)不足時(shí),
Low Memory Killer 會(huì)找優(yōu)先級低的進(jìn)程,優(yōu)先進(jìn)行回收,
殺死優(yōu)先級較低的進(jìn)程,讓高優(yōu)先級進(jìn)程獲取更多內(nèi)存;
同時(shí)還會(huì)考慮一個(gè)因素——回收收益,
即 回收 某一個(gè)進(jìn)程 能 收回 多大的內(nèi)存;ActivityManagerService直接管理所有進(jìn)程的內(nèi)存資源分配。
所有進(jìn)程要申請或釋放內(nèi)存都需要通過ActivityManagerService對象。垃圾回收不定期執(zhí)行。
當(dāng)內(nèi)存不夠時(shí)就會(huì)遍歷heap空間,把垃圾對象刪除。堆內(nèi)存越大,則GC的時(shí)間更長。





