Android: Memory leak

shadow

Aktives Mitglied
Hallo,

ich habe Speicher-Probleme in meiner Android-App und habe diese auf folgende Funktion eingegrenzt:

Java:
    private String test(long value) {
        return "abc" + value;
    }

Zugegebenermaßen wird diese Funktion sehr oft aufgerufen, ich programmiere an einem Spiel und bei jedem zeichnen brauche ich die Funktion mehrmals...

Ich verstehe nur nicht, warum das so massiv Speicher braucht. Der Rückgabe-String der Funktion wird an canvas.drawText() übergeben und danach nicht mehr verwendet, d. h. ich selbst halte keine Referenzen auf den String. Ich habe ein bisschen damit rumgespielt und herausgefunden, dass folgendes überhaupt kein Problem ist:

Java:
    private String test(long value) {
        return "abc";
    }

Ok, hier wird das Autobixing gespart und es muss dadurch ein ein Long-Objekt weniger erzeugt werden, aber der "abc"-String wird auch hier erzeugt und die letzere Variante frisst meinen Speicher nicht im Geringsten auf...

Hat da jemand eine Idee?

Danke schonmal im Vorraus!
Stefan
 
Wie es im Detail bei der Dalvik-VM gelöst ist, ist schwer zu sagen, aber davon ausgehend, dass es gewisse Parallelen zur JVM gibt: Bei der JVM gibt es einen "String-Pool" für konstante Strings. Es könnte sein, dass die Dalvik auch einen für solche zusammengesetzen Strings hat. Wäre aber schon seltsam, wenn es ihn da wirklich raushauen würde. Kracht er mit einer OOME ab, oder geht es nur darum, dass er scheinbar immer mehr Speicher braucht...?
 
Danke für die Antwort!

Ich habe in meiner App eine kleine grafische Anzeige des belegten und verfügbaren Speichers. Das realisiere ich mit Runtime.getRuntime().freeMemory() etc...

Da sehe ich, dass innerhalb von ein paar Sekunden enorm viel Speicher verbraucht wird (das müssen mehrere MB sein), dann greift wohl irgendwann der GC und die Speicher ist wieder freigegeben. Das wiederholt sich über den gesamten Zeitraum des Programms.
Soweit ja erstmal kein Problem, wenn der Speicher immer wieder freigegeben werden kann. Aber einerseits frage ich mich, warum dieses Verhalten nur bei meinem ersten Code-Segment auffällt. Lasse ich das Autoboxing des long-Werts weg, bleibt der Speicher konstant. Und andererseits braucht meine App von Zeit zu Zeit viel Speicher auf einmal (große Bilder werden geladen), und dann fliegt die App mit meiner OOME weg, wenn gerade nicht genug Speicher da ist, was aber eigentlich der Fall wäre, wenn der Speicher nicht dieses Spielchen machen würde...
 
OK, einen OOME sollte es eigentlich nicht Fall geben, wenn es wirklich Speicher ist, der wieder freigegeben werden kann - und bei diesen "Müll-Strings", die nur einmal zusammengebaut und schnell wieder weggeworfen werden, sollte er das auf jeden Fall können. Ich bin selbst nicht sooo firm mit Android, eine Websuche liefert schnell sowas wie Track memory allocations | Android Developers Blog , vielleicht hilft das schon...?
Bist du sicher, dass er die Bilder laden kann, wenn zufällig gerade kurz vorher der GC gelaufen ist?
Ggf. müßte man mal eine Mini-Application zum Testen bauen, die wirklich nur Strings der Form "abc"+einLong zusammenbaut, und schauen, was der Speicher dort genau macht.
 
Zitat von der Android-Developerseite (Designing for Performance):

Avoid Creating Unnecessary Objects
....
- If you have a method returning a string, and you know that its result will always be appended to a StringBuffer anyway, change your signature and implementation so that the function does the append directly, instead of creating a short-lived temporary object.

Übertragen auf dein Beispiel, übergebe deine Canvas der Methode, baue den String in der Methode zusammen und zeichne in der selben Methode.

Ich kenne die Dalvik-VM auch nicht im Detail, aber ich könnte mir vorstellen, dass der Speicher lokaler Variablen nachdem die Methode fertig ist einfach wieder freigegeben wird. Wenn du einen Wert zurück gibst, ist der GC für das Aufräumen zuständig. Der Garbage Collector wird auf Android wahrscheinlich wesentlich seltener laufen als bei einer "normalen" JVM, denn das würde ja Akku kosten.

Auf einer "normalen" JVM ist es egal von wo Speicher auf dem Heap allokiert wurde, da ist immer der Garbage Collector für die Speicherverwaltung zuständig. Deshalb gibt es keinen Unterschied zwischen "Objekt zurückgeben und dann etwas damit machen" und "Objekt nur lokal anlegen und in der selben Methode etwas damit machen".
 
@Marco13: Danke für die Links, ich werd mir das mal näher ansehen. Ich könnte jedenfalls versuchen, wirklich mal manuell den GC zu aktivieren, bevor ich ein Bild lade, um das zu testen. Bei Gelegenheit muss ich das noch tun, um herauszufinden, welche Exception das genau ist...

@...ButAlive: Danke auch für Deinen Link. Diese Kleinigkeit mit Canvas übergeben habe ich schnell getestet und leider hat sich da nichts geändert.

Sobald ich wieder Zeit finde, werde ich dem Thema noch einmal genauer nachgehen...
 
Wenn dein value häufig Werte hat die mehr als 16 Zeichen im Ergebnissstring benötigen kannst du die Methode noch ein kleines bischen optimieren. Ich schätze mal 10% weniger Speicherverbrauch könnte man in dem Fall erreichen.

Java:
    private String test(long value) {
        return new StringBuilder("abc".length()+20).append("abc").append(value).toString();
    }

So gut wie alle Java Compiler erzeugen bei deinem Code zunächst einen StringBuilder mit der Kapazität ("abc".length()+16) Wenn dann versucht wird eine große long Zahl anzuhängen fehlt ein paar Zeichen im internen char[] und es wird ein größeres erzeugt und die Daten werden umkopiert. Das neue char[] hätte dann afaik eine größe von ("abc".length()+16)*2+2 was für deutlich mehr wäre als für das anhängen des values notwendig wäre.

Du solltest aber besser erstmal schauen ob du irgendwie mit weniger Aufrufen der Methode auch zum richtigen Ergebniss kommen kannst. Eventuell kannst du das Ergebniss ja einfach irgendwo Zwischenspeichern wenn es sich nicht bei jedem Rendern ändert.
 

Zurück
Oben