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

如何以编程方式找到我的 Android 应用程序上使用的内存?

我希望有一种方法可以做到。另外,我也该如何获得手机的空闲内存?

答案

请注意,现代操作系统(如 Linux)上的内存使用是一个极其复杂且难以理解的领域。实际上,您正确解释任何数字的机会非常低。 (几乎每当我与其他工程师一起查看内存使用量数字时,对于它们的实际含义总会进行长时间的讨论,这只会导致模糊的结论。)

注意:我们现在有更多有关管理应用程序内存的文档,其中涵盖了许多内容,并且是有关 Android 状态的最新信息。

第一件事可能是阅读本文的最后部分,该部分讨论了如何在 Android 上管理内存:

从 Android 2.0 开始更改服务 API

现在, 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可能是更准确的一个…… 但是,实际上,这只是要点:“获取一粒盐所得到的任何存储信息;通常是非常大的一粒。”

最后是命令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 占用过多而导致这种分页之前,后台进程被杀死。

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

通过调用获取 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 上获取源代码。

这是一项正在进行的工作,但这是我不了解的内容:

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 对象的数组,但是这些对象实际上都没有告诉您它们与哪些 pid 相关联。如果仅传递所有 pid 的数组,则将无法理解结果。据我了解,它的使用使一次传递多个 pid 变得毫无意义,然后,如果是这样,为什么要这样做,以便 activityManager.getProcessMemoryInfo()仅采用一个 int 数组?

Hackbod 的解决方案是 Stack Overflow 的最佳答案之一。它为一个非常模糊的主题照亮。这对我帮助很大。

另一个非常有用的资源是以下必看视频: Google I / O 2011:Android 应用程序的内存管理


更新:

Process Stats 是一项发现您的应用程序如何管理内存的服务,博客文章Process Stats:了解应用程序如何使用 RAM的作者 Dianne Hackborn:

Android Studio 0.8.10 + 引入了一个非常有用的工具,称为Memory Monitor

在此处输入图片说明

它的优点是:

  • 在图形中显示可用和已使用的内存,以及随时间推移的垃圾回收事件。
  • 快速测试应用程序运行速度是否可能与垃圾回收事件过多有关。
  • 快速测试应用程序崩溃是否可能与内存不足有关。

在此处输入图片说明

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

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

1)我想不是,至少不是来自 Java。
2)

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

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

  • 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上一次通话大约需要200 毫秒 。性能开销使该函数对我们无用,因为我们会定期调用它,并且每次调用都非常引人注意(请参阅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指标。您可以在此处找到有关它的更多详细信息:


PS我注意到,该主题仍然缺少实际和简单的代码段,这些代码段在性能不是关键要求的情况下如何估计进程的私有内存使用情况:

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;

在 android studio 3.0 中,他们引入了 android-profiler 来帮助您了解您的应用如何使用 CPU,内存,网络和电池资源。

https://developer.android.com/studio/profile/android-profiler

在此处输入图片说明

有很多回答上述这肯定会帮助你,但(后买得起和研究亚洲开发银行存储工具 2 天)我想我可以用我的意见帮助了。

正如Hackbod 所说: 因此,如果您要获取实际映射到每个进程中的所有物理 RAM,并将所有进程加起来,则最终结果可能要比实际总 RAM 大得多。因此无法获得每个进程的确切内存量。

但是您可以通过某种逻辑接近它。我将告诉您如何。

上面提到了一些类似android.os.Debug.MemoryInfoActivityManager.getMemoryInfo() API,您可能已经在阅读和使用它们,但我将讨论其他方式

因此,首先您需要成为 root 用户才能使其正常运行。通过在进程中执行su以 root 特权进入控制台,并获取其output and input stream 。然后在 ouputstream 中传递id\n (输入)并将其写入过程输出,如果将得到一个包含uid=0 ,则您是 root 用户。

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

当您获得进程的输出流时,请使用\n而不是 id 来命令(procrank,dumpsys meminfo 等),并获取其输入inputstream并读取,然后将流存储为 bytes [],char [] 等。使用原始数据。 。然后您就完成了!

许可:

<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: 结果

您可以从控制台以单个字符串获取原始数据,而不是在任何情况下从任何 API 获取原始数据,由于要手动将其分离,因此存储起来很复杂

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