這些是你需要知道的Android內(nèi)存基礎(chǔ)

背景介紹

  • Java優(yōu)勢之一就是其具有垃圾回收機(jī)制不撑。在大部分情況下廷粒,JVM的GC(垃圾回收器)能夠幫助我們回那些不可到達(dá)的對象(就是未被引用的對象)祖搓。
  • 當(dāng)然底桂,在一些情況下,我們?nèi)匀恍枰约喝メ尫艃?nèi)存(就是把對象引用置null筋遭,把容器打颤、數(shù)組清空),否則就會引起內(nèi)存泄漏漓滔,內(nèi)存泄漏嚴(yán)重時將容易引發(fā)OutOfMemoryError编饺,詳情見內(nèi)存泄漏
  • 此外响驴,由于GC會停止所有的線程透且,包括UI線程,所以頻繁的GC必然會導(dǎo)致畫面卡頓(Android中每16ms為一幀)豁鲤,因此還應(yīng)避免GC的頻繁發(fā)生秽誊。一個導(dǎo)致GC頻繁發(fā)生的原因就是內(nèi)存抖動,點擊鏈接看詳情琳骡。
    GC時線程被停止示意圖
  • 所以锅论,理解Java的內(nèi)存機(jī)制,有助于幫助我們在寫代碼的過程中避免內(nèi)存泄漏楣号。

走進(jìn)內(nèi)存模型

Java內(nèi)存層級

JVM運(yùn)行時內(nèi)存劃分

;

JVM運(yùn)行時內(nèi)存劃分-詳細(xì)

;

程序計數(shù)器

程序計數(shù)器是線程私有的內(nèi)存區(qū)域最易,這個區(qū)域是Java虛擬機(jī)中唯一一個沒有限制OutOfMemoryError的內(nèi)存區(qū)域怒坯。之所以需要它是因為Java的多線程機(jī)制是通過輪流切換分配處理器執(zhí)行時間來實現(xiàn)的,所以會涉及到線程的暫停和重啟藻懒,而在一個線程中如果正在執(zhí)行Java方法的話剔猿,這個計數(shù)器就回去記錄當(dāng)前正在執(zhí)行的虛擬機(jī)字節(jié)碼,一旦被暫停嬉荆,恢復(fù)只需要從程序計數(shù)器記錄的為止繼續(xù)執(zhí)行就可以归敬。

但是,如果線程中執(zhí)行的是一個Native方法员寇,那么程序計數(shù)器是不會去記錄的弄慰,所以此時的程序計數(shù)器為空。

虛擬機(jī)棧Stack

Java虛擬機(jī)棧也是線程私有的蝶锋。一條線程啟動就會為它建立一個虛擬機(jī)棧陆爽。

在線程中每有一個Java方法被調(diào)用就會創(chuàng)建一個 “棧幀” 。每個 “棧幀” 會保存執(zhí)行該方法所需的局部變量表(一般Java程序員喜歡用這個部分來代表棧)扳缕、操作數(shù)棧慌闭、動態(tài)鏈接以及方法出口等信息。

如果一個線程中有過多的 “棧幀” 要入到虛擬機(jī)棧中躯舔,即短時間內(nèi)調(diào)用了過多的方法驴剔,就會造成 -- 棧益處 -- ,即 StackOverflowError 錯誤粥庄。

在這個內(nèi)存區(qū)域中丧失,如果虛擬機(jī)需要擴(kuò)展內(nèi)存,但沒有申請到足夠的內(nèi)存惜互,就會拋出 OutOfMemoryError 錯誤布讹。

本地方法棧

和虛擬機(jī)棧有些類似,但它是為Native方法提供服務(wù)的训堆。

Java堆Heap

Java的堆內(nèi)存是Java虛擬機(jī)所管理的內(nèi)存中最大的一塊描验。它是所有線程所共享的,用于存放對象實例和數(shù)組坑鱼,Java虛擬機(jī)的GC主要就發(fā)生在這個地方膘流。因此這塊區(qū)域也叫做"GC堆"。

Java堆的內(nèi)存可以按照垃圾回收算法【分代回收】分為【新生代區(qū)】和【老年區(qū)】鲁沥,進(jìn)一步的呼股,【新生代區(qū)】可以分為【Eden區(qū)】、【From Survivor區(qū)】和【To Survivor區(qū)】画恰。

從內(nèi)存角度來說卖怜,Java堆內(nèi)存又被劃分為線程共享的內(nèi)存區(qū)域和每個線程私有的內(nèi)存區(qū)域。

在Java堆區(qū)域中阐枣,如果沒有內(nèi)存分配給要創(chuàng)建的實例马靠,并且堆也不能夠再擴(kuò)展,就會拋出OutOfMemoryError錯誤蔼两。

回收算法

Java8之后甩鳄,Heap Segment真正意義上的是由Young GeneriationOld Generiation組成的。對象在其中是標(biāo)記復(fù)制算法來判定一個對象是否應(yīng)該被清理掉额划。
Heap Segment中發(fā)生的GC稱為Major GC妙啃,只會影響Heap Segment區(qū)。

內(nèi)存圖

Young Generiation中的GC變化 — 復(fù)制算法

這個區(qū)域發(fā)生的GC稱為Minor GC俊戳。

  • 當(dāng)對象被創(chuàng)建后揖赴,首先會被加入eden區(qū)。當(dāng)eden區(qū)滿了之后抑胎,就會觸發(fā)一次GC燥滑,存活下來的對象會被復(fù)制到survivor區(qū)。
  • 當(dāng)不為空的Survivor區(qū)滿了阿逃,同樣會觸發(fā)一次GC铭拧。
  • 當(dāng)短時間內(nèi)有大量對象創(chuàng)建和釋放同樣會造成內(nèi)存抖動,會觸發(fā)CG恃锉。
  • 如圖所示搀菩,survivor有兩個區(qū)域,其中一個總是保持為空破托。
  • 現(xiàn)假設(shè)兩個Survivor區(qū)分別為S0肪跋,S1,并且首次GC時土砂,eden區(qū)中存活的對象被復(fù)制到S0中州既。當(dāng)再次發(fā)生GC時,S0和eden中仍然存活的對象就會被復(fù)制到空的S1中瘟芝,此時S0為空易桃;再次發(fā)生GC時,S1和eden中存活的對象將被復(fù)制到S0中锌俱,此時S1為空晤郑;再次發(fā)生GC...就是這樣進(jìn)行的。當(dāng)一個對象被來回復(fù)制轉(zhuǎn)移的次數(shù)達(dá)到閥值(默認(rèn)為15次贸宏,可以通過使用-XX:MaxTenuringThreshold該命令來調(diào)整閥值)時造寝,這個對象將被復(fù)制到Old Generiation區(qū)中,此時該對象將會變的相對安全吭练,因為Old Segment區(qū)的GC頻率相對較低诫龙。

Old Segment中的GC變化

這個區(qū)域發(fā)送的GC成為Full GC

  • 該區(qū)域滿了之后會觸發(fā)一次GC鲫咽,在該次GC中签赃,一些年齡較大的對象會被清理掉谷异。
  • 若多次觸發(fā)GC后,該區(qū)域仍然處于滿的狀態(tài)锦聊,則會拋出OutOfMemoryError歹嘹。
  • 以兩種情況下,新建對象會被直接復(fù)制到該區(qū)域中:
    • 當(dāng)新建對象所需要的內(nèi)存大于1/2的單個survivor區(qū)內(nèi)存時孔庭。比如一些很長的對象尺上;
    • 當(dāng)新建對象被該區(qū)中的對象引用時,或者引用了該區(qū)域中的對象圆到。

方法區(qū)

Java的方法區(qū)和Java的堆內(nèi)存一樣是被線程所共有的怎抛。它主要存放虛擬機(jī)加載的類信息、常量芽淡、靜態(tài)變量马绝、即時編譯產(chǎn)生的代碼等。

一些地方會將方法區(qū)合并到Java堆中一起去說吐绵。把它作為“永久代”迹淌。這在Hot-Spot虛擬機(jī)而言成立,但是一般來說是不成立的己单。

Java的方法區(qū)如果內(nèi)存不夠分配的話唉窃,也是會拋出OutOfMemoryError錯誤的。也就是如果加載過多類到方法區(qū)的話纹笼,可能會造成方法區(qū)內(nèi)存益處纹份。

對象的可到達(dá)性

在GC檢查對象的是否可以回收時,是根據(jù)對象是否可到達(dá)引用練頂端的GC Roots對象來判斷的廷痘。GC Roots對象一般是虛擬機(jī)棧中變量表中引用的對象蔓涧、類靜態(tài)屬性引用的對象、常量對象笋额、JNI傳到底層的對象元暴。就是說,一個對象如果溯源不到這幾種類型的對象的話兄猩,就認(rèn)為它是無法到達(dá)的茉盏,那么它將會在GC時被回收。

新的引用類型

在JDK 1.2之后枢冤,Java擴(kuò)充了4種引用類型定義:

強(qiáng)應(yīng)用類型

即我們平時通過new關(guān)鍵字創(chuàng)建出來的的對象的引用鸠姨,只要強(qiáng)引用還存在,那么這些對象就一定不回被回收淹真,即使時拋出OutOfMemoryError讶迁。什么時候強(qiáng)引用會不存在呢?當(dāng)一個方法執(zhí)行完核蘸,棧幀中的變量表將會被清理巍糯,在該方法中創(chuàng)建使用的臨時強(qiáng)引用就會被清理掉啸驯,之后,原本它指向的對象就被變的不可到達(dá)鳞贷。

軟引用類型

用來描述一些有用但不是必須的對象坯汤,即通過SoftReference創(chuàng)建的對象,它們將會在原本確定要發(fā)生內(nèi)存溢出前的一次GC中被回收搀愧,如果回收完內(nèi)存還是不夠,Java堆就會拋出OutOfMemoryError錯誤疆偿。就是說咱筛,在觸發(fā)內(nèi)存溢出發(fā)生前,這些對象是和強(qiáng)引用一樣杆故,只要引用還在迅箩,就不會被回收。

弱引用類型

用來描述一些不必須的對象处铛,即通過WeakReference創(chuàng)建的對象饲趋。弱引用對象的生命周期只有一次GC。

虛引用類型

一個對象的存在與否完全不受虛引用的影響撤蟆,它唯一的用處就是可以用來監(jiān)測一個對象是否被回收奕塑。

方法區(qū)中的-運(yùn)行時常量池

運(yùn)行時常量池主要存放類中編譯時期生成的常量,當(dāng)然也可以動態(tài)的往里面添加家肯。

比如:

"abc".intern();

這個方法首先會檢查運(yùn)行時常量池中是否有這個字符串龄砰,有的話取出來用,沒有的話生成一個并存到常量池中讨衣。

再比如换棚,運(yùn)行過程中生成通過static修飾的String時,也會加入到常量池中反镇。對于String而言固蚤,常量 + 常量 生成的也是常量,但是常量 + 變量 生成的就是變量了歹茶。

關(guān)于Dalvik虛擬機(jī)

Dalvik虛擬機(jī)是Google按照J(rèn)VM虛擬機(jī)規(guī)范定制的虛擬機(jī)夕玩,它更符合移動設(shè)備的環(huán)境要求。與標(biāo)準(zhǔn)虛擬機(jī)不同:

  • Dalvik編譯生成的是.dex文件辆亏,這種格式的文件體積更小风秤。而JVM規(guī)范的是.class文件。
  • Dalvik虛擬機(jī)是基于寄存器的扮叨,而JVM規(guī)范是基于棧的缤弦,所以速度方面會有優(yōu)勢。比如上面的說的標(biāo)準(zhǔn)Java虛擬機(jī)中彻磁,它的虛擬機(jī)棧就為線程的運(yùn)行提供了服務(wù)碍沐。而Dalvik虛擬機(jī)中狸捅,使用寄存器去儲存運(yùn)行指令,同時寄存器也提供了程序計數(shù)器累提。寄存器是處理器的一部分哦尘喝!
  • Dalvik虛擬機(jī)允許在內(nèi)存中創(chuàng)建都個實例,以隔離不同的應(yīng)用程序斋陪。這樣朽褪,當(dāng)一個應(yīng)用程序在自己的進(jìn)程中崩潰后,不會影響其它進(jìn)程的運(yùn)行无虚。

關(guān)于ART虛擬機(jī)

ART虛擬機(jī)在Android 5.0以后是被默認(rèn)開啟的缔赠,此時Dalvik已經(jīng)被Google放棄維護(hù)了。它與Dalvik虛擬機(jī)的不同:

  • ART虛擬機(jī)在應(yīng)用程序安裝時就會把字節(jié)碼通過dex2oat工具直接轉(zhuǎn)成機(jī)器碼儲存友题,這個過程叫做AOT(Ahead-Of-Time)嗤堰。而Dalvik是在每次啟動應(yīng)用程序時,通過傳統(tǒng)的JIT(JUST IN TIME)模式將字節(jié)碼轉(zhuǎn)成機(jī)器碼度宦。顯然踢匣,這樣速度會慢不少。當(dāng)然戈抄,ART虛擬機(jī)的占用內(nèi)存也會更大些离唬。
  • ART虛擬機(jī)在進(jìn)行GC時采用了并法的模式。
    • 在傳統(tǒng)的GC模式下呛凶,當(dāng)虛擬機(jī)觸發(fā)一次GC男娄,會先暫停所有線程,然后檢查所有對象漾稀,將符合回收條件的對象進(jìn)行標(biāo)記模闲,然后進(jìn)行回收,最后再恢復(fù)線程崭捍,這樣的話gc速度會快些尸折,但是遇到內(nèi)存抖動,就會卡頓了殷蛇。同時实夹,傳統(tǒng)的GC算法導(dǎo)致了【內(nèi)存碎片化】嚴(yán)重,在一次回收后粒梦,很多內(nèi)存塊都會出現(xiàn)不連續(xù)的情況亮航,這樣會導(dǎo)致尋址變得困難,從而拖慢程序運(yùn)行速度匀们。
    • 而ART虛擬的垃圾回收算法允許GC時對對象的標(biāo)記和一些對象的清理工作并發(fā)進(jìn)行缴淋。同時,ART引入了【移動垃圾回收器】技術(shù),使得碎片化內(nèi)存能夠被對齊重抖,從而能稍微節(jié)約一些內(nèi)存空間露氮。

總結(jié)

  • Heap Segment被劃分為兩塊:Young GeneriationOld Generiation
  • Young Genertiation中又被劃分為Eden區(qū)和兩個Survivor區(qū)钟沛,對象在其中采用標(biāo)記復(fù)制算法來判定一個對象是應(yīng)該清理還是移到Old Generiation中畔规。該內(nèi)存區(qū)域發(fā)生GC的頻率較高。
  • Old Generiation發(fā)生GC的頻率相對較低恨统。當(dāng)有大對象被創(chuàng)建叁扫,或者和該區(qū)域有關(guān)的對象被創(chuàng)建時,它將會被直接移動到該區(qū)域中畜埋。

看到這里的童鞋快獎勵自己一口辣條吧陌兑!

想要看CoorChice更多的文章,可以加個關(guān)注哦由捎!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市饿凛,隨后出現(xiàn)的幾起案子狞玛,更是在濱河造成了極大的恐慌,老刑警劉巖涧窒,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件心肪,死亡現(xiàn)場離奇詭異,居然都是意外死亡纠吴,警方通過查閱死者的電腦和手機(jī)硬鞍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來戴已,“玉大人固该,你說我怎么就攤上這事√抢埽” “怎么了伐坏?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長握联。 經(jīng)常有香客問我桦沉,道長,這世上最難降的妖魔是什么金闽? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任纯露,我火速辦了婚禮,結(jié)果婚禮上代芜,老公的妹妹穿的比我還像新娘埠褪。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布组橄。 她就那樣靜靜地躺著荞膘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪玉工。 梳的紋絲不亂的頭發(fā)上羽资,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天,我揣著相機(jī)與錄音遵班,去河邊找鬼屠升。 笑死,一個胖子當(dāng)著我的面吹牛狭郑,可吹牛的內(nèi)容都是我干的腹暖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼翰萨,長吁一口氣:“原來是場噩夢啊……” “哼脏答!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起亩鬼,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤殖告,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后雳锋,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體黄绩,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年玷过,在試婚紗的時候發(fā)現(xiàn)自己被綠了爽丹。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡辛蚊,死狀恐怖粤蝎,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情嚼隘,我是刑警寧澤诽里,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布,位于F島的核電站飞蛹,受9級特大地震影響谤狡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜卧檐,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一墓懂、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧霉囚,春花似錦捕仔、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽闪唆。三九已至,卻和暖如春钓葫,著一層夾襖步出監(jiān)牢的瞬間悄蕾,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工础浮, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留帆调,地道東北人。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓豆同,卻偏偏與公主長得像番刊,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子影锈,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容