2013-06-19 15 views
16

W polowaniu na wyciek pamięci w mojej aplikacji ścigałem zachowanie, którego nie mogę zrozumieć. Przydzielam duży blok pamięci, ale nie zbiera się w nim śmieci, czego skutkiem jest OOM, chyba że podaję zerowy odnośnik w onDestroy.Duża porcja pamięci nie śmieci zebrane

W tym przykładzie mam dwie prawie identyczne czynności, które przełączają się między sobą. Oba mają jeden przycisk. Po naciśnięciu przycisku MainActivity uruchamia OOMActivity i OOMActivity zwraca przez wywołanie finish(). Po kilkukrotnym naciśnięciu przycisków, Android rzuca OOMException.

Jeśli dodaję onDestroy do OOMActivity i explicite null odniesienie do porcji pamięci, widzę w dzienniku, że pamięć została poprawnie zwolniona.

Dlaczego pamięć nie jest automatycznie zwalniana bez zerowania?

główną działalność:

package com.example.oom; 

import android.app.Activity; 
import android.content.Intent; 
import android.os.Bundle; 
import android.view.View; 
import android.view.View.OnClickListener; 
import android.widget.Button; 

public class MainActivity extends Activity implements OnClickListener { 

    private int buttonId; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     System.gc(); 
     Button OOMButton = new Button(this); 
     OOMButton.setText("OOM"); 
     buttonId = OOMButton.getId(); 

     setContentView(OOMButton); 
     OOMButton.setOnClickListener(this); 
    } 

    @Override 
    public void onClick(View v) { 
     if (v.getId() == buttonId) { 
      Intent leakIntent = new Intent(this, OOMActivity.class); 
      startActivity(leakIntent); 
     } 
    } 

} 

OOMActivity:

public class OOMActivity extends Activity implements OnClickListener { 

    private static final int WASTE_SIZE = 20000000; 
    private byte[] waste; 
    private int buttonId; 

    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 

     Button BackButton = new Button(this); 
     BackButton.setText("Back"); 
     buttonId = BackButton.getId(); 

     setContentView(BackButton); 
     BackButton.setOnClickListener(this); 

     waste = new byte[WASTE_SIZE]; 

    } 

    public void onClick(View view) { 
     if (view.getId() == buttonId) { 
      finish(); 
     } 
    } 

} 
+0

świetny przykład, świetne pytanie! – WarrenFaith

+0

Proszę przeczytać ten czat. Omówiliśmy to tutaj (http://chat.stackoverflow.com/transcript/message/10084186#10084186) – Reno

+2

Prawdopodobnie testujesz to na urządzeniu o strukturze plastra miodu. Śmieciarka na post-plasterku będzie wystarczająco agresywna, aby uwolnić obiekt "odpadowy". Testowane w 4.1.2, 4.2.2, 2.3.5, 2.3.7. Wywołanie System.gc() przed "waste = new byte [WASTE_SIZE]" pozwala uniknąć problemu na urządzeniach o strukturze plastra miodu. – pellucide

Odpowiedz

1

zniszczenie aktywność nie oznacza zniszczenie klasy, tylko dlatego, że nie widzę klasa nie oznacza OS (w tym przypadku Androida) stracił wszystkie odniesienia do niego i zakończył je. Dlatego nawet w dokumentach określają, aby czyścić wszelkie uchwyty i obiekty, których już nie potrzebujesz, aby zapobiec wyciekom pamięci. Twoje zdrowie.

1

kilka rzeczy:

1) nie można oceniać, czy Twój aktywny jest wyciekły właśnie oglądając dzienniki GC; każda implementacja JVM może dowolnie wybierać, kiedy zbierać śmieci, nawet jeśli nic nie wskazuje na nie. Zauważ, że jest on wymagany do zbierania śmieci, zanim wyrzuci błąd OOM ... ale jeśli ma wystarczająco dużo dostępnej pamięci, może zachować 10 z twoich działań w pamięci, a następnie zebrać je wszystkie naraz.

2) Mogą istnieć wewnętrzne struktury Androida, które utrzymują odniesienia do Twojej aktywności dłużej niż cykl życia Działania ... i programista nie ma nad tym kontroli. Z tego powodu zaleca się, aby działanie nie odnosiło się do dużych ilości danych (lub, jeśli tak, to powinno jawnie zwolnić te odniesienia w onDestroy (lub nawet w trybie onPause, jeśli chcesz być bardziej agresywny)

3) Niektóre maszyny JVM optymalizują się w czasie wykonywania, tak że fragment pamięci, który nigdy nie jest zapisywany ani uzyskiwany dostęp, nigdy nie jest faktycznie przydzielany w pamięci fizycznej. Może z tego powodu Twój test nie jest ważny w nowszych wersjach Androida. Aby obejść ten problem, można dodać pętlę, która ustawia niektóre wartości w tablicy na wartości losowe, a następnie inną pętlę w innym miejscu w kodzie, który je czyta; w ten sposób JVM zmuszona jest przydzielić pamięć.

Powiązane problemy