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


799

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

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


2
或者,如果有人想进一步了解所有这些信息,请参考http://elinux.org/Android_Memory_Usage

Answers:


1007

请注意,现代操作系统(例如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;

但是关于,,和... 之间的区别是什么Pss,现在好了。PrivateDirtySharedDirty

实际上,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给我系统进程:

** pid 890 [系统]中的MEMINFO **
                    达尔维克人其他总计
            尺寸:10940 7047 N / A 17987
       已分配:8943 5516 N / A 14459
            免费:336 1531不适用1867
           (Pss):4585 9282 11916 25783
  (共享共享):2184 3596 916 6696
    (私人肮脏):4504 5956 7456 17916

 对象
           浏览次数:149查看根数:4
     AppContexts:13活动:0
          资产:4 AssetManagers:4
   本地绑定器:141代理绑定器:158
死亡人数:49
 OpenSSL套接字:0

 的SQL
            堆:205 db文件: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合子
  977 24100K 24096K 5667K 4340K android.process.acore
...
   59 336K 332K 99K 92K / system / bin /已安装
   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提供了系统整体内存使用情况的摘要。这里有很多数据,只有前几个值得讨论(剩下的几个人很少理解,而我对这几个人的问题经常会引起相互矛盾的解释):

内存总量:395144 kB
内存免费:184936 kB
缓冲区:880 kB
缓存的:84104 kB
交换快取:0 kB

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

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

Cached是用于文件系统缓存和其他此类事物的RAM。典型的系统将需要大约20MB的内存,以避免进入不良的分页状态。针对特定系统对Android内存不足杀手进行了调整,以确保在后台进程消耗过多的缓存RAM导致后台分页之前,杀死后台进程。


1
看一下pixelbeat.org/scripts/ps_mem.py,它使用上述技术来显示程序使用的RAM
pixelbeat 2010年

17
很好写!我在macgyverdev.blogspot.com/2011/11/…上写了一篇有关内存管理和使用不同工具来检查您的堆使用情况的文章,如果有人觉得有用的话。
JohanNorén

3
“ dalvik”和“ native”这两列到底是什么?
dacongy 2012年

1
“本地”,“达尔维克”,“其他”到底是什么?在我的应用中,“其他”是否非常庞大?我该如何减少呢?
兰德里,2012年

我可以使用“亚行外壳dumpsys meminfo中”,而是“亚行外壳procrank”告诉我。‘/系统/ bin / sh的:procrank:未找到’我已经不是clue.Wish你能帮助我
雨果

79

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

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


7
此运行时是否会按当前进程或整个系统堆返回内存使用情况?
Mahendran 2014年

1
totalMemory()方法的JavaDoc中的@mahemadhi“返回正在运行的程序可用的内存总量”
Alex

这不是对该问题的正确答案。答案与特定应用无关。
阿米尔·雷扎扎德

52

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

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数组?


2
它们可能与输入数组的顺序相同。
2010年

2
这似乎是一种非常不直观的处理方式。是的,可能就是这种情况,但是OOP到底如何呢?
瑞安·比斯利

6
该API旨在提高效率,而不是易于使用或简单。这绝不是99%的应用程序应该接触的东西,因此效率是最重要的设计目标。
hackbod 2010年

2
很公平。我正在尝试编写一个内部工具来跟踪我们正在编写的一个或多个应用程序的内存使用情况。因此,我正在寻找一种进行监视的方法,同时对其他过程的影响最小,但仍要尽可能详细地说明结果(后处理)。假设每个.getProcessMemoryInfo调用都存在一些开销,那么遍历这些进程,然后对每个进程进行调用似乎效率低下。如果保证返回的数组与调用的顺序相同,我将盲目处理结果,并假设奇偶校验。
瑞安·比斯利

5
这是一个小问题,但是对于Log,则不需要添加换行,而您需要为它进行处理。
ThomasW 2011年


19

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

在此处输入图片说明

它的优点是:

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

在此处输入图片说明

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

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


16

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

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

1
更改为(ActivityManager activityManager =(ActivityManager)getSystemService(ACTIVITY_SERVICE);),而不是(ActivityManager activityManager = =(ActivityManager)getSystemService(ACTIVITY_SERVICE);)
Rajkamal

7

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

  • 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;


1

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

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

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

还有像一些API android.os.Debug.MemoryInfoActivityManager.getMemoryInfo()上面提到你已经有可能被了解和使用,但我会再谈其他方式

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

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

当您获得进程传递的输出流时,使用命令(procrank,dumpsys meminfo等...)\n而不是id并获取其inputstream并读取,将流存储在bytes [],char []等中。使用raw数据完成!!!!!

许可:

<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获取原始数据,由于原始数据需要手动分离,因此存储起来很复杂

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.