Android에서 내 애플리케이션의 메모리 사용량을 어떻게 감지합니까?


800

프로그래밍 방식으로 Android 애플리케이션에서 사용 된 메모리를 어떻게 찾을 수 있습니까?

나는 그것을 할 수있는 방법이 있기를 바랍니다. 또한, 휴대폰의 여유 메모리를 어떻게 확보 할 수 있습니까?


2
또는

1
앱의 메모리 개발자
Shomu

답변:


1008

Linux와 같은 최신 운영 체제의 메모리 사용은 매우 복잡하고 이해하기 어려운 영역입니다. 실제로 당신이 얻는 숫자를 실제로 정확하게 해석 할 확률은 매우 낮습니다. (다른 엔지니어와의 메모리 사용량을 볼 때마다 매우 모호한 결론을 내릴 수 있다는 사실에 대한 논의가 항상 있습니다.)

참고 : 이제 여기에있는 많은 자료를 다루고 Android 상태에 대한 최신 정보를 제공하는 앱 메모리 관리에 대한 훨씬 더 광범위한 문서 가 있습니다.

우선이 기사의 마지막 부분을 읽어 보는 것이 좋습니다.이 기사에서는 Android에서 메모리를 관리하는 방법에 대해 설명합니다.

Android 2.0부터 서비스 API 변경

이제 ActivityManager.getMemoryInfo()전체 메모리 사용량을 볼 수있는 최고 수준의 API입니다. 이것은 시스템이 백그라운드 프로세스를 위해 더 이상 메모리를 갖지 않는 정도를 응용 프로그램이 측정 할 수 있도록 도와 주므로 서비스와 같은 필요한 프로세스를 종료해야합니다. 순수 Java 애플리케이션의 경우 하나의 앱이이 시점에서 시스템에 스트레스를 줄 수 없도록 Java 힙 제한이 부분적으로 있기 때문에이 기능은 거의 사용하지 않아야합니다.

하위 수준으로 가면 디버그 API를 사용하여 메모리 사용에 대한 원시 커널 수준 정보를 얻을 수 있습니다. android.os.Debug.MemoryInfo

2.0부터는 ActivityManager.getProcessMemoryInfo다른 프로세스에 대한이 정보를 얻기 위한 API도 있습니다 . 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, PrivateDirty그리고 SharedDirty... 음 이제 재미가 시작됩니다.

Android (및 일반적으로 Linux 시스템)의 많은 메모리는 실제로 여러 프로세스에서 공유됩니다. 따라서 프로세스에서 사용하는 메모리 양이 실제로 명확하지 않습니다. 페이징을 디스크에 추가하고 (Android에서 사용하지 않는 스왑은 물론) 명확하지 않습니다.

따라서 실제로 각 프로세스에 매핑 된 모든 실제 RAM을 가져 와서 모든 프로세스를 합산하면 실제 총 RAM보다 훨씬 큰 숫자로 끝날 수 있습니다.

Pss숫자는 메모리 공유를 고려하여 커널이 계산하는 지표입니다. 기본적으로 프로세스의 각 RAM 페이지는 해당 페이지를 사용하는 다른 프로세스 수의 비율로 조정됩니다. 이 방법으로 (이론적으로) 모든 프로세스에서 ps를 합산하여 사용중인 총 RAM을 확인하고 프로세스 간 pss를 비교하여 상대적 가중치를 대략적으로 알 수 있습니다.

여기서 흥미로운 또 다른 지표 PrivateDirty는 기본적으로 디스크에 페이징 할 수없는 (디스크의 동일한 데이터로 백업되지 않음) 프로세스 내부의 RAM 크기이며 다른 프로세스와 공유되지 않습니다. 이를 보는 또 다른 방법은 해당 프로세스가 사라질 때 시스템에서 사용할 수있는 RAM입니다 (캐시 및 기타 용도로 빠르게 포함됨).

그것은 이것에 대한 SDK API와 거의 같습니다. 그러나 장치 개발자라면 할 수있는 일이 더 많습니다.

를 사용 adb하면 실행중인 시스템의 메모리 사용에 대해 얻을 수있는 많은 정보가 있습니다. 일반적인 adb shell dumpsys meminfo것은 위의 정보와 다양한 다른 것들을 포함하는 각 Java 프로세스의 메모리 사용에 대한 많은 정보를 뱉어내는 명령 입니다. 단일 프로세스의 이름이나 pid를 사용 adb shell dumpsys meminfo system하여 시스템 프로세스를 볼 수 있습니다.

** pid 890 [MEMORY]의 MEMINFO **
                    네이티브 달빅 다른 총
            크기 : 10940 7047 N / A 17987
       할당 : 8943 5516 해당 없음 14459
            무료 : 336 1531 해당 없음 1867
           (Pss) : 4585 9282 11916 25783
  (공유 더티) : 2184 3596916 6696
    (개인 더러운) : 4504 5956 7456 17916

 사물
           조회수 : 149보기 루트 : 4
     AppContexts : 13 활동 : 0
          자산 : 4 자산 관리자 : 4
   로컬 바인더 : 141 프록시 바인더 : 158
사망자 : 49
 OpenSSL 소켓 : 0

 SQL
            힙 : 205 dbFiles : 0
       numPagers : 0 비활성 페이지 KB : 0
    activePageKB : 0

상부 섹션은 주 하나이며 size, 특정 힙의 주소 공간의 전체 크기는 allocated, 힙이 가지고 생각하는 것이 실제 할당의 KB이고 free나머지 KB 힙 추가 할당을 갖는 자유롭게하고 pss및은 priv dirty동일하다 이전에 각 힙과 관련된 페이지에 대해 설명했듯이

모든 프로세스에서 메모리 사용량 만 보려면 명령을 사용할 수 있습니다 adb shell procrank. 동일한 시스템에서이 결과는 다음과 같습니다.

  PID Vss Rss Pss Uss cmdline
  890 84456K 48668K 25850K 21284K 시스템 _ 서버
 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
...
   59336K 332K 99K 92K / system / bin / installd
   60 396K 392K 93K 84K / system / bin / keystore
   51280K 276K 74K 68K / system / bin / servicemanager
   54256K 252K 69K 64K / system / bin / debuggerd

여기에서 VssRss열은 기본적으로 노이즈입니다 (이것은 프로세스의 간단한 주소 공간과 RAM 사용량입니다. 여러 프로세스에서 RAM 사용량을 합하면 엄청나게 많은 수를 얻습니다).

Pss우리가 전에 본 것과 같고 Uss입니다 Priv Dirty.

흥미로운 점은 여기에서주의해야 할 : Pss그리고 Uss약간 (이상보다 약간) 우리가 본 것을 다릅니다 meminfo. 왜 그런 겁니까? procrank는 다른 커널 메커니즘을 사용하여 데이터를 수집하고 meminfo약간 다른 결과를 제공합니다. 왜 그런 겁니까? 솔직히 나는 단서가 없습니다. procrank더 정확한 정보 라고 생각합니다 .하지만 실제로는 "소금 한 덩어리로 얻은 기억 정보를 가져 가십시오.

마지막으로 adb shell cat /proc/meminfo시스템의 전체 메모리 사용량을 요약 한 명령 이 있습니다. 여기에는 많은 데이터가 있으며 토론 할 가치가있는 처음 몇 숫자 (그리고 나머지는 소수의 사람들이 이해하고 소수의 사람들에 대한 나의 질문은 종종 설명이 상충됩니다) :

총계 : 395144 kB
MemFree : 184936 kB
버퍼 : 880 kB
캐시 : 84104 kB
스왑 캐시 : 0 kB

MemTotal 커널 및 사용자 공간에 사용 가능한 총 메모리 양입니다 (일부 RAM은 라디오, DMA 버퍼 등에 필요하기 때문에 장치의 실제 실제 RAM보다 적음).

MemFree전혀 사용되지 않는 RAM의 양입니다. 여기 보이는 숫자는 매우 높습니다. 일반적으로 Android 시스템에서는 사용 가능한 메모리를 사용하여 프로세스를 계속 실행하기 때문에 몇 MB에 불과합니다.

Cached파일 시스템 캐시 및 기타 것들에 사용되는 RAM입니다. 페이징 상태가 나 빠지지 않도록 일반적인 시스템에는 20MB 정도가 필요합니다. 안드로이드 메모리 부족 킬러는 캐시 된 RAM이 너무 많이 소비되어 페이징을 일으키기 전에 백그라운드 프로세스가 종료되도록 특정 시스템에 맞게 조정됩니다.


1
(A)에서 찾아 보게 pixelbeat.org/scripts/ps_mem.py 프로그램에 사용되는 RAM을 보여주기 위해 위에서 언급 한 기술을 사용
pixelbeat

17
아주 좋은 글! 여기 당신의 힙 사용을 검사하기 위해 메모리 관리와 다른 도구의 사용에 대한 게시물을 작성한 macgyverdev.blogspot.com/2011/11/... 그것은 유용한 사람의 발견합니다.
Johan Norén

3
두 열 "dalvik"이 "네이티브"란 정확히 무엇입니까?
dacongy

1
"네이티브" "달빅" "기타"란 정확히 무엇입니까? 내 응용 프로그램에서 "기타"가 매우 큽니까? 어떻게 줄일 수 있습니까?
landry

나는 "ADB 쉘 procrank이"말해 "ADB 쉘 dumpsys이 meminfo 파일"을 사용할 수 있지만,.. "procrank : / 시스템 / 빈 / sh를 찾을 수 없습니다"나는 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

1
totalMemory () 메소드의 JavaDoc에서 @mahemadhi "실행중인 프로그램에서 사용 가능한 총 메모리 양을 리턴합니다"
Alex

이것은 질문에 대한 정답이 아닙니다. 대답은 특정 응용 프로그램에 대한 것이 아닙니다.
Amir Rezazadeh

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
입력 배열과 같은 순서 일 것입니다.
taer

2
그것은 일을하는 매우 직관적이지 않은 방법처럼 보입니다. 예, 아마도 그렇습니다. 그러나 어떻게 OOP입니까?
Ryan Beesley

6
API는 사용 편의성 또는 단순성이 아니라 효율성을 위해 설계되었습니다. 이는 99 %의 앱이 다루어야 할 것이 아니라 효율성이 가장 중요한 디자인 목표입니다.
hackbod

2
그럴 수 있지. 우리가 작성하는 하나 이상의 응용 프로그램의 메모리 사용을 추적하기 위해 내부 도구를 작성하려고합니다. 결과적으로 다른 프로세스에 가장 영향을 미치면서 가능한 한 결과에 대해 자세하게 설명하면서 (이후 처리)이 모니터링을 수행 할 수있는 방법을 찾고 있습니다. 각 .getProcessMemoryInfo 호출에 대한 오버 헤드가 있다고 가정하면 프로세스를 반복 한 다음 각 프로세스를 호출하는 것은 비효율적 인 것으로 보입니다. 반환 된 배열이 호출과 동일한 순서로 보장되면 결과를 맹목적으로 처리하고 패리티를 가정합니다.
Ryan Beesley

5
이것은 사소한 문제이지만 Log의 경우 줄 바꿈을 추가 할 필요가 없습니다.
ThomasW

24

Hackbod는 Stack Overflow에 대한 최고의 답변 중 하나입니다. 매우 모호한 피사체에 빛을 비 춥니 다. 그것은 많은 도움이되었습니다.

또 다른 유용한 리소스는 꼭 봐야 할 비디오입니다. Google I / O 2011 : Android 앱용 메모리 관리


최신 정보:

프로세스 통계 : 앱에서 메모리를 관리하는 방법을 검색하는 서비스 인 블로그 통계 : Process Stats : Dianne Hackborn의 RAM 사용 방식 이해 :


19

Android Studio 0.8.10+는 Memory Monitor 라는 매우 유용한 도구를 도입했습니다 .

여기에 이미지 설명을 입력하십시오

좋은 점 :

  • 사용 가능한 메모리와 사용 된 메모리를 그래프로 표시하고 시간에 따른 가비지 수집 이벤트
  • 앱 속도 저하가 과도한 가비지 수집 이벤트와 관련이 있는지 여부를 빠르게 테스트합니다.
  • 앱 충돌 여부는 메모리 부족과 관련이 있는지 빠르게 테스트합니다.

여기에 이미지 설명을 입력하십시오

그림 1. Android 메모리 모니터에서 GC (Garbage Collection) 이벤트 강제 실행

앱을 사용하여 앱의 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 에서는 한 번의 통화 에 약 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 메트릭을 반환합니다 . 여기에 대한 자세한 내용은 one , twothree 에서 찾을 수 있습니다 .


추신 : 성능이 중요한 요구 사항이 아닌 경우 테마 에 프로세스의 개인 메모리 사용 을 추정 하는 방법에 대한 실제적이고 간단한 코드 스 니펫이 여전히 부족하다는 것을 알았습니다 .

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 일간의 여유 시간과 adb 메모리 도구에 대한 연구 후) 나는 내 의견으로 도 도울 수 있다고 생각 합니다.

으로 Hackbod는 말한다 : 당신이 실제로 각 프로세스에 매핑 된 모든 실제 RAM을, 그리고 모든 프로세스를 추가했다 따라서 경우에, 당신은 아마 실제 전체 RAM보다 수가 훨씬 더 끝낼 것입니다. 따라서 프로세스 당 정확한 메모리 양을 얻을 수있는 방법이 없습니다.

그러나 당신은 어떤 논리에 의해 그것에 가까워 질 수 있습니다.

이 몇 가지 같은 API입니다 android.os.Debug.MemoryInfo그리고 ActivityManager.getMemoryInfo()당신이 이미에 대해 읽고 사용했을 수도있는 위에서 언급하지만 나는 다른 방법에 대해 이야기합니다

따라서 먼저 작동하려면 루트 사용자 여야합니다. su프로세스에서 실행하여 루트 권한으로 콘솔에 들어가서 해당 권한을 가져옵니다 output and input stream. 그런 다음 통과 id\n (입력) ouputstream과 프로세스 출력에 기록, 만일이되는 InputStream가 포함 얻을 것이다 uid=0, 당신은 루트 사용자입니다.

위의 프로세스에서 사용할 논리는 다음과 같습니다.

프로세스 의 출력 스트림을 얻을 때 id 대신 명령 (procrank, dumpsys meminfo 등 ...)을 전달\ninputstream 하고 읽은 다음 스트림을 바이트 [], char [] 등으로 저장하십시오. 원시 데이터를 사용 하십시오. !!!!!

허가 :

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

루트 사용자인지 확인하십시오.

// 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 대신 콘솔에서 단일 문자열로 원시 데이터를 가져옵니다 . 수동으로 분리해야하므로 저장하기가 복잡합니다 .

이것은 단지 시도입니다, 내가 뭔가를 놓친 경우 제안하십시오

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.