堆分區
堆大小 = 新生代 + 老年代。默認下,新生代 ( Young ) = 1/3 的堆空間大小,老年代 ( Old ) = 2/3 的堆空間大小;
新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名為 from 和 to,以示區分。默認的,Edem : from : to = 8 : 1 : 1;
JVM 每次只會使用 Eden 和其中的一塊 Survivor 區域來為對象服務,所以無論什么時候,總是有一塊 Survivor 區域是空閑著的。因此,新生代實際可用的內存空間為 9/10 ( 即90% )的新生代空間;
GC 分為兩種:老生代中采用標記-清除算法的Full GC ( 或稱為 Major GC )和新生代中采用復制算法的Minor GC。新生代是 GC 收集垃圾的頻繁區域;
持久代位于方法區,且僅存在于Hotspot虛擬機中,sun公司計劃移除。
**所謂的新生代和老年代是針對于分代收集算法來定義的,新生代又分為Eden和Survivor兩個區。加上老年代就這三個區。數據會首先分配到Eden區 當中(當然也有特殊情況,如果是大對象那么會直接放入到老年代(大對象是指需要大量連續內存空間的java對象)。),當Eden沒有足夠空間的時候就會 觸發jvm發起一次Minor GC。如果對象經過一次Minor GC還存活,并且又能被Survivor空間接受,那么將被移動到Survivor空 間當中。并將其年齡設為1,對象在Survivor每熬過一次Minor GC,年齡就加1,當年齡達到一定的程度(默認為15)時,就會被晉升到老年代 中了,當然晉升老年代的年齡是可以設置的。
其實新生代和老年代就是針對于對象做分區存儲,更便于回收等等**
新生代主要存放的是那些很快就會被GC回收掉的或者不是特別大的對象(這個大就要看你是否設置了-XX:PretenureSizeThreshold 參數了)。新生代采用的復制算法,即將新生代分為3個區:較大的Eden和兩個較小的Survivor(默認的Eden:Survivor = 8:1)。發生在新生代的GC為Minor GC 。在Minor GC時會將新生代中還存活著的對象復制進一個Survivor中,然后對Eden和另一個Survivor進行清理。所以,平常可用的新生代大小為Eden的大小+一個Survivor的大小年代則是存放那些在程序中經歷了好幾次回收仍然還活著或者特別大的對象(這個大就要看你是否設置了-XX:PretenureSizeThreshold 參數了)。老年代采用的是標記-清除或者標記-整理算法,這兩個算法主要看虛擬機采用的哪個收集器,兩種算法的區別是:標記-清除可能會產生大量連續的內存碎片。在老年代中的GC則為Major GC。Major GC和Full GC會造成stop-the-world。-Xms2000m -Xmx2000m -Xmn800m -XX:PermSize=64m -XX:MaxPermSize=256mXms,即為jvm啟動時得JVM初始堆大小,Xmx為jvm的最大堆大小,xmn為新生代的大小,permsize為永久代的初始大小,MaxPermSize為永久代的最大空間。老年代和新生代也是和內存相關,虛擬機初始化時已經設定了使用的內存大小,并劃分為三部分:新生代– 新創建的對象,舊生代 – 經過多次垃圾回收沒有被回收的對象或者大對象持久代– JVM使用的內存,包含類信息等所謂的新生代和老年代是針對于分代收集算法來定義的,新生代又分為Eden和Survivor兩個區。加上老年代就這三個區。數據會首先分配到Eden區 當中(當然也有特殊情況,如果是大對象那么會直接放入到老年代(大對象是指需要大量連續內存空間的java對象)。),當Eden沒有足夠空間的時候就會 觸發jvm發起一次Minor GC。如果對象經過一次Minor GC還存活,并且又能被Survivor空間接受,那么將被移動到Survivor空 間當中。并將其年齡設為1,對象在Survivor每熬過一次Minor GC,年齡就加1,當年齡達到一定的程度(默認為15)時,就會被晉升到老年代 中了,當然晉升老年代的年齡是可以設置的。其實新生代和老年代就是針對于對象做分區存儲,更便于回收等等
當對象在 Eden ( 包括一個 Survivor 區域,這里假設是 from 區域 ) 出生后,在經過一次 Minor GC 后,如果對象還存活,并且能夠被另外一塊 Survivor 區域所容納( 上面已經假設為 from 區域,這里應為 to 區域,即 to 區域有足夠的內存空間來存儲 Eden 和 from 區域中存活的對象 ),則使用復制算法將這些仍然還存活的對象復制到另外一塊 Survivor 區域 ( 即 to 區域 ) 中,然后清理所使用過的 Eden 以及 Survivor 區域 ( 即 from 區域 ),并且將這些對象的年齡設置為1,以后對象在 Survivor 區每熬過一次 Minor GC,就將對象的年齡 + 1,當對象的年齡達到某個值時 ( 默認是 15 歲,可以通過參數 -XX:MaxTenuringThreshold 來設定 ),這些對象就會成為老年代。但這也不是一定的,對于一些較大的對象 ( 即需要分配一塊較大的連續內存空間 ) 則是直接進入到老年代;
Full GC 發生的次數不會有 Minor GC 那么頻繁,并且做一次 Full GC 要比進行一次 Minor GC 的時間更長;
因為 jvm 每次只是用新生代中的 eden 和 一個 survivor,因此新生代實際的可用內存空間大小為所指定的 90%;
PermGen 即永久代 ( 方法區 ),它還有一個名字,叫非堆,主要用來存儲由 jvm 加載的類文件信息、常量、靜態變量等;
每次調 System.gc(),是先進行 Minor GC,然后再進行 Full GC;
當 Full GC 進行的時候,默認的方式是盡量清空新生代 ( YoungGen ),因此在調 System.gc() 時,新生代 ( YoungGen ) 中存活的對象會提前進入老年代;
題眼:JVM使用的是分代垃圾回收的方式,可以將Java對象分為"年輕"對象和"年老"對象.JVM將內存堆(Heap)分為兩個區域,一個是"年輕"區,另一個是"老"區,Java將這兩個區域分別稱作是"新生代"和"老生代"。
詳細:JVM使用的是分代垃圾回收的方式,主要是因為在程序運行的時候會有如下特點:
◆大多數對象在創建后很快就沒有對象使用它了。
◆大多數在一直被使用的對象很少再去引用新創建的對象。因此就將Java對象分為"年輕"對象和"年老"對象,JVM將內存堆(Heap)分為兩個區域,一個放年輕對象,一個放年老對象,java對對內存的這兩個區域分別成為 新生代,老生代。
新生代區域特點: a) 絕大多數新創建的對象都存放在這個區域b) 此區域一般來說較小而且JVM垃圾回收頻率較高c) 采用的算法和存放對象特點使得該區域JVM垃圾回收的效率也非常高
老生代區域特點: a) 將在"新生代"中生存了較長時間的對象轉移過來b) 區域一般要大一些而且增長的速度相對于"新生代"要慢一些c) 垃圾回收的執行頻率也會低很多
了解這個對于我們的好處是:JVM在JVM垃圾回收處理時會消耗一定的系統資源,如果我們在JVM啟動的時候添加相關參數來控制"新生代"區域的大小以達到調整JVM垃圾回收處理的頻率,那么我們就會合理利用系統資源。
補充:
1 通過設置-xx:PretenureSizeThreshold 來設置多大的對象直接進入老年代。你把這個值調的大一點,則可以保證大部分對象不會直接進入老年代,老年代對象的gc同長慢,一般內存不滿時不會gc的,所以你的大對象一直都在不會被回收。2 (-xx:MaxTenuringThreshold默認15)這個值也可以調調,這個表示在新生代折騰多少次后進入年老代。
安卓系統機制
系統啟動過程
加載bootloader
-
Bootloader加載Linux內核
Bootloader作用是:類似于BIOS,初始化基本硬件設備,建立內存空間映射、為加載linux內核準備好運行環境。Linux內核初始化完成后,裝載完文件系統,就啟動了第一個init進程
-
Linux初始化后啟動第一個進程init
init進程解析linux的腳本文件init.rc,根據這個文件內容init進程會裝載Android文件系統,創建系統目錄,啟動Android重要守護進程等。
init進程還會分裂出更多的名為damens的守護進程,如usb deamon等處理底層硬件相關
-
init進程初始化結束后會啟動Zygote進程。
- Zygote進程
Zygote進程fork出應用進程,是所有進程的父進程。
Zygote進程初始化的時候回創建Dalvik虛擬機,預裝載系統資源文件和java類,所有從Zygote fork出的進程繼承和共享這些資源不用浪費時間重新加載。
Zygote初始化完后,也將變成守護進程,負責響應啟動apk應用的啟動請求。
-
Zygote進程fork第一個進程SystemServer進程
SystemServer進程是Zygote fork出的第一個進程,也是整個Android系統的核心進程。在SystemServer主要運行的是Binder服務。
SystemServer首先啟動本地服務SensorService接著啟動ActivityManagerService、WindowsManagerService、PackageManagerService等所有java服務。
-
SystemService加載完所有java服務后調用AMS的SystemReady()方法
其實是為了打開Launcher。還要去PMS取一下所有類型為
ACTION_MAIN
的intent決定打開的Launcher。
App啟動流程
-
點擊app圖標后,click事件會調用startActivity(intent)會通過Binder IPC機制最終調用到AMS。該服務會執行以下操作:
通過PackageManager的resolveIntent()獲得該intent對象的指向信息
通過PMS獲得用戶是否有權限調用該intent指向的Activity
-
如果有權限,AMS會檢查并在新的task中啟動目標activity
檢查該進程的ProcessRecord是否存在,如果ProcessRecord為null,AMS會創建新的進程并實例化目標activity
-
創建進程
AMS調用startProcessLocked()創建新的進程,該方法會通過Socket通道傳參給Zygote進程,Zygote孵化自己并調用ZYgoteinit.main()方法來實例化ActivityThread對象并最終返回新進程的PID。
ActivityThread隨后依次調用Looper.prepare() 和Looper.loop()開啟消息循環
-
綁定進程到Application
通過ActivityThread調用bindApplication()完成。
該方法發送一個BIND_APPLICATION消息到消息隊列,最終通過handleBindApplication()處理該消息,最終調用makeApplication()方法來加載App的classes到內存。
-
啟動Activity
到這里,系統已經擁有該Application的進程。后面調用的順序就是普通的從一個已經存在的進程啟動一個新進程的Activity
實際步驟是:執行realStartActivity(),他會調用application線程的sheduleLauncherActivity()發生一個LAUNCHER_ACTIVITY消息到消息隊列中,通過handleLauncherActivity()處理該消息。
安裝軟件過程
apk文件就是一個資源和組件的壓縮包,安裝的過程就是復制和解析的過程。
-
復制
安卓機有內部存儲和SD卡兩部分,很多安卓機的內存并不大,需要把apk安裝到SD卡上節省內存空間,所以程序目錄/data/app/實際上也是在內部存儲和SD卡上各一個。
系統自帶的App是安裝在/system/app/目錄下的,這個目錄只有root權限才能訪問,所以系統App在root之前是無法刪除和修改的,也就是說,系統App升級時,實際上是在/data/app/里重新安裝了一個App,這個路徑會重新注冊到系統那里,系統再打開App時,就會指向新App的地址。當然,這個新的App是可以卸載的,不過新的App卸載后,系統會把 /system/app/里那個舊的App提供給你,所以是卸掉新的,還你舊的。
還是系統App,在root后,我們可以操作/system/app/目錄,但是系統安裝Apk仍然會裝到/data/app/里,所以如果想修改/system/app/目錄里的app,必須自己手動push新的apk文件進去,這個新的apk文件不會自動被安裝,需要重啟設備,系統在重啟時檢查到apk被更新,才會去安裝apk。
系統目錄有個/system/priv-app/目錄,這里面放的是權限更高的系統核心應用,如開機launcher、系統UI、系統設置等,這個目錄我們最好不要動,保持系統干凈簡潔。
-
安裝
具體的apk安裝過程,就是由這個PMS操作的。PMS會監控/data/app/這個目錄,在上一步中,系統安裝程序向這個目錄復制了一個apk,PMS自己就會定期掃描這個目錄,找到后綴為apk的文件,如果這個apk沒有被安裝過,它就會自動開始安裝,安裝時會做這么幾件事:
創建應用目錄,路徑為/data/data/your package(你的應用包名),App中使用的數據庫、so庫、xml文件等,都會放在這個目錄下。
提取dex文件,dex是App的可執行文件,系統解壓apk就能得到dex文件,然后把dex文件放到/data/dalvik-cache,這樣可以提前緩存dex到內存中,能加快啟動速度。系統還會把dex優化為odex,進一步加快啟動速度。
判斷是否可以安裝apk,如檢查apk簽名等。
為應用分配并保存一個UID,UID是來自Linux的用戶賬戶體系,不過在Android這種單用戶系統里,UID被用來與App對應,這也是安全機制的一部分,每個App都有自己對應的UID,這種對應關系是持久化保存的,App更新或卸載重裝后,系統還會給它分配原來那個UID。用adb pull /data/system/packages.list可以查看所有App的UID。GID(用戶組)一般等于UID。
利用AndroidManifest文件,注冊Apk的各項信息,包括但不限于:****. 根據installLocation屬性(internalOnly、auto、preferExternal),選擇安裝在內部存儲器還是SD卡上。. 根據sharedUserId屬性,為App分配UID,如果兩個App使用同一個UID,打包時又使用了相同的簽名,它們就被視為同一個用戶,可以共享數據,甚至運行在同一個進程上。. 向/data/system/packages.xml文件中,記錄App的包名、權限、版本號、安裝路徑等;同時在/data/system/packages.list中,更新所有已安裝的app列表。. 注冊App中的的四大組件(Activity、Service、Broadcast Receiver和Content Provider),包括組件的intent-filter和permission等。. 在桌面上添加App的快捷方式,如果AndroidManifest文件中有多個Activity被標注為<action android:name="android.intent.action.MAIN" />和<category android:name="android.intent.category.LAUNCHER" />,系統就會向桌面添加多個App快捷方式,所以有時候在安裝一個App后,用戶可能會感覺安裝了多個App。
-
通知
apk安裝完成后,PMS會發一個ACTION_PACKAGE_ADDED廣播,如果是卸載,會發ACTION_PACKAGE_REMOVED廣播。
安卓進程機制
前臺進程
可見進程
服務進程
即Service服務
- 后臺進程
即當前App按Home鍵后成為后臺進程
Binder IPC機制
Android 從下而上分了內核層、硬件抽象層、系統服務層、Binder IPC 層、應用程序框架層
Android 中「應用程序框架層」以 SDK 的形式開放給開發者使用,「系統服務層」中的核心服務隨系統啟動而運行,通過應用層序框架層提供的 Manager 實時為應用程序提供服務調用。系統服務層中每一個服務運行在自己獨立的進程空間中,應用程序框架層中的 Manager 通過 Binder IPC 的方式調用系統服務層中的服務。
小結
Binder IPC 屬于 C/S 架構,包括 Client、Driver、Server 三個部分
Client 可以手動調用 Driver 的 transact 接口,也可以通過 AIDL 生成的 Proxy 調用
Server 中會啟動一個「線程池」來處理 Client 的調用請求,處理完成后將結果返回給 Driver,Driver 再返回給 Client
AIDL
一個繼承了 Binder,一個繼承了 AIDL 自動生成的 Stub 對象
AIDL 自動生成了 Stub 類
在 Service 端繼承 Stub 類,Stub 類中實現了 onTransact 方法實現了「解包」的功能
在 Client 端使用 Stub 類的 Proxy 對象,該對象實現了「組包」并且調用 transact 的功能
Activity與Service幾種通信
1.Binder2.intent3.廣播
如何保證服務常駐
-
黑色保活
利用不同進程之間廣播喚起
保證APP不殺死方法
-
白色保活
- 顯示通知
-
灰色保活
啟動一個不顯示通知的前臺服務
啟動前臺服務:只需要在服務啟動的時候,調用startForeground()方法,在其中傳入一個待顯示的Notification即可。停止前臺服務,需要調用stopForeground()方法。
去掉前臺服務的通知,行之有效的方法是:
- 需要兩個前臺服務,共享同一個Notification ID。
- 一個服務啟動完畢之后,馬上停止自己,會去掉通知欄的通知。
- 而之前已經借助這個ID保持前臺的服務,依然會處于前臺的狀態不變。