756

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

그것을 할 수있는 방법이 있기를 바랍니다. 또한 전화의 무료 메모리도 어떻게 얻습니까?


8 답변


982

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,PrivateDirty, 및SharedDirty... 지금은 재미가 시작됩니다.

Android (및 Linux 시스템 전반)의 많은 메모리는 실제로 여러 프로세스에서 공유됩니다. 따라서 프로세스가 사용하는 메모리의 양은 실제로 명확하지 않습니다. 그 페이징을 디스크에 추가하십시오 (우리가 안드로이드에서 사용하지 않는 것은 교환 할 필요가 없습니다).

따라서 실제로 각 프로세스에 매핑 된 실제 RAM을 모두 가져 와서 모든 프로세스를 추가하는 경우 실제로 총 RAM보다 훨씬 많은 수의 RAM이 생성됩니다.

그만큼Pss숫자는 메모리 공유를 고려한 커널 계산의 기준입니다. 기본적으로 프로세스의 RAM 페이지는 해당 페이지를 사용하는 다른 프로세스 수의 비율에 따라 조정됩니다. 이렇게하면 이론적으로 모든 프로세스에서 pss를 추가하여 사용중인 총 RAM을 확인하고 프로세스 간 pss를 비교하여 상대적인 가중치를 대략적으로 파악할 수 있습니다.

다른 흥미로운 측정 항목은 다음과 같습니다.PrivateDirty기본적으로 디스크에 페이징 할 수없는 프로세스 내부의 RAM 크기 (디스크의 동일한 데이터로 백업되지 않음)이며 다른 프로세스와 공유되지 않습니다. 이것을 살펴볼 또 다른 방법은 RAM이 프로세스가 사라지면 시스템에서 사용할 수있게 될 것입니다 (그리고 아마도 캐시와 다른 용도로 빠르게 포함될 것입니다).

이것이 SDK API입니다. 그러나 장치로 개발자로서 더 많은 일을 할 수 있습니다.

사용adb실행중인 시스템의 메모리 사용에 관해 얻을 수있는 많은 정보가 있습니다. 공통적 인 것은 명령입니다.adb shell dumpsys meminfo각 자바 프로세스의 메모리 사용에 관한 많은 정보를 뱉어 내고, 위의 정보와 다양한 것들을 포함 할 것이다. 예를 들어, 단일 프로세스의 이름이나 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힙이 추가 할당을 위해 남은 나머지 킬로 비트입니다.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 사용량을 합산하면 엄청나게 큰 숫자를 얻습니다).

Pss우리가 전에 보았던 것과 같습니다.Uss~이다.Priv 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이 필요하기 때문에 실제 실제 RAM보다 작습니다).

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

Cached파일 시스템 캐시와 다른 것들에 사용되는 RAM입니다. 일반적인 시스템은 나쁜 페이징 상태가되는 것을 피하기 위해 20MB 정도가 필요합니다. 안드로이드 메모리 부족 킬러는 캐시 된 RAM이 너무 많이 소모되어 페이징을 초래하기 전에 백그라운드 프로세스가 종료되도록 특정 시스템에 맞춰 조정됩니다.


  • 좀 봐.pixelbeat.org/scripts/ps_mem.py프로그램에서 사용 된 RAM을 보여주기 위해 위에서 언급 한 기술을 사용합니다. - pixelbeat
  • 아주 좋은 서면! 메모리 관리 및 다른 도구를 사용하여 여기에서 힙 사용을 검사하는 게시물을 썼습니다.macgyverdev.blogspot.com/2011/11/…누군가가 유용하다고 생각한다면. - Johan Norén
  • 정확하게 두 열 "dalvik"이 무엇입니까? "천연"? - dacongy
  • 정확하게 "원시" "dalvik" "기타"? 내 앱에서 & quot; 기타 & quot; 매우 거대하니? 어떻게 줄일 수 있습니까? - landry
  • "adb shell dumpsys meminfo"를 사용할 수 있지만 "adb shell procrank"은 "/ system / bin / sh : procrank : not found"를 말해줍니다. 단서가 없어. 너 내가 도울 수 있기를. - Hugo

72

예, 메모리 정보를 프로그래밍 방식으로 가져와 메모리 집약적 인 작업을 수행할지 여부를 결정할 수 있습니다.

다음을 호출하여 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


  • 이 런타임은 현재 프로세스 또는 전체 시스템 힙에 의한 메모리 사용을 반환합니까? - mahe madhi
  • totalMemory () 메소드의 JavaDoc에서 @mahemadhi & quot; 실행중인 프로그램에서 사용할 수있는 총 메모리 양을 반환합니다. & quot; - Alex

49

이것은 진행중인 작업이지만, 이것이 내가 이해하지 못하는 것입니다 :

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 배열만을 사용하도록 만들까요?


  • 입력 배열과 같은 순서로 표시됩니다. - taer
  • 그것은 매우 비 직관적 인 일을하는 것처럼 보입니다. 그렇습니다. 아마도 그럴 것이지만, 어떤 방식 으로든 그것이 OOP에 어떻게 존재합니까? - Ryan Beesley
  • API는 사용 편의성이나 단순성이 아닌 효율성을 고려하여 설계되었습니다. 이것은 단지 99 %의 앱이 만져서는 안되는 부분이 아니므로 효율성이 가장 중요한 디자인 목표입니다. - hackbod
  • 공정하다. 작성중인 하나 이상의 응용 프로그램에 대한 메모리 사용을 추적하기위한 내부 도구를 작성하려고합니다. 결과적으로 다른 프로세스에 최소한 영향을 미치면서이 모니터링을 수행 할 수있는 방법을 찾고 있지만 결과는 가능한 한 상세하게 설명하고 있습니다 (사후 처리). 프로세스를 반복하면서 각 프로세스에 대한 호출을 만드는 것은 비효율적 인 것처럼 보입니다. 각 .getProcessMemoryInfo 호출에 대해 약간의 오버 헤드가 있다고 가정합니다. 리턴 된 배열이 호출과 동일한 순서로 보장된다면 나는 결과를 맹목적으로 처리하고 단지 패리티라고 가정한다. - Ryan Beesley
  • 이것은 사소한 문제이지만 Log에서는 줄 바꿈을 추가 할 필요가 없습니다.이 줄 바꿈은 사용자를 위해 처리됩니다. - ThomasW

24

Hackbod 's는 스택 오버플로에 대한 최고의 해답 중 하나입니다. 그것은 아주 애매한 주제에 빛을 던집니다. 그것은 나를 많이 도왔다.

정말 도움이되는 또 다른 리소스는 꼭보아야 할 비디오입니다.Google I / O 2011 : Android 앱을위한 메모리 관리


최신 정보:

프로세스 통계, 앱이 블로그 게시물에서 설명한 메모리 관리 방법을 발견하는 서비스프로세스 통계 : 앱에서 RAM을 사용하는 방식 이해Dianne Hackborn 저 :


19

Android Studio 0.8.10 이상은 믿을 수 없을 정도로 유용한 도구를 선보였습니다.메모리 모니터.

enter image description here

그것은 무엇을 위해 좋은가 :

  • 그래프에서 사용 가능하고 사용 된 메모리 및 가비지 수집 표시   시간 경과에 따른 사건.
  • 앱의 느려지는 여부를 빠르게 테스트   과도한 가비지 수집 이벤트와 관련됩니다.
  • 신속한 테스트   앱 충돌 여부는 메모리 부족과 관련이있을 수 있습니다.

enter image description here

그림 1. 안드로이드 메모리 모니터에서 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);


  • (ActivityManager activityManager = = (ActivityManager) getSystemService (ACTIVITY_SERVICE);) 대신 (ActivityManager activityManager = (ActivityManager) getSystemService (ACTIVITY_SERVICE);)로 변경하십시오. - Rajkamal

3

우리는 현재 프로세스의 총 메모리를 얻는 모든 표준 방식에 몇 가지 문제가 있음을 알았습니다.

  • Runtime.getRuntime().totalMemory(): JVM 메모리 만 반환합니다.
  • ActivityManager.getMemoryInfo(),Process.getFreeMemory()및 기타 기반/proc/meminfo- 결합 된 모든 프로세스에 대한 메모리 정보를 반환합니다 (예 :android_util_Process.cpp)
  • Debug.getNativeHeapAllocatedSize()- 용도mallinfo()에 의해 수행 된 메모리 할당에 대한 정보를 반환합니다.malloc()관련 함수 만android_os_Debug.cpp)
  • Debug.getMemoryInfo()- 일을하지만 너무 느립니다. 그것은 대략 가지고 간다200ms...에Nexus 6하나의 통화. 성능 오버 헤드로 인해 우리는이 기능을 쓸모 없게 만들고 정기적으로 호출하므로 모든 호출이 상당히 눈에니다 (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미터법. 여기에서 자세한 내용을 확인할 수 있습니다.하나,.


추신주제가 여전히 실제적이고 간단한 코드 스 니펫이 부족한 것으로 나타났습니다.견적성능이 중요한 요구 사항이 아닌 경우 프로세스의 개인 메모리 사용 :

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;


0

위의 답변은 분명히 도움이 될 것입니다.(2 일간의 여유와 adb 메모리 도구 연구 후)내가 도와 줄 수 있다고 생각해.의견너무.

같이Hackbod는 말합니다 :따라서 실제로 각 프로세스에 매핑 된 실제 RAM을 모두 가져 와서 모든 프로세스를 추가하는 경우 실제로 총 RAM보다 훨씬 많은 수의 RAM이 생성됩니다. 따라서 프로세스 당 정확한 메모리 양을 확보 할 수있는 방법은 없습니다.

하지만 몇 가지 논리로 그걸 가까이에서 볼 수 있습니다.

다음과 같은 API가 있습니다.android.os.Debug.MemoryInfoActivityManager.getMemoryInfo()위에서 이미 언급 했었지만 이미 읽었을 지 모르지만 나는 다른 방법으로 이야기 할 것입니다.

따라서 먼저 root 사용자가되어야 작동하게됩니다. 실행으로 루트 권한으로 콘솔에 들어가십시오.su진행 중이며output and input stream. 그 다음 패스id\n (들어가다)ouputstream에서 출력을 출력하고, 입력 스트림을 포함하는 경우uid=0,당신은 루트 사용자입니다.

이제 여기에 위의 과정에서 사용할 논리가 있습니다.

프로세스의 출력을 얻을 때명령 (procrank, dumpsys meminfo 등)을 전달하십시오.\n대신 이드그걸 얻고inputstream스트림을 바이트 [], 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 :result

원시 데이터는 어떤 API가 아닌 콘솔에서 단일 문자열로 가져옵니다. 복잡한 데이터는 수동으로 분리해야하므로 저장해야합니다..

이것은 단지 시도입니다, 뭔가를 놓친다면 제게 제안 해주세요.

연결된 질문


관련된 질문

최근 질문