TShopping

 找回密碼
 註冊
搜索
查看: 503|回復: 1

[教學] Android內存管理機制詳解

[複製鏈接]
發表於 2013-11-25 00:05:06 | 顯示全部樓層 |閱讀模式
 
Push to Facebook Push to Plurk Push to Twitter 
windows內存區別        在Linux中經常發現空閒內存很少,似乎所有的內存都被系統佔用了,表面感覺是內存不夠用了,其實不然。這是Linux內存管理的一個優秀特性,在這方面,區別於 Windows的內存管理。主要特點是,無論物理內存有多大,Linux都將其充份利用,將一些程序調用過的硬盤數據讀入內存,利用內存讀寫的高速特性來提高Linux系統的數據訪問性能。而Windows是只在需要內存時,才為應用程序分配內存,並不能充分利用大容量的內存空間。換句話說,每增加一些物理內存,Linux都將能充分利用起來,發揮了硬件投資帶來的好處,而Windows只將其做為擺設,即使增加8GB甚至更大。
android內存的意義        其實我們在用安卓手機的時候不用太在意剩餘內存,Android上的應用是java,當然需要虛擬機,而android上的應用是帶有獨立虛擬機的,也就是每開一個應用就會打開一個獨立的虛擬機。其實和java的垃圾回收機制類似,系統有一個規則來回收內存。進行內存調度有個閥值,只有低於這個值系統才會按一個列表來關閉用戶不需要的東西。當然這個值默認設置得很小,所以你會看到內存老在很少的數值徘徊。但事實上他並不影響速度。相反加快了下次啟動應用的速度。這本來就是 android標榜的優勢之一,如果人為去關閉進程,沒有太大必要。特別是使用自動關進程的軟件。為什麼內存少的時候運行大型程序會慢呢,原因是:在內存剩餘不多時打開大型程序時會觸發系統自身的調進程調度策略,這是十分消耗系統資源的操作,特別是在一個程序頻繁向系統申請內存的時候。這種情況下系統並不會關閉所有打開的進程,而是選擇性關閉,頻繁的調度自然會拖慢系統。

進程管理軟件
        進程管理軟件有無必要呢?有的。就是在運行大型程序之前,你可以手動關閉一些進程釋放內存,可以顯著的提高運行速度。但一些小程序完全可交由系統自己管理。那麼如果不關程序是不是會更耗電。android的應用在被切換到後台時,它其實已經被暫停了,並不會消耗cpu資源只保留了運行狀態。所以為什麼有的程序切出去重進會到主界面。但是一個程序如果想要在後台處理些東西,如音樂播放,它就會開啟一個服務。服務可在後台持續運行,所以在後台耗電的也只有帶服務的應用了。我們可以把帶服務的進程用進程管理軟件關閉就可以了。沒有帶服務的應用在後台是完全不耗電的沒有必要關閉。這種設計本來就是一個非常好的設計,下次啟動程序時會更快,因為不需要讀取界面資源,何必要關掉他們抹殺這個android的優點呢。
Android進程種類1.
        前台進程(foreground
        目前正在屏幕上顯示的進程和一些系統進程。舉例來說,DialerStorageGoogle Search等系統進程就是前台進程;再舉例來說,當你運行一個程序,如瀏覽器,當瀏覽器界面在前台顯示時,瀏覽器屬於前台進程(foreground),但一旦你按home回到主界面,瀏覽器就變成了後台程序(background)。我們最不希望終止的進程就是前台進程。
2.        可見進程(visible
        可見進程是一些不再前台,但用戶依然可見的進程,舉個例來說:widget、輸入法等,都屬於visible。這部分進程雖然不在前台,但與我們的使用也密切相關,我們也不希望它們被終止(你肯定不希望時鐘、天氣,新聞等widget被終止,那它們將無法同步,你也不希望輸入法被終止,否則你每次輸入時都需要重新啟動輸入法)
3.        桌面進程(home app
        即launcher,保證在多任務切換之後,可以快速返回到home界面而不需重新加載launcher
4.        次要服務(secondary server
        目前正在運行的一些服務(主要服務,如撥號等,是不可能被進程管理終止的,故這裡只談次要服務),舉例來說:谷歌企業套件,Gmail內部存儲,聯繫人內部存儲等。這部分服務雖然屬於次要服務,但很一些系統功能依然息息相關,我們時常需要用到它們,所以也太希望他們被終止
5.        後台進程(hidden
        即是後台進程(background),就是我們通常意義上理解的啟動後被切換到後台的進程,如瀏覽器,閱讀器等。當程序顯示在屏幕上時,他所運行的進程即為前台進程(foreground),一旦我們按home返回主界面(注意是按home,不是按back),程序就駐留在後台,成為後台進程(background)。後台進程的管理策略有多種:有較為積極的方式,一旦程序到達後台立即終止,這種方式會提高程序的運行速度,但無法加速程序的再次啟動;也有較消極的方式,盡可能多的保留後台程序,雖然可能會影響到單個程序的運行速度,但在再次啟動已啟動的程序時,速度會有所提升。這裡就需要用戶根據自己的使用習慣找到一個平衡點
6.        內容供應節點(content provider
        沒有程序實體,進提供內容供別的程序去用的,比如日曆供應節點,郵件供應節點等。在終止進程時,這類程序應該有較高的優先權
7.        空進程(empty
        沒有任何東西在內運行的進程,有些程序,比如BTE,在程序退出後,依然會在進程中駐留一個空進程,這個進程裡沒有任何數據在運行,作用往往是提高該程序下次的啟動速度或者記錄程序的一些歷史信息。這部分進程無疑是應該最先終止的。
幽靈劊子手LMK (Low Memory Killer)
執行條件
        剩餘內存小於應用定義的APP_MEM值,開始查看adj值列表,kill相應程序。
實現機制
Low Memory Killer的源代碼在kernel/drivers/staging/android/lowmemorykiller.c
  1. module_init(lowmem_init);
  2. module_exit(lowmem_exit);
複製代碼

模塊加載和退出的函數,主要的功能就是register_shrinker和unregister_shrinker結構體lowmem_shrinker。主要是將函數lowmem_shrink註冊到shrinker鏈表裡,在mm_scan調用。
下面詳細的介紹這個函數:
  1. for (i = 0; i < array_size; i++) {
  2.         if (other_file < lowmem_minfree[i]) {
  3.             min_adj = lowmem_adj[i];
  4.             break;
  5.         }
  6.     }
複製代碼


other_file, 系統的空閒內存數,根據上面的邏輯判斷出,low memory killer需要對adj高於多少(min_adj)的進程進行分析是否釋放。
  1. if (nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) {
  2.         lowmem_print(5, "lowmem_shrink %d, %x, return %d\n",
  3.                  nr_to_scan, gfp_mask, rem);
  4.         return rem;
  5.     }
複製代碼

  判斷,系統當前的狀態是否需要進行low memory killer。
  1. for_each_process(p) {
  2.         struct mm_struct *mm;
  3.         struct signal_struct *sig;
  4.         int oom_adj;
  5.         task_lock(p);
  6.         mm = p->mm;
  7.         sig = p->signal;
  8.         if (!mm || !sig) {
  9.             task_unlock(p);
  10.             continue;
  11.         }
  12.         oom_adj = sig->oom_adj;
  13.         if (oom_adj < min_adj) {
  14.             task_unlock(p);
  15.             continue;
  16.         }
  17.         tasksize = get_mm_rss(mm);
  18.         task_unlock(p);
  19.         if (tasksize <= 0)
  20.             continue;
  21.         if (selected) {
  22.             if (oom_adj < selected_oom_adj)
  23.                 continue;
  24.             if (oom_adj == selected_oom_adj &&
  25.                 tasksize <= selected_tasksize)
  26.                 continue;
  27.         }
  28.         selected = p;
  29.         selected_tasksize = tasksize;
  30.         selected_oom_adj = oom_adj;
  31.         lowmem_print(2, "select %d (%s), adj %d, size %d, to kill\n",
  32.                  p->pid, p->comm, oom_adj, tasksize);
  33.     }
複製代碼

對每個sig->oom_adj大於min_adj的進程,找到佔用內存最大的進程存放在selected中。
  1. if (selected) {
  2.         if (fatal_signal_pending(selected)) {
  3.             pr_warning("process %d is suffering a slow death\n",
  4.                    selected->pid);
  5.             read_unlock(&tasklist_lock);
  6.             return rem;
  7.         }
  8.         lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d\n",
  9.                  selected->pid, selected->comm,
  10.                  selected_oom_adj, selected_tasksize);
  11.         force_sig(SIGKILL, selected);
  12.         rem -= selected_tasksize;
  13.     }
複製代碼

發送SIGKILL信息,殺掉該進程。
        在瞭解了其機制和原理之後,我們發現它的實現非常簡單,與標準的Linux OOM機制類似,只是實現方式稍有不同。標準Linux的OOM Killer機制在mm/oom_kill.c中實現,且會被__alloc_pages_may_oom調用(在分配內存時,即mm/page_alloc.c中)。oom_kill.c最主要的一個函數是out_of_memory,它選擇一個bad進程Kill,Kill的方法同樣是通過發送SIGKILL信號。在out_of_memory中通過調用select_bad_process來選擇一個進程Kill,選擇的依據在badness函數中實現,基於多個標準來給每個進程評分,評分最高的被選中並Kill。一般而言,佔用內存越多,oom_adj就越大,也就越有可能被選中。
資源配置
閾值表可以通過/sys/module/lowmemorykiller/parameters/adj和/sys/module/lowmemorykiller/parameters/minfree進行配置,例如在init.rc中:
  1. # Write value must be consistent with the above properties.

  2.    write /sys/module/lowmemorykiller/parameters/adj 0,1,2,7,14,15
  3.    write /proc/sys/vm/overcommit_memory 1
  4.    write /sys/module/lowmemorykiller/parameters/minfree 1536,2048,4096,5120,5632,6144

  5.    class_start default
複製代碼

進程oom_adj同樣可以進行設置,通過write /proc/<PID>/oom_adj ,在init.rc中,init進程的pid為1,omm_adj被配置為-16,永遠不會被殺死。
  1. # Set init its forked children's oom_adj.
  2.    write /proc/1/oom_adj -16
複製代碼

Low memory killer的基本原理我們應該弄清了,正如我前面所說的,進程omm_adj的大小跟進程的類型以及進程被調度的次序有關。進程的類型,可以在ActivityManagerService中清楚的看到:
  1.     static final int EMPTY_APP_ADJ;
  2.     static final int HIDDEN_APP_MAX_ADJ;
  3.     static final int HIDDEN_APP_MIN_ADJ;
  4.     static final int HOME_APP_ADJ;
  5.     static final int BACKUP_APP_ADJ;
  6.     static final int SECONDARY_SERVER_ADJ;
  7.     static final int HEAVY_WEIGHT_APP_ADJ;
  8.     static final int PERCEPTIBLE_APP_ADJ;
  9.     static final int VISIBLE_APP_ADJ;
  10.     static final int FOREGROUND_APP_ADJ;
  11.     static final int CORE_SERVER_ADJ = -12;
  12.     static final int SYSTEM_ADJ = -16;<span style="font-family: Calibri; font-size: 14px;"></span>
複製代碼


ActivityManagerService定義各種進程的oom_adj,CORE_SERVER_ADJ代表一些核心的服務的omm_adj,數值為-12,由前面的分析可知道,這類進程永遠也不會被殺死。
在init.rc中:
  1. # Define the oom_adj values for the classes of processes that can be
  2. # killed by the kernel.  These are used in ActivityManagerService.
  3.     setprop ro.FOREGROUND_APP_ADJ 0
  4.     setprop ro.VISIBLE_APP_ADJ 1
  5.     setprop ro.HOME_APP_ADJ 1
  6.     setprop ro.PERCEPTIBLE_APP_ADJ 2
  7.     setprop ro.HEAVY_WEIGHT_APP_ADJ 3
  8.     setprop ro.SECONDARY_SERVER_ADJ 4
  9.     setprop ro.BACKUP_APP_ADJ 5
  10.     setprop ro.HIDDEN_APP_MIN_ADJ 7
  11.     setprop ro.EMPTY_APP_ADJ 15

  12. # Define the memory thresholds at which the above process classes will
  13. # be killed.  These numbers are in pages (4k).
  14.     setprop ro.FOREGROUND_APP_MEM 2048
  15.     setprop ro.VISIBLE_APP_MEM 3072
  16.     setprop ro.HOME_APP_MEM 3072
  17.     setprop ro.PERCEPTIBLE_APP_MEM 4096
  18.     setprop ro.HEAVY_WEIGHT_APP_MEM 4096
  19.     setprop ro.SECONDARY_SERVER_MEM 10240
  20.     setprop ro.BACKUP_APP_MEM 10240
  21.     setprop ro.HIDDEN_APP_MEM 10240
  22.     setprop ro.EMPTY_APP_MEM 14336

  23. # Write value must be consistent with the above properties.
  24. # Note that the driver only supports 6 slots, so we have combined some of
  25. # the classes into the same memory level; the associated processes of higher
  26. # classes will still be killed first.
  27.     write /sys/module/lowmemorykiller/parameters/adj 0,1,2,4,7,15

  28.     write /proc/sys/vm/overcommit_memory 1
  29.     write /proc/sys/vm/min_free_order_shift 4
  30.   write /sys/module/lowmemorykiller/parameters/minfree 2048,3072,4096,10240,10240,14336

  31.     # Set init its forked children's oom_adj.
  32.     write /proc/1/oom_adj -16
複製代碼

打開程序或者有程序進入後台時都會執行updateOomAdjLocked()函數:

  1. final boolean updateOomAdjLocked() {
  2.         boolean didOomAdj = true;
  3.         final ActivityRecord TOP_ACT = resumedAppLocked();
  4.         final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;

  5.         if (false) {
  6.             RuntimeException e = new RuntimeException();
  7.             e.fillInStackTrace();
  8.             Slog.i(TAG, "updateOomAdj: top=" + TOP_ACT, e);
  9.         }

  10.         mAdjSeq++;

  11.         // Let's determine how many processes we have running vs.
  12.         // how many slots we have for background processes; we may want
  13.         // to put multiple processes in a slot of there are enough of
  14.         // them.
  15.         int numSlots = HIDDEN_APP_MAX_ADJ - HIDDEN_APP_MIN_ADJ + 1;
  16.         int factor = (mLruProcesses.size()-4)/numSlots;
  17.         if (factor < 1) factor = 1;
  18.         int step = 0;
  19.         int numHidden = 0;
  20.         
  21.         // First try updating the OOM adjustment for each of the
  22.         // application processes based on their current state.
  23.         int i = mLruProcesses.size();
  24.         int curHiddenAdj = HIDDEN_APP_MIN_ADJ;
  25.   while (i > 0) {
  26.             i--;
  27.             ProcessRecord app = mLruProcesses.get(i);
  28.             //Slog.i(TAG, "OOM " + app + ": cur hidden=" + curHiddenAdj);
  29.             if (updateOomAdjLocked(app, curHiddenAdj, TOP_APP)) {
  30.                 if (curHiddenAdj < EMPTY_APP_ADJ
  31.                     && app.curAdj == curHiddenAdj) {
  32.                     step++;
  33.                     if (step >= factor) {
  34.                         step = 0;
  35.                         curHiddenAdj++;
  36.                     }
  37.                 }
  38.                 if (app.curAdj >= HIDDEN_APP_MIN_ADJ) {
  39.                     if (!app.killedBackground) {
  40.                         numHidden++;
  41.                         if (numHidden > MAX_HIDDEN_APPS) {
  42.                             Slog.e(TAG, "No longer want " + app.processName
  43.                                     + " (pid " + app.pid + "): hidden #" + numHidden);
  44.                             EventLog.writeEvent(EventLogTags.AM_KILL, app.pid,
  45.                                     app.processName, app.setAdj, "too many background");
  46.                             app.killedBackground = true;
  47.                             Process.killProcessQuiet(app.pid);
  48.                         }
  49.                     }
  50.                 }
  51.             } else {
  52.                 didOomAdj = false;
  53.             }
  54.         }
  55.         // If we return false, we will fall back on killing processes to
  56.         // have a fixed limit.  Do this if a limit has been requested; else
  57.         // only return false if one of the adjustments failed.
  58.         return ENFORCE_PROCESS_LIMIT || mProcessLimit > 0 ? false : didOomAdj;
  59.     }<span style="font-family: Calibri; font-size: 14px;"> </span>
複製代碼


以上就是android內存管理機制的內容了,在一些設備內存比較低的情況下,我們可以對其內存進行優化,從而讓我們的設備運行的更加流暢。


 

臉書網友討論
發表於 2013-11-25 21:17:12 | 顯示全部樓層
牛X海海!!!

版主招募中

您需要登錄後才可以回帖 登錄 | 註冊 |

本版積分規則



Archiver|手機版|小黑屋|免責聲明|TShopping

GMT+8, 2016-12-6 16:51 , Processed in 0.057737 second(s), 22 queries .

本論壇言論純屬發表者個人意見,與 TShopping綜合論壇 立場無關 如有意見侵犯了您的權益 請寫信聯絡我們。

Powered by Discuz! X3.2

© 2001-2013 Comsenz Inc.

快速回復 返回頂部 返回列表