ChatGPT解决这个技术问题 Extra ChatGPT

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

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

我希望有办法做到这一点。另外,我如何获得手机的空闲内存呢?

或者,如果有人想了解更多有关这一切的信息,请参阅 http://elinux.org/Android_Memory_Usage

K
Kyle

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

注意:我们现在有更多关于 Managing Your App's Memory 的文档,其中涵盖了此处的大部分材料,并且更新了 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 中看到的略有(或略多于)不同。这是为什么?与 meminfo 相比,procrank 使用不同的内核机制来收集其数据,它们给出的结果略有不同。这是为什么?老实说,我没有头绪。我相信 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 消耗过多以导致此类分页之前被杀死。


c
coocood

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

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


此运行时是否会返回当前进程或整个系统堆的内存使用情况?
@mahemadhi 来自 totalMemory() 方法的 JavaDoc “返回可用于运行程序的内存总量”
这不是问题的正确答案。答案与特定应用程序无关。
R
Ryan Beesley

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

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() 中的结果?显然你想让结果数据有意义,那么为什么谷歌让结果关联变得如此困难呢?如果我想处理整个内存使用情况,当前系统甚至无法正常工作,因为返回的结果是一个 android.os.Debug.MemoryInfo 对象数组,但这些对象实际上都没有告诉您它们与哪些 pid 相关联。如果您只是传入一个包含所有 pid 的数组,您将无法理解结果。据我了解它的用途,它使得一次传递多个 pid 变得毫无意义,如果是这种情况,为什么要让它 activityManager.getProcessMemoryInfo() 只需要一个 int 数组?


它们可能与输入数组的顺序相同。
这似乎是一种非常不直观的做事方式。是的,可能就是这种情况,但是无论如何 OOP 是怎么回事?
API 旨在提高效率,而不是易用性或简单性。这不是 99% 的应用程序应该触及的东西,因此效率是最重要的设计目标。
很公平。我正在尝试编写一个内部工具来跟踪我们正在编写的一个或多个应用程序的内存使用情况。因此,我正在寻找一种方法来进行这种监控,同时对其他进程的影响最小,但仍然尽可能详细地说明结果(后处理)。假设每个 .getProcessMemoryInfo 调用都有一些开销,那么遍历进程,然后对每个进程进行调用似乎效率低下。如果保证返回的数组与调用的顺序相同,我将盲目处理结果并假设奇偶校验。
这是一个小问题,但对于 Log,不需要添加换行符,这是为您处理的。
P
Peter Mortensen

Hackbod's 是 Stack Overflow 上最好的答案之一。它揭示了一个非常模糊的主题。这对我帮助很大。

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

更新:

Process Stats,一项用于发现您的应用如何管理内存的服务,在 Dianne Hackborn 的博文Process Stats: Understanding How Your App Uses RAM中进行了解释:


M
Machado

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

https://i.stack.imgur.com/s7KtA.png

它有什么好处:

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

https://i.stack.imgur.com/hnJGL.png

图 1. 在 Android 内存监视器上强制执行 GC(垃圾收集)事件

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


P
Peter Mortensen

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

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

更改为 (ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);) 而不是 (ActivityManager activityManager = = (ActivityManager) getSystemService(ACTIVITY_SERVICE);)
D
Dmitry Shesterkin

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

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 指标。您可以在此处找到有关它的更多详细信息:onetwothree

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;

A
Akash Patel

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

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

https://i.stack.imgur.com/VGRqm.png


I
Iamat8

上面有很多答案肯定会对您有所帮助,但是(在对 adb 内存工具进行了 2 天的负担和研究之后)我想我也可以提供我的意见。

作为 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。然后在输出流中传入id\n(enter)并写入处理输出,如果会得到一个包含uid=0的输入流,你是root用户。

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

当您使用 \n 而不是 id 获得进程的输出 通过您的命令(procrank、dumpsys meminfo 等...)并获取其 inputstream 并读取并以字节 [] 存储流时, 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();
 }
}

https://i.stack.imgur.com/hkCwi.png

您从控制台获取单个字符串中的原始数据,而不是在某些情况下从任何 API 获取原始数据,存储起来很复杂,因为您需要手动将其分开。

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


A
Amir Fo

我在阅读答案时感到困惑,所以我决定阅读文档。好的,让我们一起做:

本机堆内存使用情况

您可以使用 Debug 对象获取设备的本机堆内存大小:

long nativeTotal = Debug.getNativeHeapSize();
long nativeFree = Debug.getNativeHeapFreeSize();
long nativeAllocated = Debug.getNativeHeapAllocatedSize();
long nativeUsed = nativeTotal - nativeFree;

注意:nativeUsednativeAllocated 具有相同的值。

运行时内存使用

使用应用程序的 Runtime 对象的内存使用情况,每个应用程序都有一个运行时对象来访问其接口:

Runtime runtime = Runtime.getRuntime();
long runtimeMax = runtime.maxMemory();
long runtimeTotal = runtime.totalMemory();
long runtimeFree = runtime.freeMemory();
long runtimeUsed = runtimeTotal - runtimeFree;

系统内存使用

您可以使用 ActivityManager.MemoryInfo 对象获取系统的内存使用情况:

ActivityManager activityManager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
ActivityManager.MemoryInfo memoryInfo = new ActivityManager.MemoryInfo();
activityManager.getMemoryInfo(memoryInfo);
long systemTotal = memoryInfo.totalMem;
long systemFree = memoryInfo.availMem;
long systemUsed = systemTotal - systemFree;

进程内存使用

另一种读取系统内存使用情况的方法是在linux中解析/proc/meminfo文件的内容。

RandomAccessFile reader = new RandomAccessFile("/proc/meminfo", "r");
Pattern pattern = Pattern.compile("(\\d+)");
Matcher totalMatcher = pattern.matcher(reader.readLine());
totalMatcher.find();
long procTotal = Long.parseLong(totalMatcher.group(1)) * 1024L; // KB to B
Matcher freeMatcher = pattern.matcher(reader.readLine());
freeMatcher.find();
long procFree = Long.parseLong(freeMatcher.group(1)) * 1024L; // KB to B
long procUsed = procTotal - procFree;

好吧,我还是一头雾水。但这是获得内存使用情况的所有方法。将它们全部记录下来以调试您的应用程序!!!

我还为他们每个人提供了一个链接,以阅读他们的用法和信息。