2013-06-19 10 views
16

Auf der Suche nach einem Speicherleck in meiner App habe ich ein Verhalten verfolgt, das ich nicht verstehen kann. Ich reserviere einen großen Speicherblock, aber es wird keine Speicherbereinigung erhalten, was zu einem OOM führt, außer wenn ich den Verweis explizit in onDestroy auf Null setze.Großer Speicherbrocken nicht Müll gesammelt

In diesem Beispiel habe ich zwei fast identische Aktivitäten, die zwischen den anderen wechseln. Beide haben einen einzigen Knopf. Beim Drücken der Schaltfläche MainActivity startet OOMActivity und OOMActivity kehrt durch Aufruf von finish() zurück. Nach einigen Drücken der Tasten löst Android eine OOMException aus.

Wenn ich die onDestroy zu OOMActivity hinzufügen und explizite null den Verweis auf den Speicher Chunk, kann ich in dem Protokoll sehen, dass der Speicher ordnungsgemäß freigegeben ist.

Warum wird der Speicher nicht automatisch ohne Nullung freigegeben?

MainActivity:

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

gutes Beispiel, gute Frage! – WarrenFaith

+0

Bitte lesen Sie diesen Chat. Wir haben es [hier besprochen] (http://chat.stackoverflow.com/transcript/message/10084186#10084186) – Reno

+2

Sie testen das wahrscheinlich auf einem Pre-Waben-Gerät. Der Müllsammler auf Post-Waben wird aggressiv genug sein, um das "Abfall" -Objekt freizugeben. Getestet an 4.1.2, 4.2.2, 2.3.5, 2.3.7. Durch Aufruf von System.gc() vor "vergeuden = neues Byte [WASTE_SIZE]" wird das Problem auf Pre-Waben-Geräten vermieden. – pellucide

Antwort

1

Aktivität Zerstörung bedeutet nicht Klasse Zerstörung, nur weil man die Klasse nicht sehen, bedeutet nicht das Betriebssystem (Android in diesem Fall) verlor alle Hinweise darauf und beende es. Deshalb geben sie auch in den Dokumentationen an, dass Sie alle Handles und Objekte entfernen, die Sie nicht mehr benötigen, um Speicherlecks zu vermeiden. Prost.

1

Ein paar Dinge:

1) Sie können nicht beurteilen, ob Ihre Aktivität durch die Beobachtung der GC-Protokolle nur durchgesickert wird; Jede JVM-Implementierung kann frei wählen, wann Objekte auf Müll gesammelt werden sollen, selbst wenn nichts darauf verweist. Beachten Sie, dass es erforderlich ist, Garbage-Collect zu sammeln, bevor es einen OOM-Fehler auslöst ... aber wenn genügend Arbeitsspeicher verfügbar ist, kann es 10 Ihrer Aktivitäten im Speicher behalten und dann alle auf einmal sammeln.

2) Möglicherweise gibt es interne Android-Strukturen, die einen Verweis auf Ihre Aktivität länger als den Lebenszyklus der Aktivität behalten ... und der Entwickler hat keine Kontrolle darüber. Aus diesem Grund wird empfohlen, dass die Aktivität keine großen Datenmengen referenziert (oder wenn dies der Fall ist, sollte sie diese Referenzen explizit in onDestroy freigeben (oder sogar in onPause, wenn Sie aggressiver sein wollen).

3) Einige JVMs werden zur Laufzeit optimiert, so dass ein Teil des Speichers, der niemals in den Speicher geschrieben oder darauf zugegriffen wird, niemals tatsächlich im physischen Speicher zugeordnet wird. Aus diesem Grund ist Ihr Test möglicherweise nicht für neuere Android-Versionen gültig. Um dies zu umgehen, können Sie eine Schleife hinzufügen, die einige Werte im Array auf zufällige Werte setzt, und dann eine andere Schleife an einer anderen Stelle im Code, die sie liest. Auf diese Weise ist die JVM gezwungen, den Speicher zuzuordnen.

Verwandte Themen