Android开发

Android 内存管理基础

2024-01-06  本文已影响0人  DarcyZhou

本文转载自:Android内存管理基础

1.内存管理概览

  Android运行时(ART)和Dalvik虚拟机使用分页和内存映射来管理内存。

1.1 内存类型

  Android设备包含三种不同类型的内存:RAM、zRAM和存储器。请注意,CPU和GPU访问同一RAM。

内存管理01.png

1.2 物理内存和虚拟内存

1.2.1 物理内存

  物理内存是指真实存在的插在主板内存槽上的内存条的容量,是Android系统中所有进程共享的存储空间。

1.2.2 虚拟内存

  当进程申请内存时得到的是虚拟内存,虚拟内存是对物理内存的抽象;每个进程操作的是自己的虚拟内存,并由CPU负责虚拟内存地址与实际物理地址的映射。

内存管理02.png

需要注意的是,假如进程没有使用这些虚拟内存,那么不会建立虚拟地址与物理内存的映射。例如:

void main()
{
    for (int i= 0; i < 512; i++)
    {
        malloc(1024 * 1024);
    }
}
// 结果分析:malloc只申请了内存并未使用,因此进程的虚拟内存大小VSZ是539M,而物理内存RSS只有3M。

进程在真正使用内存时,虚拟内存与物理内存才建立映射关系,例如:

vector<void*> mems;
void main()
{
    for (int i= 0; i < 512; i++)
    {
        void *p = malloc(1024 * 1024);
        memset(p, 0, 1024 * 1024);
        mems.push_back(p);
    }
}
// 结果分析:用memset对申请的内存执行了写入操作,因此进程的虚拟内存大小VSZ是539M,而物理内存RSS增加到了527M。

1.2.3 虚拟内存的好处

(1)使应用程序不必管理共享内存空间;由于所有进程共享物理内存,所以物理内存常常是不连续的,而对应用程序来说,虚拟内存的内存地址是连续的,由CPU负责虚拟内存地址的映射;

(2)能够在进程之间共享库使用的内存;详见1.2.4;

(3)由于内存隔离而提高了安全性;

(4)通过使用分页或分段技术,可使用比物理内存大小更多的内存。

1.2.4 共享库内存

  共享库所占内存:同一个共享库的代码在物理内存中只会存在一份,这块内存会映射到不同进程的虚拟内存中,对各个进程来说,就像是自己私有的内存一样,而对于系统来说,则是节省了内存的资源。

  Android系统实现跨进程共享RAM页面的方式有:

1.3 VSS RSS PSS USS区别

  一个进程的内存信息可以用VSS RSS PSS USS来表示,含义如下:

内存管理03.png

举例:

已知:
(1)共享库libTest.so所占内存共100M,被进程A和进程B共同占用;
(2)进程A申请了100M内存,但是实际使用了60M内存;

则进程A的:
(1)VSS=100M+100M=200M;
(2)RSS=60M+100M=150M; 
(3)PSS=60M+100/2=110M; 
(4)USS=60M;

所以,如果想要知道所有进程使用了多少内存,那么可以使用PSS或RSS。计算PSS需要花很长时间,因为系统需要确定共享的页面以及共享页面的进程数量。RSS不区分共享和非共享页面(因此计算起来更快),更适合跟踪内存分配量的变化。

1.4 Android系统的页面置换

  RAM分为多个“页面”。通常,每个页面为4KB的内存。

2.内存分析方法

2.1 adb常用内存分析命令

2.1.1 ps命令

  ps命令可以列出Android系统中当前所有进程的信息:

>adb shell ps
USER           PID  PPID     VSZ    RSS WCHAN            ADDR S NAME           
root             1     0   60588   1360 0                   0 S init
root             2     0   60588   1360 0                   0 S [kthreadd]
root           144     2       0      0 0                   0 S [kswapd0]
root          1034     1 4286332  22916 0                   0 S zygote64
root          1035     1 1616152  25532 0                   0 S zygote
system         570     1   11356   1196 0                   0 S servicemanager
system        1655   958 4786828 277192 0                   0 S system_server
u0_a1166      9742  1035 1716624     20 0                   0 T com.bc.sample
root           727     1    9968   2204 0                   0 S lmkd

>adb shell ps | grep com.bc.sample
u0_a1166      9742  1035 1716624     20 0                   0 T com.bc.sample

含义解释:

2.1.2 dumpsys meminfo

  dumpsys meminfo命令可以根据进程名(一般是包名)获取进程当前的内存使用情况:

#命令行输入
>adb shell dumpsys meminfo com.bc.sample
Applications Memory Usage (in Kilobytes):
Uptime: 654784890 Realtime: 2062282674

** MEMINFO in pid 13705 [com.bc.sample] **
                   Pss  Private  Private  SwapPss     Heap     Heap     Heap
                 Total    Dirty    Clean    Dirty     Size    Alloc     Free
                ------   ------   ------   ------   ------   ------   ------
  Native Heap     3445     3396        0       50     7680     6140     1539
  Dalvik Heap      667      612        0       68     2671     1135     1536
 Dalvik Other      360      348        0        0                           
        Stack       48       48        0        0                           
       Ashmem        2        0        0        0                           
      Gfx dev     1664     1576       88        0                           
    Other dev       12        0       12        0                           
     .so mmap     1528      180        0       31                           
    .apk mmap      166        0       28        0                           
    .ttf mmap       58        0        0        0                           
    .dex mmap     3834        4     1744        0                           
    .oat mmap      102        0        0        0                           
    .art mmap     3368     3036        4        7                           
   Other mmap       13        4        0        0                           
   EGL mtrack    18496    18496        0        0                           
    GL mtrack    14964    14964        0        0                           
      Unknown      473      464        0        6                           
        TOTAL    49362    43128     1876      162    10351     7275     3075 

 App Summary
                       Pss(KB)
                        ------
           Java Heap:     3652
         Native Heap:     3396
                Code:     1956
               Stack:       48
            Graphics:    35124
       Private Other:      828
              System:     4358 
               TOTAL:    49362       TOTAL SWAP PSS:      162

 Objects
               Views:       22         ViewRootImpl:        2
         AppContexts:        3           Activities:        1
              Assets:        3        AssetManagers:        4
       Local Binders:       15        Proxy Binders:       21
       Parcel memory:        3         Parcel count:       13
    Death Recipients:        2      OpenSSL Sockets:        0
            WebViews:        0 

 Dalvik
         isLargeHeap:    false 

 SQL
         MEMORY_USED:        0
  PAGECACHE_OVERFLOW:        0          MALLOC_SIZE:        0

2.1.3 adb shell cat /proc/meminfo

  读取/proc/meminfo文件,可以获取系统整体内存使用情况:

>adb shell cat /proc/meminfo
MemTotal:        5826364 kB   //内存总大小
MemFree:           85532 kB   //空闲内存总大小
MemAvailable:    3540368 kB
Buffers:          127840 kB
Cached:          3373548 kB
SwapCached:        10952 kB   
Active:          2928196 kB
Inactive:        1346956 kB
Active(anon):     427512 kB
Inactive(anon):   432012 kB
Active(file):    2500684 kB
Inactive(file):   914944 kB
Unevictable:       81280 kB
Mlocked:           81280 kB
SwapTotal:       2097148 kB   //交换空间总大小
SwapFree:        1449580 kB   //交换空间空闲大小

2.1.4 procrank

  procrank命令可以获取系统中所有进程的VSS、RSS、PSS、USS的大小:

>adb shell procrank
PID Vss      Rss     Pss     Uss    cmdline
380 2195880K 210712K 111133K 67400K system_server
146 1561656K 63080  47628K  42036K zygote
868 1587984K 69500K  25414K  18480K com.android.launcher
145 2122596K 73552K  23428K  10972K zygote64

2.2 Android Studio Profiler

  Android studio自带的profiler工具可以选择MEMORY查看当前内存使用情况,如下所示:

内存管理04.png

其中列出了所选进程的内存使用情况,这里的java heap、native heap都是指虚拟内存大小:

内存管理05.png

2.3 代码获取

2.3.1 手机总内存情况MemoryInfo

  MemoryInfo内包含的内存信息如下:

public static class MemoryInfo implements Parcelable {

    public long availMem;
    public long totalMem;
    public long threshold;
    public boolean lowMemory;
}

获取MemoryInfo的方式如下:

// Activity中已经实现了获取activityManager,自定义类中通过这种方式获取ActivityManager:
ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
am.getMemoryInfo(memoryInfo);

Log.v(TAG,"手机总内存:" + memoryInfo.totalMem);
Log.v(TAG,"手机当前可用物理内存:" + memoryInfo.availMem);
Log.v(TAG,"Android系统认为低内存状态的阈值:" + memoryInfo.threshold);

2.3.2 读取/proc/meminfo文件

  读取/proc/meminfo文件,同样可获取系统总内存情况,可获取的信息见2.1.3;代码如下:

public static long getRamSwapFree() {
    // // 系统内存信息文件
    String str1 = "/proc/meminfo"; 
    String str2;
    String[] arrayOfString;
    long swapFree = 0;
    InputStreamReader localFileReader = null;
    BufferedReader localBufferedReader = null;
    try {
        localFileReader = new InputStreamReader(new FileInputStream(str1), Charset.forName("utf-8"));
        localBufferedReader = new BufferedReader(localFileReader, 8192);
        while ((str2 = localBufferedReader.readLine()) != null) {
            arrayOfString = str2.split("\s+");
            if (arrayOfString.length >= 2) {
                String memType = arrayOfString[0];
                // 下面是个例子,类似地可获取其他更多信息
                if (TextUtils.equals(arrayOfString[0], "SwapFree:")) {
                    swapFree = Integer.parseInt(arrayOfString[1]);
                }
            }
        }
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        if (null != localBufferedReader) {
            try {
                localBufferedReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        if (null != localFileReader) {
            try {
                localFileReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    return swapFree / (1024);
}

2.3.3 获取当前进程PSS物理内存

Debug.MemoryInfo memoryInfo = new Debug.MemoryInfo();
Debug.getMemoryInfo(memoryInfo);
Log.v(TAG,"总PSS:" + memoryInfo.getTotalPss());
Log.v(TAG,"Java PSS:" + memoryInfo.dalvikPss);
Log.v(TAG,"Native PSS:" + memoryInfo.nativePss);
Log.v(TAG,"总RSS:"+memoryInfo.getTotalRss());

// 安卓6.0以上还可以使用如下方式:
Log.v(TAG,"安卓6.0以上,total-pss:" + memoryInfo.getMemoryStat("summary.total-pss"));
Log.v(TAG,"安卓6.0以上,java-heap:" + memoryInfo.getMemoryStat("summary.java-heap"));
Log.v(TAG,"安卓6.0以上,native-heap:" + memoryInfo.getMemoryStat("summary.native-heap"));
Log.v(TAG,"安卓6.0以上,code:" + memoryInfo.getMemoryStat("summary.code"));
Log.v(TAG,"安卓6.0以上,graphics:" + memoryInfo.getMemoryStat("summary.graphics"));

2.3.4 获取当前进程VSS虚拟内存

(1)获取java堆VSS

long javaMaxHeapSize = Runtime.getRuntime().maxMemory();
long javaTotalHeapSize = Runtime.getRuntime().totalMemory();
long javaFreeHeapSize = Runtime.getRuntime().freeMemory();
Log.v(TAG,"java堆最大内存大小:" + javaMaxHeapSize);
Log.v(TAG,"当前java堆内存大小:" + javaTotalHeapSize);
Log.v(TAG,"当前java堆空闲内存大小:" + javaFreeHeapSize);

(2)获取native堆VSS

long nativeHeap = Debug.getNativeHeapSize();
long nativeAllocHeap = Debug.getNativeHeapAllocatedSize();
long nativeFreeSize = Debug.getNativeHeapFreeSize();
Log.v(TAG,"当前native堆内存大小:" + nativeHeap);
Log.v(TAG,"当前native堆已分配内存大小:" + nativeAllocHeap);
Log.v(TAG,"当前native堆空闲内存大小:" + nativeFreeSize);

3.低内存状态

3.1 LMK进程

  很多时候,kswapd不能为系统释放足够的内存。在这种情况下,系统会使用onTrimMemory()通知应用内存不足,应该减少其分配量。如果这还不够,内核会开始终止进程以释放内存。它会使用低内存终止守护进程 (LMK) 来执行此操作。

  LMK使用一个名为oom_adj_score的“内存不足”分值来确定正在运行的进程的优先级,以此决定要终止的进程。最高得分的进程最先被终止。后台应用最先被终止,系统进程最后被终止。下表列出了从高到低的LMK评分类别。评分最高的类别,即第一行中的项目将最先被终止(设备制造商可以更改LMK的行为。):

内存管理06.png

以下是上表中各种类别的说明:

3.2 onTrimMemory()

  上面介绍过,当内存不足时,系统会回调onTrimMemory()来通知应用程序内存不足,onTrimMemory()的定义如下:

public interface ComponentCallbacks2 extends ComponentCallbacks {
    void onTrimMemory(@TrimMemoryLevel int level);
}

对于Application、Activity、Fragment、Service、ContentProvider,源码中已经继承了ComponentCallbacks2接口,如果开发者需要自定义onTrimMemory的更多行为(例如收到内存不足回调时,进行一些内存释放工作),直接重写父类的方法即可。

  如果开发者需要在自定义的类中接收ComponentCallbacks2系统回调,可按如下方式注册:

public class ComponentCallbacksDemo implements ComponentCallbacks2{

    public ComponentCallbacksDemo() {
        // 注册ComponentCallbacks回调
        getApplicationContext().registerComponentCallbacks(this);
    }

    @Override
    public void onTrimMemory(int level) {
        if (level >= TRIM_MEMORY_UI_HIDDEN) {
            // todo do some free memory work
        }
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {}

    @Override
    public void onLowMemory() {}
}

当onTrimMemory(int level)回调时,level表示内存等级,level有以下几种类型:

/**
 * Level for {@link #onTrimMemory(int)}: the process is nearing the end
 * of the background LRU list, and if more memory isn't found soon it will
 * be killed.
 */
static final int TRIM_MEMORY_COMPLETE = 80;

/**
 * Level for {@link #onTrimMemory(int)}: the process is around the middle
 * of the background LRU list; freeing memory can help the system keep
 * other processes running later in the list for better overall performance.
 */
static final int TRIM_MEMORY_MODERATE = 60;

/**
 * Level for {@link #onTrimMemory(int)}: the process has gone on to the
 * LRU list.  This is a good opportunity to clean up resources that can
 * efficiently and quickly be re-built if the user returns to the app.
 */
static final int TRIM_MEMORY_BACKGROUND = 40;

/**
 * Level for {@link #onTrimMemory(int)}: the process had been showing
 * a user interface, and is no longer doing so.  Large allocations with
 * the UI should be released at this point to allow memory to be better
 * managed.
 */
static final int TRIM_MEMORY_UI_HIDDEN = 20;

/**
 * Level for {@link #onTrimMemory(int)}: the process is not an expendable
 * background process, but the device is running extremely low on memory
 * and is about to not be able to keep any background processes running.
 * Your running process should free up as many non-critical resources as it
 * can to allow that memory to be used elsewhere.  The next thing that
 * will happen after this is {@link #onLowMemory()} called to report that
 * nothing at all can be kept in the background, a situation that can start
 * to notably impact the user.
 */
static final int TRIM_MEMORY_RUNNING_CRITICAL = 15;

/**
 * Level for {@link #onTrimMemory(int)}: the process is not an expendable
 * background process, but the device is running low on memory.
 * Your running process should free up unneeded resources to allow that
 * memory to be used elsewhere.
 */
static final int TRIM_MEMORY_RUNNING_LOW = 10;

/**
 * Level for {@link #onTrimMemory(int)}: the process is not an expendable
 * background process, but the device is running moderately low on memory.
 * Your running process may want to release some unneeded resources for
 * use elsewhere.
 */
static final int TRIM_MEMORY_RUNNING_MODERATE = 5;

3.3 onLowMemory()

  onTrimMemory是API 14及以上可使用,对于低系统可使用onLowMemory(),其等同于ComponentCallbacks2的当onTrimMemory(TRIM_MEMORY_COMPLETE}回调,对于高系统建议实现nTrimMemory()即可。

public interface ComponentCallbacks {
    void onConfigurationChanged(@NonNull Configuration newConfig);
    void onLowMemory();
}
上一篇 下一篇

猜你喜欢

热点阅读