AsyncTask verursacht OutOfMemory Exception

  • Antworten:16
p a
  • Forum-Beiträge: 131

16.11.2011, 15:27:20 via Website

Hi,

ich hab folgendes Problem und zwar ...

führe ich beim start meiner App einen AsyncTask aus, der Daten aus einer fast 6mb großen JSON-Datei liest und die in eine Datenbank schreibt.
Wobei die App dann nach nicht allzulanger Zeit eine absehbare "OutOfMemoryException" auslöst.

hier ist mal ein vergleichbarer Quellcodeteil (der echte ist noch einiges länger)
1InputStream is = context.getResources().openRawResource(availibilityJson);
2 BufferedReader br = new BufferedReader(new InputStreamReader(is, "UTF-8"));
3 StringBuilder alles = new StringBuilder();
4 String line;
5 while ((line = br.readLine()) != null)
6 {
7 alles.append(line);
8 }
9 is.close();
10 jsonArray = new JSONArray(alles.toString());
11 for (int i = 0; i < jsonArray.length(); i++)
12 {
13 JSONObject obj = jsonArray.getJSONObject(i);
14 String ean = obj.getString("EAN").trim();
15 String vk6 = obj.getString("vk6").trim();
16 Availability availability = new Availability(ean, vk6);
17 Datenbank.availibilityEintragen(availability);
18 }

Meine Frage ist jetzt, wie kann ich diesen Code ein wenig "glätten" damit er nicht mit so großen Datenmengen hantieren muss?
Also quasi 100 Datensätze lesen und in die Datenbank schreiben, dann wieder alles aus dem Speicher schmeissen und die nächsten 100 Datensätze verarbeiten.

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

16.11.2011, 15:40:46 via Website

Zeilenweise einlesen ist ja schonmal gut, aber wenn du es als JSON Objekt verarbeiten willst, wirst du es eh immer komplett einlesen müssen.
Das ist auch der Nachteil bei XML mit JDOM gegen SAX.

Hast du Einfluss auf das Format der Datei?
Wenn ja, dann bau sie doch so auf, dass pro Zeile ein Datensatz steht.
Dann lies sie Zeilenweise ein und extrahiere die Datensätze aus jeder Zeile einzeln z.B. mit einem Regex Pattern, oder einfach mit substrings.

JSON schön und gut, aber für große Datenmengen braucht man eine Datenstruktur, die häppchenweise Streaming-fähig ist.

— geändert am 16.11.2011, 15:42:06

Antworten
p a
  • Forum-Beiträge: 131

16.11.2011, 15:45:14 via Website

nein, ich habe keinen einfluss auf das format der Datei (*träum* wäre das schön)

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

16.11.2011, 15:54:58 via Website

Ist sie denn zufällig zeilenweise aufgebaut?
Dann musst du dir ja nur überlegen wie du sinnvolle Häppchen aufteilst und diese dann nacheinander verarbeitest.

So wie ich das sehe besteht ein Datensatz ja nur aus 2 Attributen.
Da sollte ja machbar sein.

Sonst liest du halt eine definierte Anzahl von Zeichen, extrahierst daraus mit Regex die Schlüssel/Wert Paare.
Verwirfst das bisher gelesene und liest wieder X Zeichen.
(natürlich darfst du nicht mitten in einem Datensatz trennen).

— geändert am 16.11.2011, 15:55:22

Antworten
Gelöschter Account
  • Forum-Beiträge: 281

17.11.2011, 20:32:27 via Website

Bei aller grundsätzlichen Diskussion, man könnte ja vor dem Iterieren über das JSON-Array den StringBuilder alles auf null setzen, dann wären ja schon mal 6MB weg... Netter Anteil, wenn man nur 16 hat... Oder seh ich da was falsch?

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

17.11.2011, 20:55:57 via Website

Was tatsächlich gehen könnte:

1String allesString = alles.toString();
2alles = null;
3jsonArray = new JSONArray(allesString);

Wenn man rechnet, dass der StringBuffer 6MB hat, dann ein toString() gemacht wird für die Übergabe an JSON,
wobei ein neuer String mit der gleichen Kapazität erzeugt wird, also nochmal 6MB.
Dann liest das JSON Objekt nochmal eine neue Datenstruktur mit diesen Daten ein, also nochmal 6MB + Overhead...macht 18 MB.

Nimmt man den StringBuffer weg, sind es halt immernoch 12MB + Overhead...könnte gehen, wobei das halt auch zum Problem wird, falls die Datei doch mal wächst, oder man Speicher durch Grafiken etc. verbrät.
Ich würde es lieber häppchenweise lesen...das ist sauberer und performanter.

Antworten
Gelöschter Account
  • Forum-Beiträge: 281

17.11.2011, 22:04:47 via Website

Das ist schon klar, es ging ja auch nur um "erste Hilfe" ;-)

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

18.11.2011, 08:49:50 via Website

Thomas M.
Das ist schon klar, es ging ja auch nur um "erste Hilfe" ;-)
Ist ja auch ein völlig richtiger Aspekt, den du da aufgezeigt hast :)
Ich wollts halt ein wenig genauer erklären, weil das ja auch nur funktionieren kann, wenn man die Erzeugung des JSON Objekts und das .toString() trennt und dazwischen den StringBuffer aus-nullt.
Daran wäre die Umsetzung sonst nämlich garantiert gescheitert :)

Antworten
Gelöschter Account
  • Forum-Beiträge: 9

18.11.2011, 12:19:48 via Website

Hi,

Du könntest die Datei byte für byte einlesen und selber parsen (sollte bei der einfachen Struktur nicht das große Thema sein), oder eine Streaming fähigen Parser verwenden. Siehe :
http://jackson.codehaus.org/
oder
http://code.google.com/p/json-simple/

Noch eine Kleinigkeit : Zwecks Performance würde ich empfehlen mit Availability availability = new Availability(ean, vk6) nicht innerhalb der Schleife jedes mal ein neues Objekt erzeugen, sondern 1x vor der Schleife erzeugen und dann nur die Werte setzen (oder gleich die Strings an die DB Funktion übergeben. Das schont den Garbage Collector.

hth

Antworten
p a
  • Forum-Beiträge: 131

21.11.2011, 13:56:05 via Website

Soo hab es jetzt hinbekommen, schien echt daran zuliegen dass er soviele Objekte erzeugen musste.

Jetzt laufen alle 4 "Sachen aus JSON in die Datenbank schreiben" Tasks vernünftig durch.

Allerdings hab ich jetzt das Problem dass das eintragen der 6mb Datei über 10 Minuten dauert, das läuft natürlich im Prinzip nur bei der ersten Initialisierung durch, aber ist dennoch viel zu lang.
Weiss da vielleicht jemand Möglichkeiten wie sich das einlesen von JSON weiter optimieren lässt, also mindestens um die Hälfte müsste ich die Zeit schon noch drücken die das "in die Datenbank schreiben" im Moment braucht.

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

21.11.2011, 14:26:12 via Website

Wie gesagt sind solche komfortablen Datenstrukturen wie JSON oder DOM bei XML echte Performance-Killer.
Ich würde das Einlesen häppchenweise "streamen" wie ich bereits oben beschreiben habe, dann sparst du dir die ganze Konvertierung in JSON.

Mach doch einfach mal ein paar einfache Ausgaben der Systemzeit in den Code, um zu sehen welche Operation die meiste Zeit verbraucht und setz da mit der Optimierung an.

Antworten
p a
  • Forum-Beiträge: 131

21.11.2011, 14:31:56 via Website

Welche Konvertierung in JSON kann ich mir sparen?
Die Daten liegen mir ja in JSON vor, ich schreib auch schon die ganze Zeit Logeinträge, er schafft ca 23 Einträge pro Sekunde, ich hab nur leider über 11.000 Einträge, darum dauert das so lange.
Die Zeiten die da benötigt werden kenne ich im wesentlichen, ich könnte sie auch anhand meines Logs bis ins kleinste Detail nachvollziehen, wenn ich das wollte.

Antworten
Rafael K.
  • Forum-Beiträge: 2.359

21.11.2011, 14:37:52 via Website

Ja dann mach das doch mal.

Interessant ist doch welche Operation wie lange dauert.
- Das Laden der Daten vom Server
- Das Erstellen des JSON Objekts
- Das holen einer Zeile aus dem JSON Objekt
- Der INSERT in die Datenbank

Wie man das ganze streamen könnt hab ich ja oben schon beschrieben.
Dann musst du kein riesiges JSON Objekt erzeugen, sparst Speicher, arbeitest auf kleineren Datenmengen...das hätte nur Vorteile.

Ansonsten solltest du evtl. mal die einzelnen INSERTS in eine Transaktion kapseln.
http://stackoverflow.com/questions/6351808/bad-sqlite-performance-on-external-storage-in-android

— geändert am 21.11.2011, 14:43:11

Antworten
p a
  • Forum-Beiträge: 131

22.11.2011, 10:28:00 via Website

Jetzt vermischt sich grad Freude mit Verwirrung

Ich hab jetzt mit Hilfe von preparedStatements und Transaktionen das Ausführen aller 4 Tasks von ca. 15 Minuten auf 22 Sekunden (!!!) reduziert.
Mehrfach getestet, alle Daten sind da, Freunde sagen dass ist durchaus im Bereich des Möglichen, aber ich kann es nicht ganz glauben.

Was sagt ihr dazu?

Antworten
p a
  • Forum-Beiträge: 131

22.11.2011, 13:36:32 via Website

Ja, stimmt

Hab die beiden Logs jetzt mal verglichen und ausgerechnet das die nötige Zeit um 98,64% (tadaaa) reduziert werden konnte.

Das scheint fast ausschliesslich an den Prepared Statements zu liegen, nicht schlecht, hätte ich nicht erwartet.

Antworten