Android uygulamamın bellek kullanımını nasıl bulabilirim?


800

Android uygulamamda kullanılan belleği programlı olarak nasıl bulabilirim?

Umarım bunu yapmanın bir yolu vardır. Ayrıca, telefonun boş hafızasını da nasıl alabilirim?


2
Ya da herkes bunun hakkında daha fazla bilgi edinmek istiyorsa, lütfen http://elinux.org/Android_Memory_Usage

Yanıtlar:


1008

Linux gibi modern işletim sistemlerinde bellek kullanımının son derece karmaşık ve anlaşılması güç bir alan olduğunu unutmayın. Aslında aldığınız sayıları doğru yorumlama şansınız son derece düşüktür. (Diğer mühendislerle bellek kullanım numaralarına her baktığımda, aslında ne anlama geldikleri hakkında sadece belirsiz bir sonuca yol açtığı konusunda uzun bir tartışma var.)

Not: Artık, uygulamanızın Belleğini Yönetmeyle ilgili, burada malzemelerin çoğunu kapsayan ve Android'in durumu ile daha güncel olan çok daha kapsamlı belgelere sahibiz .

İlk şey, muhtemelen bu makalenin Android'de hafızanın nasıl yönetildiği hakkında bazı tartışmalara sahip olan son bölümünü okumaktır:

Android 2.0 ile başlayan Hizmet API'sı değişiklikleri

Şimdi ActivityManager.getMemoryInfo(), genel bellek kullanımına bakmak için en üst düzey API'miz. Bu, çoğunlukla uygulamanın arka plan işlemleri için daha fazla belleğe sahip olmaya ne kadar yakın olduğunu ölçmesine yardımcı olmak için vardır, bu nedenle hizmetler gibi gerekli işlemleri öldürmeye başlaması gerekir. Saf Java uygulamaları için, bunun bir faydası yoktur, çünkü Java yığın sınırı, bir uygulamanın sistemi bu noktaya kadar strese sokmasını önlemek için kısmen vardır.

Daha alt seviyeye geçerek, bellek kullanımı hakkında ham çekirdek düzeyinde bilgi almak için Hata Ayıklama API'sını kullanabilirsiniz: android.os.Debug.MemoryInfo

2.0'dan başlayarak, ActivityManager.getProcessMemoryInfobaşka bir işlem hakkında bu bilgileri almak için bir API olduğunu unutmayın : ActivityManager.getProcessMemoryInfo (int [])

Bu, tüm bu verileri içeren düşük düzeyli bir MemoryInfo yapısı döndürür:

    /** 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;

Ama fark arasındaki farkın ne kadar olduğu Pss, PrivateDirtyve SharedDirty... iyi şimdi eğlence başlar.

Android'de (ve genel olarak Linux sistemlerinde) çok fazla bellek aslında birden çok süreçte paylaşılıyor. Yani bir işlemin ne kadar bellek kullandığı net değil. Disk üzerinde disk belleği (Android'de kullanmadığımız takas olsun) üzerine ekleyin ve daha az nettir.

Böylece, aslında her bir işlem için eşlenen tüm fiziksel RAM'i alıp tüm işlemleri toplarsanız, gerçek toplam RAM'den çok daha büyük bir sayı elde edersiniz.

PssAyrıca o sayfayı kullanan diğer işlemlerin sayısının bir oranı olarak ölçeklendirilmiş bir süreçte RAM temelde her sayfa - sayı hesabı bellek paylaşımı dikkate alır çekirdek değerlerini hesaplar bir ölçümdür. Bu şekilde (teoride) kullandıkları toplam RAM'i görmek için tüm süreçler arasında pss'i ekleyebilir ve göreceli ağırlıkları hakkında kaba bir fikir edinmek için süreçler arasındaki pss'yi karşılaştırabilirsiniz.

Buradaki diğer ilginç metrik PrivateDirty, temel olarak, işlemin içindeki diske sayfalanamayan (diskteki aynı verilerle desteklenmeyen) RAM miktarıdır ve başka herhangi bir işlemle paylaşılmaz. Buna bakmanın bir başka yolu, bu işlem kaybolduğunda (ve muhtemelen hızlı bir şekilde önbelleklere ve diğer kullanımlarına maruz kaldığında) sistem tarafından kullanılabilecek olan RAM'dir.

Bu hemen hemen bunun için SDK API'leri. Ancak, cihazınızla bir geliştirici olarak yapabileceğiniz daha çok şey var.

Kullanarak adb, çalışan bir sistemin bellek kullanımı hakkında alabileceğiniz birçok bilgi vardır. Yaygın olanı, adb shell dumpsys meminfoher bir Java işleminin bellek kullanımı hakkında, yukarıdaki bilgileri ve çeşitli diğer şeyleri içeren bir grup bilgi verecek olan komuttur . Ayrıca görmek için tek bir işlemin adını veya pid'ini de kullanabilirsiniz, örneğin adb shell dumpsys meminfo systembana sistem işlemini verin:

** pid 890 [sistem] içinde MEMINFO **
                    yerli dalvik diğer toplam
            boyut: 10940 7047 Yok 17987
       tahsis edilen: 8943 5516 YOK 14459
            ücretsiz: 336 1531 Yok 1867
           (Pss): 4585 9282 11916 25783
  (paylaşılan kirli): 2184 3596 916 6696
    (özel kirli): 4504 5956 7456 17916

 Nesneler
           İzlenme: 149 İzleme
     AppContexts: 13 Etkinlikler: 0
          Varlıklar: 4 Varlık
   Yerel Bağlayıcılar: 141 Proxy Bağlayıcıları: 158
Ölüm Alıcıları: 49
 OpenSSL Soketleri: 0

 SQL
            yığın: 205 db
       numPagers: 0 etkin değilPageKB: 0
    activePageKB: 0

Üst bölüm, ana biridir size, belirli bir yığın bir adres alanı içindeki toplam boyutudur allocated, yığın sahip olduğu düşündüğü gerçek tahsislerinin kb freekalan kb yığın ek ayırma işlemleri için olan serbest ve pssve priv dirtyaynı daha önce açıklandığı gibi yığınların her biriyle ilişkilendirilmiş sayfalara özel.

Tüm işlemlerde bellek kullanımına bakmak istiyorsanız, komutu kullanabilirsiniz adb shell procrank. Bunun aynı sistemde çıktısı şuna benzer:

  PID Vss Rss Pss Uss cmdline
  890 84456K 48668K 25850K 21284K sistem_sunucusu
 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 zigot
  977 24100K 24096K 5667K 4340K android.process.acore
...
   59 336K 332K 99K 92K / sistem / çöp kutusu / kurulu
   60396K 392K 93K 84K / sistem / depo gözü / anahtar deposu
   51 280K 276K 74K 68K / sistem / depo gözü / servis cihazı
   54256K 252K 69K 64K / sistem / depo gözü / hata ayıklayıcı

Burada Vssve Rsssütunları temelde gürültülüdür (bunlar bir işlemin doğrudan adres alanı ve RAM kullanımıdır, burada RAM kullanımını süreçlere eklerseniz gülünç büyük bir sayı elde edersiniz).

Pssdaha önce gördüğümüz gibi ve Ussöyle Priv Dirty.

Burada dikkat edilmesi gereken ilginç bir şey: Pssve Ussgördüğümüzden biraz (veya biraz daha fazla) farklı meminfo. Neden? Procrank, verilerini toplamak için olduğundan farklı bir çekirdek mekanizması kullanır meminfove biraz farklı sonuçlar verir. Neden? Dürüst olmak gerekirse bir ipucum yok. procrankDaha doğru olabileceğine inanıyorum ... ama gerçekten, bu sadece noktayı terk ediyor: "bir tuz tanesi ile aldığınız herhangi bir hafıza bilgisini alın; genellikle çok büyük bir tane."

Son olarak adb shell cat /proc/meminfosistemin genel bellek kullanımının bir özetini veren komut vardır. Burada çok fazla veri var, sadece tartışmaya değer ilk birkaç sayı (ve az sayıda insan tarafından anlaşılan geri kalanlar ve bu birkaç insanla ilgili sorularım genellikle çelişkili açıklamalarla sonuçlanıyor):

MemToplam: 395144 kB
MemFree: 184936 kB
Tamponlar: 880 kB
Önbellek: 84104 kB
Önbellek: 0 kB

MemTotal çekirdek ve kullanıcı alanı için kullanılabilir toplam bellek miktarıdır (genellikle cihazın gerçek fiziksel RAM'inden daha azdır, çünkü bu RAM'in bir kısmı radyo, DMA tamponları vb. için gereklidir).

MemFreehiç kullanılmayan RAM miktarıdır. Burada gördüğünüz sayı çok yüksek; genellikle bir Android sisteminde bu sadece birkaç MB olacaktır, çünkü süreçleri çalışmaya devam etmek için kullanılabilir belleği kullanmaya çalışıyoruz

Cacheddosya sistemi önbellekleri ve benzeri şeyler için kullanılan RAM'dir. Kötü çağrı durumlarına girmekten kaçınmak için tipik sistemlerin 20 MB kadar olması gerekir; Android, bellek dışı katil, önbelleğe alınmış RAM, bu tür disk belleği ile sonuçlanacak şekilde çok fazla tüketilmeden önce arka plan işlemlerinin öldürüldüğünden emin olmak için belirli bir sistem için ayarlanmıştır.


1
Programlar için kullanılan RAM'i göstermek için yukarıda belirtilen teknikleri kullanan pixelbeat.org/scripts/ps_mem.py adresine bir göz atın
pixelbeat

17
Çok güzel yazılmış! Bellek yönetimi ve yığın kullanımınızı incelemek için farklı araçların kullanımı hakkında bir yazı yazdım, eğer birisi faydalı bulursa macgyverdev.blogspot.com/2011/11/… .
Johan Norén

3
"Dalvik" ve "doğal" iki sütun tam olarak nedir?
dacongy

1
"Yerli" "dalvik" "diğer" tam olarak nedir? Benim app, "diğer" çok büyük? nasıl azaltabilirim?
9'da landry

Ben "adb shell procrank” söyle "adb shell dumpsys MemInfo" kullanabilirsiniz ancak.. ": Procrank: / system / bin / sh bulunamadı" Ben clue.Wish yapmadıysanız bana yardım edebilir
Hugo

79

Evet, bellek bilgilerini programlı olarak alabilir ve bellek yoğun çalışma yapıp yapmayacağınıza karar verebilirsiniz.

Arayarak VM Yığın Boyutu alın:

Runtime.getRuntime().totalMemory();

Şunları arayarak Tahsis Edilen VM Belleğini alın:

Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();

Arayarak VM Yığın Boyutu Sınırı alın:

Runtime.getRuntime().maxMemory()

Arayarak Yerel Tahsisli Belleği Alın:

Debug.getNativeHeapAllocatedSize();

OutOfMemoryError davranışını anlamak ve bellek kullanımını izlemek için bir uygulama yaptım.

https://play.google.com/store/apps/details?id=net.coocood.oomresearch

Kaynak kodunu https://github.com/coocood/oom-research adresinden alabilirsiniz.


7
Bu çalışma zamanı, geçerli işlem veya genel sistem yığını tarafından bellek kullanımını döndürecek mi?
Mahendran

1
totalMemory () yönteminin JavaDoc dosyasından @mahemadhi "Çalışan program için kullanılabilir olan toplam bellek miktarını döndürür"
Alex

Bu soruya doğru bir cevap değil. Cevap belirli bir uygulama ile ilgili değildir.
Amir Rezazadeh

52

Bu devam eden bir çalışma, ama anlamadığım şey bu:

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 neden activityManager.getProcessMemoryInfo () öğesinde sonuçla eşlenmiyor? Açıkça ortaya çıkan verileri anlamlı hale getirmek istiyorsunuz, bu yüzden Google sonuçları ilişkilendirmeyi neden bu kadar zorlaştırdı? Döndürülen sonuç bir android.os.Debug.MemoryInfo nesneleri dizisi olduğu için tüm bellek kullanımını işlemek istersem mevcut sistem bile iyi çalışmıyor, ancak bu nesnelerin hiçbiri size hangi pidelerle ilişkili olduğunu söylemiyor. Tüm pidlerden oluşan bir diziyi geçerseniz, sonuçları anlamanız mümkün olmaz. Kullanımını anladığım kadarıyla, bir kerede birden fazla pid'i geçmeyi anlamsız hale getirir ve sonra durum buysa, neden eventManager.getProcessMemoryInfo () sadece bir int dizisi alır?


2
Muhtemelen giriş dizisiyle aynı sıradadırlar.
taer

2
Bu, işleri yapmanın çok sezgisel olmayan bir yolu gibi görünüyor. Evet, muhtemelen böyle, ama bu nasıl olursa olsun OOP?
Ryan Beesley

6
API, kullanım kolaylığı veya basitlik için değil verimlilik için tasarlanmıştır. Bu, uygulamaların% 99'unun dokunması gereken bir şey değil, bu nedenle verimlilik en önemli tasarım hedefi.
hackbod

2
Yeterince adil. Yazdığımız bir veya daha fazla uygulamanın bellek kullanımını izlemek için dahili bir araç yazmaya çalışıyorum. Sonuç olarak, diğer süreçleri en az etkilemekle birlikte bu izlemeyi yapmanın bir yolunu arıyorum, ancak yine de sonuçlarla mümkün olduğunca ayrıntılı (post-processing). Her bir .getProcessMemoryInfo çağrısı için bazı ek yükler olduğu varsayılarak, süreçler üzerinde yineleme yapmak ve daha sonra her işlem için çağrı yapmak verimsiz görünüyor. Döndürülen dizinin çağrı ile aynı sırada olması garanti edilirse, sonuçları körü körüne işleyeceğim ve sadece parite olduğunu varsayacağım.
Ryan Beesley

5
Bu küçük bir sorundur, ancak Log için sizin için işlenen bir satır besleme eklemek gerekli değildir.
ThomasW

24

Hackbod's Stack Overflow'un en iyi cevaplarından biridir. Çok belirsiz bir konuya ışık tutuyor. Bana çok yardımcı oldu.

Gerçekten yararlı bir başka kaynak da görülmesi gereken video: Google I / O 2011: Android Uygulamaları için bellek yönetimi


GÜNCELLEME:

İşlem İstatistikleri, uygulamanızın blog yayınında açıklanan belleği nasıl yönettiğini keşfetmek için bir hizmet. İşlem İstatistikleri: Uygulamanızın Dianne Hackborn tarafından RAM'i Nasıl Kullantığını Anlama:


19

Android Studio 0.8.10+, Memory Monitor adlı inanılmaz kullanışlı bir araç tanıttı .

resim açıklamasını buraya girin

Ne için iyi:

  • Bir grafikte kullanılabilir ve kullanılmış bellek ve zaman içinde çöp toplama olayları gösteriliyor.
  • Uygulama yavaşlığının aşırı çöp toplama olaylarıyla ilişkili olup olmadığını hızla test edin.
  • Uygulama kilitlenmelerinin belleğin tükenmesi ile ilgili olup olmadığını hızla test edin.

resim açıklamasını buraya girin

Şekil 1. Android Bellek İzleyicisi'nde bir GC (Çöp Toplama) olayını zorlama

Bunu kullanarak uygulamanızın RAM gerçek zamanlı tüketimi hakkında bol miktarda iyi bilgiye sahip olabilirsiniz.


16

1) Sanırım hayır, en azından Java'dan değil.
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);) yerine (ActivityManager activityManager = = (ActivityManager) getSystemService (ACTIVITY_SERVICE);) olarak değiştirildi
Rajkamal

7

Mevcut sürecin toplam belleğini almanın tüm standart yollarının bazı sorunları olduğunu öğrendik.

  • Runtime.getRuntime().totalMemory(): yalnızca JVM belleği döndürür
  • ActivityManager.getMemoryInfo(), Process.getFreeMemory()Dayalı başka ve bir şey /proc/meminfo- kombine tüm işlemler hakkında döner hafıza bilgileri (örneğin android_util_Process.cpp )
  • Debug.getNativeHeapAllocatedSize()- yalnızca ilgili işlevler mallinfo()tarafından gerçekleştirilen bellek ayırmaları malloc()ve ilgili işlevler hakkında bilgi veren kullanır (bkz. android_os_Debug.cpp )
  • Debug.getMemoryInfo()- işi yapıyor ama çok yavaş. Yaklaşık sürer 200ms üzerinde Nexus 6 Tek bir çağrı için. Performans yükü, düzenli olarak dediğimiz ve her çağrı oldukça dikkat çekici olduğu için bu işlevi bizim için işe yaramaz hale getirir (bkz. Android_os_Debug.cpp )
  • ActivityManager.getProcessMemoryInfo(int[])- Debug.getMemoryInfo()dahili olarak arama yapar (bkz. ActivityManagerService.java )

Son olarak, aşağıdaki kodu kullandık:

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 metriğini döndürür . Bununla ilgili daha fazla ayrıntıyı burada bulabilirsiniz: bir , iki ve üç .


PS: Temanın performans kritik bir gereklilik değilse, sürecin özel bellek kullanımını nasıl tahmin edeceğine dair gerçek ve basit bir kod snippet'inin eksik olduğunu fark ettim :

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

Yukarıda kesinlikle size yardımcı olacak bir sürü cevap var ama (adb bellek araçları üzerinde 2 gün göze ve araştırma sonra) Ben de benim görüşüme yardımcı olabilir düşünüyorum .

As Hackbod diyor ki: aslında her işlem için eşlenen fiziksel RAM hepsini almak ve süreçlerin tüm ekleyebilirsiniz edildi Böylece, muhtemelen gerçek toplam RAM daha sayı çok daha fazla ile bitirmek istiyorum. bu nedenle işlem başına tam bellek miktarını elde etmenin bir yolu yoktur.

Ama ona bir mantıkla yaklaşabilirsin ... ve nasıl olduğunu anlatacağım ..

Yukarıda halihazırda okunmuş ve kullanılmış olabilecek bazı API'ler var android.os.Debug.MemoryInfove ActivityManager.getMemoryInfo()bahsettik, ancak başka bir yoldan konuşacağım

Bu yüzden öncelikle çalışabilmesi için kök kullanıcı olmanız gerekir. suSüreçte çalışarak kök ayrıcalığına sahip konsol içine alın ve edinin output and input stream. Sonra geçer id\n (enter) ouputstream ve proses çıkış yazmak, varsa bir inputStream içeren alacak uid=0, kök kullanıcı bulunmaktadır.

Şimdi yukarıdaki işlemde kullanacağınız mantık

Eğer işlem geçmek ouputstream\n olsun id yerine komut (procrank, dumpsys meminfo vb ...) almak ve almak inputstreamve okumak, akışı bayt [], char [] vb saklamak .. ham veri kullanın .. ve sen yapılır !!!!!

izin:

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

Kök kullanıcı olup olmadığınızı kontrol edin:

// 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
   } 
}

Komutunuzu 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: sonuç

El ile ayırmanız gerekeceğinden saklanması karmaşık olan herhangi bir API'dan, bazı durumlarda konsoldan tek bir dizede ham veri alırsınız .

Bu sadece bir deneme, lütfen bir şey kaçırırsam öner

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.