首页 文章

如何在Android中发现我的应用程序的内存使用情况?

提问于
浏览
755

如何以编程方式查找Android应用程序中使用的内存?

我希望有办法做到这一点 . 另外,我如何获得手机的免费记忆?

8 回答

  • 72

    请注意,像Linux这样的现代操作系统上的内存使用是一个非常复杂且难以理解的领域 . 事实上,你实际正确地解释你得到的任何数字的可能性非常低 . (几乎每次我与其他工程师一起查看内存使用数字时,总会有很长时间讨论它们实际上意味着什么只会导致模糊的结论 . )

    Note: we now have much more extensive documentation on Managing Your App's Memory that covers much of the material here and is more up-to-date with the state of Android.

    首先要阅读本文的最后一部分,其中讨论了如何在Android上管理内存:

    Service API changes starting with Android 2.0

    现在 ActivityManager.getMemoryInfo() 是我们用于查看总体内存使用情况的最高级API . 这主要是为了帮助应用程序测量系统与后台进程没有更多内存的接近程度,从而需要开始杀死所需的进程,如服务 . 对于纯Java应用程序,这应该没用,因为Java堆限制部分是为了避免一个应用程序能够强调系统到此为止 .

    更低级别,您可以使用Debug API获取有关内存使用情况的原始内核级信息:android.os.Debug.MemoryInfo

    注意从2.0开始,还有一个API, ActivityManager.getProcessMemoryInfo ,用于获取有关另一个进程的信息:ActivityManager.getProcessMemoryInfo(int[])

    这将返回包含所有这些数据的低级MemoryInfo结构:

    /** The proportional set size for dalvik. */
        public int dalvikPss;
        /** The private dirty pages used by dalvik. */
        public int dalvikPrivateDirty;
        /** The shared dirty pages used by dalvik. */
        public int dalvikSharedDirty;
    
        /** The proportional set size for the native heap. */
        public int nativePss;
        /** The private dirty pages used by the native heap. */
        public int nativePrivateDirty;
        /** The shared dirty pages used by the native heap. */
        public int nativeSharedDirty;
    
        /** The proportional set size for everything else. */
        public int otherPss;
        /** The private dirty pages used by everything else. */
        public int otherPrivateDirty;
        /** The shared dirty pages used by everything else. */
        public int otherSharedDirty;
    

    但至于 PssPrivateDirtySharedDirty 之间的区别是什么......现在好玩的开始了 .

    Android(以及一般的Linux系统)中的大量内存实际上是在多个进程之间共享的 . 因此,进程使用多少内存实际上并不清楚 . 将该页面分页添加到磁盘(更不用说我们在Android上不使用的交换),它甚至不太清楚 .

    因此,如果您将实际映射到的每个物理RAM都放到每个进程中,并将所有进程相加,那么最终可能会得到比实际总RAM大得多的数字 .

    Pss 数字是内核计算的一个度量,它考虑了内存共享 - 基本上每个进程中的RAM页面都按照使用该页面的其他进程数量的比例进行缩放 . 通过这种方式,您可以(在理论上)将pss添加到所有进程中以查看它们正在使用的总RAM,并比较进程之间的pss以大致了解它们的相对权重 .

    另一个有趣的指标是 PrivateDirty ,它基本上是进程内部无法分页到磁盘的RAM数量(它不受磁盘上相同数据的支持),并且不与任何其他进程共享 . 另一种看待这种情况的方法是当该进程消失时系统可用的RAM(并且可能很快被包含在缓存和其它用途中) .

    这就是SDK API . 但是,作为开发人员,您可以使用设备做更多事情 .

    使用 adb ,您可以获得有关正在运行的系统的内存使用的大量信息 . 常见的一个是命令 adb shell dumpsys meminfo ,它会发出一堆关于每个Java进程的内存使用情况的信息,包含上述信息以及其他各种信息 . 您还可以查看单个进程的名称或pid以查看,例如 adb shell dumpsys meminfo system 给我系统进程:

    ** MEMINFO in pid 890 [system] **
                        native   dalvik    other    total
                size:    10940     7047      N/A    17987
           allocated:     8943     5516      N/A    14459
                free:      336     1531      N/A     1867
               (Pss):     4585     9282    11916    25783
      (shared dirty):     2184     3596      916     6696
        (priv dirty):     4504     5956     7456    17916
    
     Objects
               Views:      149        ViewRoots:        4
         AppContexts:       13       Activities:        0
              Assets:        4    AssetManagers:        4
       Local Binders:      141    Proxy Binders:      158
    Death Recipients:       49
     OpenSSL Sockets:        0
    
     SQL
                heap:      205          dbFiles:        0
           numPagers:        0   inactivePageKB:        0
        activePageKB:        0
    

    顶部是主要部分,其中 size 是特定堆的地址空间中的总大小, allocated 是堆认为它具有的实际分配的kb, free 是堆具有用于其他分配的剩余kb,以及 psspriv dirty 与之前讨论的相同,特定于与每个堆相关联的页面 .

    如果您只想查看所有进程的内存使用情况,可以使用命令 adb shell procrank . 在同一系统上输出的内容如下:

    PID      Vss      Rss      Pss      Uss  cmdline
      890   84456K   48668K   25850K   21284K  system_server
     1231   50748K   39088K   17587K   13792K  com.android.launcher2
      947   34488K   28528K   10834K    9308K  com.android.wallpaper
      987   26964K   26956K    8751K    7308K  com.google.process.gapps
      954   24300K   24296K    6249K    4824K  com.android.phone
      948   23020K   23016K    5864K    4748K  com.android.inputmethod.latin
      888   25728K   25724K    5774K    3668K  zygote
      977   24100K   24096K    5667K    4340K  android.process.acore
    ...
       59     336K     332K      99K      92K  /system/bin/installd
       60     396K     392K      93K      84K  /system/bin/keystore
       51     280K     276K      74K      68K  /system/bin/servicemanager
       54     256K     252K      69K      64K  /system/bin/debuggerd
    

    这里的 VssRss 列基本上都是噪声(这些是进程的直接地址空间和RAM使用情况,如果你将进程间的RAM使用量加起来就会得到一个非常大的数字) .

    Pss 就像我们以前见过的那样, UssPriv Dirty .

    有趣的是这里需要注意的是: PssUss 与我们在 meminfo 中看到的略有不同(或稍微不同) . 这是为什么?那么procrank使用不同的内核机制来收集它的数据,而不是 meminfo ,它们给出的结果略有不同 . 这是为什么?老实说,我没有任何线索 . 我相信 procrank 可能更准确......但实际上,这只是留下了重点:"take any memory info you get with a grain of salt; often a very large grain."

    最后是命令 adb shell cat /proc/meminfo ,它给出了系统总内存使用情况的摘要 . 这里有很多数据,只有最初的几个数字值得讨论(其余的数据很少被人理解,我对这些人的问题经常导致相互矛盾的解释):

    MemTotal:         395144 kB
    MemFree:          184936 kB
    Buffers:             880 kB
    Cached:            84104 kB
    SwapCached:            0 kB
    

    MemTotal 是内核和用户空间可用的内存总量(通常小于设备的实际物理RAM,因为无线电,DMA缓冲区等需要一些RAM) .

    MemFree 是根本没有使用的RAM量 . 你在这里看到的数字非常高;通常在Android系统上,这只有几MB,因为我们尝试使用可用内存来保持进程运行

    Cached 是用于文件系统缓存和其他此类事物的RAM . 为此,典型系统需要大约20MB才能避免陷入错误的寻呼状态;针对特定系统调整Android内存不足以确保后台进程在缓存的RAM被其过多消耗之前被杀死以导致此类分页 .

  • 19

    是的,您可以通过编程方式获取内存信息,并决定是否进行内存密集型工作 .

    通过调用获取VM堆大小:

    Runtime.getRuntime().totalMemory();
    

    通过调用以获取分配的VM内存:

    Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
    

    通过调用以获取VM堆大小限制:

    Runtime.getRuntime().maxMemory()
    

    通过调用获取本机分配的内存:

    Debug.getNativeHeapAllocatedSize();
    

    我做了一个应用程序来弄清楚OutOfMemoryError行为并监视内存使用情况 .

    https://play.google.com/store/apps/details?id=net.coocood.oomresearch

    您可以在https://github.com/coocood/oom-research获取源代码

  • 0

    这是一项正在进行中的工作,但这是我不明白的:

    ActivityManager activityManager = (ActivityManager) context.getSystemService(ACTIVITY_SERVICE);
    MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
    activityManager.getMemoryInfo(memoryInfo);
    
    Log.i(TAG, " memoryInfo.availMem " + memoryInfo.availMem + "\n" );
    Log.i(TAG, " memoryInfo.lowMemory " + memoryInfo.lowMemory + "\n" );
    Log.i(TAG, " memoryInfo.threshold " + memoryInfo.threshold + "\n" );
    
    List<RunningAppProcessInfo> runningAppProcesses = activityManager.getRunningAppProcesses();
    
    Map<Integer, String> pidMap = new TreeMap<Integer, String>();
    for (RunningAppProcessInfo runningAppProcessInfo : runningAppProcesses)
    {
        pidMap.put(runningAppProcessInfo.pid, runningAppProcessInfo.processName);
    }
    
    Collection<Integer> keys = pidMap.keySet();
    
    for(int key : keys)
    {
        int pids[] = new int[1];
        pids[0] = key;
        android.os.Debug.MemoryInfo[] memoryInfoArray = activityManager.getProcessMemoryInfo(pids);
        for(android.os.Debug.MemoryInfo pidMemoryInfo: memoryInfoArray)
        {
            Log.i(TAG, String.format("** MEMINFO in pid %d [%s] **\n",pids[0],pidMap.get(pids[0])));
            Log.i(TAG, " pidMemoryInfo.getTotalPrivateDirty(): " + pidMemoryInfo.getTotalPrivateDirty() + "\n");
            Log.i(TAG, " pidMemoryInfo.getTotalPss(): " + pidMemoryInfo.getTotalPss() + "\n");
            Log.i(TAG, " pidMemoryInfo.getTotalSharedDirty(): " + pidMemoryInfo.getTotalSharedDirty() + "\n");
        }
    }
    

    为什么PID不映射到activityManager.getProcessMemoryInfo()中的结果?显然,您希望使得结果数据有意义,那么为什么Google难以将结果关联起来呢?如果我想处理整个内存使用情况,当前系统甚至不能正常工作,因为返回的结果是android.os.Debug.MemoryInfo对象的数组,但这些对象实际上都没有告诉你它们与哪些pids相关联 . 如果您只是传入所有pid的数组,则无法理解结果 . 据我了解它的使用,它一次传递多个pid是没有意义的,然后如果是这样的话,为什么要使activityManager.getProcessMemoryInfo()只接受一个int数组?

  • 49

    Hackbod是Stack Overflow上最好的答案之一 . 它为一个非常模糊的主题提供了亮点 . 这对我帮助很大 .

    另一个非常有用的资源是必看视频:Google I/O 2011: Memory management for Android Apps


    UPDATE:

    Process Stats,一种发现你的应用管理内存的服务,在Dianne Hackborn的博客文章Process Stats: Understanding How Your App Uses RAM中有所解释:

  • 3

    Android Studio 0.8.10引入了一个名为Memory Monitor的非常有用的工具 .

    enter image description here

    它有什么好处:

    在图表中显示可用和已用内存,以及随时间推移的垃圾回收事件 . 快速测试app缓慢是否与过多的垃圾收集事件有关 . 快速测试应用程序崩溃是否与内存不足有关 .

    enter image description here

    图1.在Android Memory Monitor上强制执行GC(垃圾收集)事件

    通过使用它,您可以获得有关应用程序RAM实时消耗的大量信息 .

  • 24

    1)我猜不是,至少不是来自Java .
    2)

    ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
    MemoryInfo mi = new MemoryInfo();
    activityManager.getMemoryInfo(mi);
    Log.i("memory free", "" + mi.availMem);
    
  • 16

    我们发现获取当前进程总内存的所有标准方法都存在一些问题 .

    • Runtime.getRuntime().totalMemory() :仅返回JVM内存

    • ActivityManager.getMemoryInfo()Process.getFreeMemory() 以及基于 /proc/meminfo 的任何其他内容 - 返回有关所有组合过程的内存信息(例如android_util_Process.cpp

    • Debug.getNativeHeapAllocatedSize() - 使用 mallinfo() ,它返回有关 malloc() 和相关函数执行的内存分配的信息(请参阅android_os_Debug.cpp

    • Debug.getMemoryInfo() - 完成工作但速度太慢了 . 单个呼叫在 Nexus 6 上需要大约 200ms . 性能开销使得这个功能对我们来说毫无用处,因为我们经常调用它并且每个调用都非常明显(参见android_os_Debug.cpp

    • ActivityManager.getProcessMemoryInfo(int[]) - 在内部调用 Debug.getMemoryInfo() (参见ActivityManagerService.java

    最后,我们最终使用以下代码:

    const long pageSize = 4 * 1024; //`sysconf(_SC_PAGESIZE)`
    string stats = File.ReadAllText("/proc/self/statm");
    var statsArr = stats.Split(new [] {' ', '\t', '\n'}, 3);
    
    if( statsArr.Length < 2 )
        throw new Exception("Parsing error of /proc/self/statm: " + stats);
    
    return long.Parse(statsArr[1]) * pageSize;
    

    它返回 VmRSS metric . 您可以在此处找到有关它的更多详细信息:onetwothree .


    P.S. 我注意到如果性能不是关键要求,主题仍然缺乏如何估计进程的私有内存使用情况的实际和简单的代码片段:

    Debug.MemoryInfo memInfo = new Debug.MemoryInfo();
    Debug.getMemoryInfo(memInfo);
    long res = memInfo.getTotalPrivateDirty();
    
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 
        res += memInfo.getTotalPrivateClean(); 
    
    return res * 1024L;
    
  • 981

    上面有很多答案肯定对你有所帮助,但是(经过两天的adb内存工具研究和支持)我想我也可以帮助我 opinion .

    因为Hackbod says : Thus if you were to take all of the physical RAM actually mapped in to each process, and add up all of the processes, you would probably end up with a number much greater than the actual total RAM.所以你无法获得每个进程的确切内存量 .

    但是你可以通过一些逻辑来接近它......我将告诉你如何......

    上面提到的一些API如android.os.Debug.MemoryInfo和ActivityManager.getMemoryInfo(),您可能已经阅读并使用了但我会谈论其他方式

    首先,您需要成为root用户才能使其正常工作 . 通过在进程中执行 su 进入具有root权限的控制台并获取其 output and input stream . 然后在ouputstream中传递 id\n (输入)并将其写入过程输出,如果将获得输入流包含 uid=0you are root user.

    现在,这是您将在上述过程中使用的逻辑

    当您获得进程 pass you command (procrank, dumpsys meminfo etc...) with \n instead of id 的ouputstream并获取其 inputstream 并读取时,将该流存储在bytes [],char []等中..使用 raw data..and您已完成!!!!!

    许可:

    <uses-permission android:name="android.permission.FACTORY_TEST"/>
    

    检查您是否是root用户:

    // su command to get root access
    Process process = Runtime.getRuntime().exec("su");         
    DataOutputStream dataOutputStream = 
                               new DataOutputStream(process.getOutputStream());
    DataInputStream dataInputStream = 
                               new DataInputStream(process.getInputStream());
    if (dataInputStream != null && dataOutputStream != null) {
       // write id to console with enter
       dataOutputStream.writeBytes("id\n");                   
       dataOutputStream.flush();
       String Uid = dataInputStream.readLine();
       // read output and check if uid is there
       if (Uid.contains("uid=0")) {                           
          // you are root user
       } 
    }
    

    使用 su 执行命令

    Process process = Runtime.getRuntime().exec("su");         
    DataOutputStream dataOutputStream = 
                               new DataOutputStream(process.getOutputStream());
    if (dataOutputStream != null) {
     // adb command
     dataOutputStream.writeBytes("procrank\n");             
     dataOutputStream.flush();
     BufferedInputStream bufferedInputStream = 
                         new BufferedInputStream(process.getInputStream());
     // this is important as it takes times to return to next line so wait
     // else you with get empty bytes in buffered stream 
     try {
           Thread.sleep(10000);
     } catch (InterruptedException e) {                     
           e.printStackTrace();
     }
     // read buffered stream into byte,char etc.
     byte[] bff = new byte[bufferedInputStream.available()];
     bufferedInputStream.read(bff);
     bufferedInputStream.close();
     }
    }
    

    logcat:
    result

    您从控制台获取单个字符串中的原始数据,而不是在某些实例中从任何API获取原始数据,这很难存储,因为您需要手动分离它 .

    这只是一个尝试,如果我错过了什么,请建议我

相关问题