1 進程通訊
定義多進程
Android應用中使用多進程只有一個辦法(用NDK的fork來做除外),就是在AndroidManifest.xml中聲明組件時,用android:process屬性來指定。
不知定process屬性,則默認運行在主進程中,主進程名字為包名。
android:process = package:remote,將運行在package:remote進程中,屬于全局進程,其他具有相同shareUID與簽名的APP可以跑在這個進程中。
android:process = :remote ,將運行在默認包名:remote進程中,而且是APP的私有進程,不允許其他APP的組件來訪問。
多進程引發(fā)的問題
靜態(tài)成員和單例失效:每個進程保持各自的靜態(tài)成員和單例,相互獨立。
線程同步機制失效:每個進程有自己的線程鎖。
SharedPreferences可靠性下降:不支持并發(fā)寫,會出現(xiàn)臟數(shù)據(jù)。
Application多次創(chuàng)建:不同進程跑在不同虛擬機,每個虛擬機啟動會創(chuàng)建自己的Application,自定義Application時生命周期會混亂。
綜上,不同進程擁有各自獨立的虛擬機,Application,內(nèi)存空間,由此引發(fā)一系列問題。
Bundle的使用
public final class Bundle extends BaseBundle implements Cloneable, Parcelable
可以看到Bundle實現(xiàn)了Parcelable 接口。
優(yōu)點:簡單易用
缺點:只能傳遞Bundle支持的數(shù)據(jù)類型
使用場景:四大組件間的進程通訊
2.文件共享
優(yōu)點:簡單易用
缺點:不適合高并發(fā)的場景,不能做到即時通訊。
使用場景:無并發(fā)訪問的情景,簡單的交換數(shù)據(jù),實時性要求不高。
3.AIDI
優(yōu)點:功能強大,支持一對多并發(fā)通信,支持實時通信。
缺點:一定要處理好線程同步的問題
使用場景:一對多進行通訊,有RPC(遠程過程調(diào)用協(xié)議)的需求
4.Messenger(信使)
優(yōu)點:功能一般,支持一對多串行通信,支持實時通信。
缺點:不能很好的處理高并發(fā)場景,不支持RPC,數(shù)據(jù)通過Message進行傳輸,因此只能支持Bundle支持的數(shù)據(jù)類型。
使用場景:低并發(fā)的一對多的實時通訊,沒有RPC的需求或者說沒有返回結(jié)果的RPC(不調(diào)用服務端的相關方法)
5.ContentProvider
優(yōu)點:主要用于數(shù)據(jù)訪問,支持一對多的并發(fā)數(shù)據(jù)共享。
缺點:受約束,主要針對數(shù)據(jù)源的增刪改查。
使用場景:一對多的數(shù)據(jù)共享。
6.Socket(套接字)
優(yōu)點:功能強大,通過讀寫網(wǎng)絡傳輸字節(jié)流,支持一對多的并發(fā)的實時通訊。
缺點:不支持直接的RPC(這里我也不是很明白,間接的怎么實現(xiàn)?)
使用場景:網(wǎng)絡的數(shù)據(jù)交換
2 內(nèi)存管理
轉(zhuǎn)自http://www.itdecent.cn/p/c4b283848970
1、初識內(nèi)存優(yōu)化
在Android的性能優(yōu)化的各個部分里,內(nèi)存的問題絕對是最令人頭疼的一部分,雖然Android有垃圾自動回收機制不需要手動干預,但也恰因為此,出現(xiàn)內(nèi)存問題如內(nèi)存泄漏和內(nèi)存溢出等,如果對內(nèi)存管理機制不熟悉,會更加難以排查問題。
因為內(nèi)存方面的知識較多且不易理解,內(nèi)存優(yōu)化部分就分兩篇文章進行,本文主要是關于Java、Android的內(nèi)存分配、回收、GC等理論知識。
2、內(nèi)存分配
談Android的內(nèi)存,就不能不提Java的內(nèi)存管理。Java程序在運行的過程中會將其管理的內(nèi)存分為若干個不同的數(shù)據(jù)區(qū):
JVM運行時數(shù)據(jù)區(qū)
方法區(qū):方法區(qū)存放的是類信息、常量、靜態(tài)變量,所有線程共享區(qū)域。
虛擬機棧:每個方法在執(zhí)行的同時都會創(chuàng)建一個棧幀(Stack Frame)用于存儲局部變量表、操作數(shù)棧、動態(tài)鏈接、方法出口等信息,線程私有區(qū)域。
本地方法棧:與虛擬機棧類似,區(qū)別是虛擬機棧為虛擬機執(zhí)行Java方法服務,本地方法棧為虛擬機使用到的Native方法服務。
堆:JVM管理的內(nèi)存中最大的一塊,所有線程共享;用來存放對象實例,幾乎所有的對象實例都在堆上分配內(nèi)存;此區(qū)域也是垃圾回收器(Garbage Collection)主要的作用區(qū)域,內(nèi)存泄漏就發(fā)生在這個區(qū)域。
程序計數(shù)器:可看做是當前線程所執(zhí)行的字節(jié)碼的行號指示器;如果線程在執(zhí)行Java方法,這個計數(shù)器記錄的是正在執(zhí)行的虛擬機字節(jié)碼指令地址;如果執(zhí)行的是Native方法,這個計數(shù)器的值為空(Undefined)。
備注:
有一種習慣說法:把Java的內(nèi)存區(qū)域分為堆內(nèi)存(Heap)和棧內(nèi)存(Stack),Stack訪問快,Heap訪問慢,Stack中保存的是對象的引用(指針),Heap中保存的是對象的實例。
實際上這種說法是籠統(tǒng)、粗糙的,此處所說的Stack僅僅是虛擬機棧中的局部變量表部分。虛擬機棧與JVM運行時數(shù)據(jù)區(qū)涵蓋的都比此種說法多。
3、內(nèi)存回收
3.1標記-清除算法
最基礎的收集算法:分為“標記”和“清除”兩個階段,首先,標記出所有需要回收的對象,然后統(tǒng)一回收所有被標記的對象。
這種方法有兩個不足點:
效率問題,標記和清除兩個過程的效率都不高;
空間問題,標記清除之后會產(chǎn)生大量的不連續(xù)的內(nèi)存碎片。
“標記-清除”算法示意圖
3.2復制算法
將內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊,當這一塊內(nèi)存將用完了,就將還存活著的對象復制到另一塊內(nèi)存上面,然后再把已使用過的內(nèi)存空間一次清理掉。
這種方法的特點:
優(yōu)點:實現(xiàn)簡單,運行高效;每次都是對整個半?yún)^(qū)進行內(nèi)存回收,內(nèi)存分配時也不需要考慮內(nèi)存碎片等情況,只要移動堆頂指針,按順序分配內(nèi)存即可;
缺點:粗暴的將內(nèi)存縮小為原來的一半,代價實在有點高。
“復制”算法示意圖
3.3標記-整理算法
先標記需要回收的對象(標記過程與“標記-清除”算法一樣),然后把所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。
這種方法的特點:
避免了內(nèi)存碎片;
避免了“復制”算法50%的空間浪費;
主要針對對象存活率高的老年代。
“標記-整理”算法示例圖.png
3.4分代收集算法
根據(jù)對象的存活周期的不同將內(nèi)存劃分為幾塊,一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴?。在新生代中,每次垃圾收集時都會發(fā)現(xiàn)有大量對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用標記—清除算法或標記—整理算法來進行回收。
4、對象是否回收的依據(jù)
4.1引用計數(shù)算法
給對象中添加一個引用計數(shù)器,每當有一個地方引用該對象時,計數(shù)器值加1;引用失效時,計數(shù)器值減1;任意時刻計數(shù)器為0的對象就是不可能再被使用的,表示該對象不存在引用關系。
這種方法的特點:
優(yōu)點:實現(xiàn)簡單,判定效率也很高;
缺點:難以解決對象之間相互循環(huán)引用導致計數(shù)器值不等于0的問題。
4.2可達性分析算法
以一系列成為“GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈相連(GC Roots到這個對象不可達),則證明此對象是不可用的。
可達性分析算法判定對象是否可回收
5、Android的內(nèi)存管理
Android系統(tǒng)的ART和Dalvik虛擬機扮演了常規(guī)的內(nèi)存垃圾自動回收的角色, 使用paging和memory-mapping來管理內(nèi)存,這意味著不管是因為創(chuàng)建對象還是使用使用內(nèi)存頁面造成的任何被修改的內(nèi)存,都會一直存在于內(nèi)存中,App唯一釋放內(nèi)存的方法就是釋放App持有的對象引用,使GC可以回收。
Android Runtime內(nèi)存堆劃分
5.1內(nèi)存回收
在Android的高級系統(tǒng)版本里面針對Heap空間有一個Generational Heap Memory的模型,最近分配的對象會存放在Young Generation區(qū)域,當這個對象在這個區(qū)域停留的時間達到一定程度,它會被移動到Old Generation,最后累積一定時間再移動到Permanent Generation區(qū)域。系統(tǒng)會根據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類型分別執(zhí)行不同的gc操作。例如,剛分配到Y(jié)oung Generation區(qū)域的對象通常更容易被銷毀回收,同時在Young Generation區(qū)域的gc操作速度會比Old Generation區(qū)域的gc操作速度更快。
5.2共享內(nèi)存
Android應用的進程都是從一個叫做Zygote的進程fork出來的。Zygote進程在系統(tǒng)啟動并且載入通用的framework的代碼與資源之后開始啟動。為了啟動一個新的程序進程,系統(tǒng)會fork Zygote進程生成一個新的進程,然后在新的進程中加載并運行應用程序的代碼。這使得大多數(shù)的RAM pages被用來分配給framework的代碼,同時使得RAM資源能夠在應用的所有進程之間進行共享。
大多數(shù)static的數(shù)據(jù)被mmapped到一個進程中。這不僅僅使得同樣的數(shù)據(jù)能夠在進程間進行共享,而且使得它能夠在需要的時候被paged out。常見的static數(shù)據(jù)包括Dalvik Code,app resources,so文件等。
大多數(shù)情況下,Android通過顯式的分配共享內(nèi)存區(qū)域(例如ashmem或者gralloc)來實現(xiàn)動態(tài)RAM區(qū)域能夠在不同進程之間進行共享的機制。例如,Window Surface在App與Screen Compositor之間使用共享的內(nèi)存,Cursor Buffers在Content Provider與Clients之間共享內(nèi)存。
5.3分配與回收內(nèi)存
每一個進程的Dalvik heap都反映了使用內(nèi)存的占用范圍。這就是通常邏輯意義上提到的Dalvik Heap Size,它可以隨著需要進行增長,但是增長行為會有一個系統(tǒng)為它設定的上限。
邏輯上講的Heap Size和實際物理意義上使用的內(nèi)存大小是不對等的,Proportional Set Size(PSS)記錄了應用程序自身占用以及和其他進程進行共享的內(nèi)存。
5.4限制應用的內(nèi)存
為了整個Android系統(tǒng)的內(nèi)存控制需要,Android系統(tǒng)為每一個應用程序都設置了一個硬性的Dalvik Heap Size最大限制閾值,這個閾值在不同的設備上會因為RAM大小不同而各有差異。如果你的應用占用內(nèi)存空間已經(jīng)接近這個閾值,此時再嘗試分配內(nèi)存的話,很容易引起OutOfMemoryError的錯誤。
ActivityManager.getMemoryClass()可以用來查詢當前應用的Heap Size閾值,這個方法會返回一個整數(shù),表明你的應用的Heap Size閾值是多少Mb(megabates)。
5.5應用切換
Android系統(tǒng)并不會在用戶切換應用的時候做交換內(nèi)存的操作。Android會把那些不包含F(xiàn)oreground組件的應用進程放到LRU Cache中。例如,當用戶開始啟動了一個應用,系統(tǒng)會為它創(chuàng)建了一個進程,但是當用戶離開這個應用,此進程并不會立即被銷毀,而是會被放到系統(tǒng)的Cache當中,如果用戶后來再切換回到這個應用,此進程就能夠被馬上完整的恢復,從而實現(xiàn)應用的快速切換。
如果你的應用中有一個被緩存的進程,這個進程會占用一定的內(nèi)存空間,它會對系統(tǒng)的整體性能有影響。因此當系統(tǒng)開始進入Low Memory的狀態(tài)時,它會由系統(tǒng)根據(jù)LRU的規(guī)則與應用的優(yōu)先級,內(nèi)存占用情況以及其他因素的影響綜合評估之后決定是否被殺掉。
需要特別注意的:
在Dalvik下,大部分Davik采取的都是標記-清理回收算法,而且具體使用什么算法是在編譯期決定的,無法在運行的時候動態(tài)更換。標記-清理回收算法無法對Heap中空閑內(nèi)存區(qū)域做碎片整理。系統(tǒng)僅僅會在新的內(nèi)存分配之前判斷Heap的尾端剩余空間是否足夠,如果空間不夠會觸發(fā)gc操作,從而騰出更多空閑的內(nèi)存空間;這樣內(nèi)存空洞就產(chǎn)生了。
內(nèi)存碎片的產(chǎn)生
如上圖所示,第一行,在開始階段,內(nèi)存分配較滿;第二行,經(jīng)過GC之后,大部分對象被釋放。此時可能產(chǎn)生的問題是,因為沒有內(nèi)存整理功能,整個頁面的4KB內(nèi)存(內(nèi)存分配的最小單位是頁面,通常為4KB)可能只有一個小對象,但是統(tǒng)計PrivateDirty/Pss時還是按照4KB計算。所以對于Dalvik虛擬機的手機來說,我們首先要盡量避免掉頻繁生成很多臨時小變量(比如說:getView, onDraw等函數(shù)中new對象),另一個又要盡量去避免產(chǎn)生很多長生命周期的大對象。
ART在GC上不像Dalvik僅有一種回收算法,ART在不同的情況下會選擇不同的回收算法。應用程序在前臺運行時,響應性是最重要的,因此也要求執(zhí)行的GC是高效的。相反,應用程序在后臺運行時,響應性不是最重要的,這時候就適合用來解決堆的內(nèi)存碎片問題。因此,Mark-Sweep GC適合作為Foreground GC,而Mark-Compact GC適合作為Background GC。由于有Compact的能力存在,內(nèi)存碎片在ART上可以很好的被避免,這個也是ART一個很好的能力。
六、Android GC何時發(fā)生?
由上文我們知道,GC操作主要是由系統(tǒng)決定的,但是我們可以監(jiān)聽系統(tǒng)的GC過程,以此來分析我們應用程序當前的內(nèi)存狀態(tài)。
Dalvik虛擬機,每一次GC打印內(nèi)容格式:
D/dalvikvm:,,,
含義解析
GC Reason:GC觸發(fā)原因
GC_CONCURRENT:當已分配內(nèi)存達到某一值時,觸發(fā)并發(fā)GC;
GC_FOR_MALLOC:當嘗試在堆上分配內(nèi)存不足時觸發(fā)的GC;系統(tǒng)必須停止應用程序并回收內(nèi)存;
GC_HPROF_DUMP_HEAP: 當需要創(chuàng)建HPROF文件來分析堆內(nèi)存時觸發(fā)的GC;
GC_EXPLICIT:當明確的調(diào)用GC時,例如調(diào)用System.gc()或者通過DDMS工具顯式地告訴系統(tǒng)進行GC操作等;
GC_EXTERNAL_ALLOC: 僅在API級別為10或者更低時(新版本分配內(nèi)存都在Dalvik堆上)
Amount freed GC:回收的內(nèi)存大小
Heap stats:堆上的空閑內(nèi)存百分比 (已用內(nèi)存)/(堆上總內(nèi)存)
External memory stats: API級別為10或者更低:(已分配的內(nèi)存量)/ (即將發(fā)生垃圾的極限)
Pause time:這次GC操作導致應用程序暫停的時間。關于這個暫停的時間,在2.3之前GC操作是不能并發(fā)進行的,也就是系統(tǒng)正在進行GC,那么應用程序就只能阻塞住等待GC結(jié)束。而自2.3之后,GC操作改成了并發(fā)的方式進行,就是說GC的過程中不會影響到應用程序的正常運行,但是在GC操作的開始和結(jié)束的時候會短暫阻塞一段時間。
Art虛擬機,每一次GC打印內(nèi)容格式:
I/art:,,,,
基本情況和Dalvik沒有什么差別,GC的Reason更多了,還多了一個LOS_Space_Status.
LOS_Space_Status:Large Object Space,大對象占用的空間,這部分內(nèi)存并不是分配在堆上的,但仍屬于應用程序內(nèi)存空間,主要用來管理 Bitmap 等占內(nèi)存大的對象,避免因分配大內(nèi)存導致堆頻繁 GC。
七、獲取內(nèi)存使用情況
通過命令行adb shell dumpsys meminfo packagename查看內(nèi)存詳細占用情況:
命令行查看內(nèi)存分配情況
其中幾個關鍵的數(shù)據(jù):
Private(Clean和Dirty的):應用進程單獨使用的內(nèi)存,代表著系統(tǒng)殺死你的進程后可以實際回收的內(nèi)存總量**。通常需要特別關注其中更為昂貴的dirty部分,它不僅只被你的進程使用而且會持續(xù)占用內(nèi)存而不能被從內(nèi)存中置換出存儲。申請的全部Dalvik和本地heap內(nèi)存都是Dirty的,和Zygote共享的Dalvik和本地heap內(nèi)存也都是Dirty的。
Dalvik Heap:Dalvik虛擬機使用的內(nèi)存,包含dalvik-heap和dalvik-zygote,堆內(nèi)存,所有的Java對象實例都放在這里。
Heap Alloc:累加了Dalvik和Native的heap。
PSS:這是加入與其他進程共享的分頁內(nèi)存后你的應用占用的內(nèi)存量,你的進程單獨使用的全部內(nèi)存也會加入這個值里,多進程共享的內(nèi)存按照共享比例添加到PSS值中。如一個內(nèi)存分頁被兩個進程共享,每個進程的PSS值會包括此內(nèi)存分頁大小的一半在內(nèi)。
Dalvik Pss內(nèi)存 = 私有內(nèi)存Private Dirty + (共享內(nèi)存Shared Dirty / 共享進程數(shù))
TOTAL:上面全部條目的累加值,全局的展示了你的進程占用的內(nèi)存情況。
ViewRootImpl:應用進程里的活動窗口視圖個數(shù),可以用來監(jiān)測對話框或者其他窗口的內(nèi)存泄露。
AppContexts及Activities:應用進程里Context和Activity的對象個數(shù),可以用來監(jiān)測Activity的內(nèi)存泄露。
作者:頭條祁同偉
鏈接:http://www.itdecent.cn/p/c4b283848970
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處。