Michael_K: Memory leak - Hilfe bei der Spurensuche

Hallo,

ein Javascript, dass im Browser läuft, gibt nicht den Speicher frei. Wie kann man herausfinden, in welcher Variable die Daten "gefangen" sind? Sowohl Firefox als auch Chrome haben das gleiche Verhalten, dass ca. 80-100 MB nicht freigegeben werden, nachdem die Berechnungen durchlaufen sind und das Ergebnis angezeigt wird. Im Firefox wird mir beim Snapshot angezeigt, dass die 80 bis 100 MB in einem String stecken, aber wie kann ich auf die Spurensuche gehen, welche Variable/Klassen etc. diesen Speicher allokiert? Die Suche im Netz hat mich etwas ratlos gelassen, wäre dankbar für gute Lektüre

So sieht der Snapshot im FF aus:

Gruss Michael

akzeptierte Antworten

  1. Hallo Michael_K,

    Das ist nicht ein String, sondern 125958 Strings, meine ich. Also im Durchschnitt 377 Zeichen pro String

    Meine Vermutung wäre, dass eine Menge Speicher in Eventlistenern steckt, die unbemerkt Closures gebildet haben. Ohne Codeinspektion ist das aber nicht näher zu analysieren.

    Mich irritiert auch die Masse an Scripts, 19000 Stück? Registrierst du DOM-Events mit onclick o.Ä im HTML?

    Rolf

    --
    sumpsi - posui - obstruxi
    1. Lieber Rolf,

      Meine Vermutung wäre, dass eine Menge Speicher in Eventlistenern steckt, die unbemerkt Closures gebildet haben.

      das ist meistens der Fall, wenn man nicht weiß, dass man EventListener nicht an DOM-Elemente bindet, sondern an das document-Objekt, um dann in den Listener-Funktionen das event.target entsprechend zu prüfen. Der Grund ist ja, dass wenn man Elemente aus dem DOM entfernt wie z.B. einen Button, auf den ein EventListener gelegt worden war, dass der EventListener eben im Speicher verbleibt - ebenso wie der entfernte Button! Der Garbage-Collector kann dann nämlich nix wegwerfen, weil es Bezüge zu anderen Objekten gibt.

      Liebe Grüße

      Felix Riesterer

      1. Hallo Felix Riesterer,

        Der Grund ist ja, dass wenn man Elemente aus dem DOM entfernt wie z.B. einen Button, auf den ein EventListener gelegt worden war, dass der EventListener eben im Speicher verbleibt - ebenso wie der entfernte Button!

        DAS kann aber nur passieren, wenn eine Referenz auf den Button noch anderswo gehalten wird, oder? Normalerweise müsste doch das HTMLButtonElement Objekt und die daran baumelnden EventListener entsorgt werden. Da JavaScript aber keine Finalizer (a.k.a. Destruktoren) kennt, kann man das schlecht bewei… breeeeems

        Es gibt sie. Indirekt. Ich hab jetzt nur keine Zeit, aber da klemm ich mich mal hinter, ob DOM Elemente, die man aus dem DOM löscht, tatsächlich nicht finalisieren.

        Rolf

        --
        sumpsi - posui - obstruxi
        1. Lieber Rolf,

          DAS kann aber nur passieren, wenn eine Referenz auf den Button noch anderswo gehalten wird, oder?

          vielleicht war das mal ein Problem und ist heute durch schlauere Garbage-Collectoren beseitigt. Keine Ahnung. Jedenfalls hatte ich vor einigen Jahren mal für den TinyMCE einen Vorschlag eingereicht, wie man den damals sehr rudimentären Suchen&Ersetzen-Dialog aufpeppen könnte. Der Entwickler hatte mir damals prompt ein Memory-Leak attestiert, eben weil ich etwas in der Art gemacht hatte (Element dynamisch erzeugen, Eventhandler dranpappen und dann Element entfernen).

          JavaScript [...] Finalizer (a.k.a. Destruktoren)

          Spannend! Klingt, als könnte es in dieser Sache Abhilfe schaffen. Aber ob das für den OP überhaupt relevant ist? Sein Problem könnte ja andere Ursachen haben.

          Liebe Grüße

          Felix Riesterer

          1. Hallo Felix,

            "moderne" Browser können das, d.h. Event handler für gelöschte Elemente werden auch vom Garbage Collector erfasst.

            Der IE konnte das nicht. Das führte tatsächlich zu Memory Leak, wenn man nicht sorgfältig die Listener entfernt hat. Aber IE ist ja nun wirklich Altlast und zumindest ich bin "faul" geworden, weil die GC in mondernen Browsern gut arbeiten.

            Wenn man es wirklich braucht, würde ich es heute mit Abort Controller umsetzen, mit dem man nun auch EventListeber entfernen kann. Das wäre aus meiner Sicht die elegantere Variante. Nur die Browser, die Abort Controller unterstützen haben auch gute GC :-)

            Gruss Michael

      2. Servus!

        Lieber Rolf,

        Meine Vermutung wäre, dass eine Menge Speicher in Eventlistenern steckt, die unbemerkt Closures gebildet haben.

        das ist meistens der Fall, wenn man nicht weiß, dass man EventListener nicht an DOM-Elemente bindet, sondern an das document-Objekt, um dann in den Listener-Funktionen das event.target entsprechend zu prüfen.

        Ich hab' grad mal geschaut, wie das in unserem Wiki erklärt wird.

        In den Einsteiger-Tutorials wird's kurz angerissen.

        Aber

        JavaScript/DOM/EventTarget/addEventListener

        • ist ausführlich und für Fortgeschrittene geeignet - eigentlich ein Tutorial
        • hat aber einen sehr langen Seitennamen (der „technisch“ berechtigt ist, auf Mobilgeräten aber abgeschnitten wird)
        • die übergeordnete Seite JavaScript/DOM/EventTarget hat dagegen nur sehr wenig Inhalt und verlinkt auf
          • AddEventListener
          • dispatchEvent
          • RemoveEventListener

        Ketzerfrage (muss nicht heute beantwortet werden)

        Könnte man nicht die 4 Seiten

        • zusammenfassen
        • ein Beispiel entwickeln und erklärend weiter ausbauen?
        • und dann nach einem passenden Seitennamen / -ort suchen?

        Das würde ich gerne mal bei einem Stammtisch besprechen.

        Herzliche Grüße

        Matthias Scharwies

        --
        Ich habe heute rausgefunden, dass in das Pizzafach meines Rucksacks auch ein Laptop passt!
        1. Hallo,

          ich konnte die Ursache finden und das Problem lösen. Ein große Hilfe war mir die Memory Snapshot-Funktion von Chrome/Chromium. Dort kann man gut herausfinden, wieviel Speicher bei welchem Funktions bzw. Klassenaufrufe "erhalten" bleiben. Das ist echt gut gemacht. Mit dem "system/Context" Constructor sieht man direkt, wieviel "retained" Speicher durch welche Code-Zeile verursacht wird.

          Wie schon richtig von Euch vermutet, war es ein erstellter EventListener. Ziemlich banaler Fehler bzw. Unachtsamkeit meinerseits, die Variable direkt in die CB Funktion zu schrieben.

          Aber zusätzlich war es auch eine async Abfrage mit der IndexedDB, die ca. 60 MB gebunden hat. Das war merkwürdig, weil ich es eigentlich richtig genutzt hatte. Ich habe dann den Code leicht umgeschrieben bzw. vereinfacht (muss ich mir in einer ruhigen Minute noch einmal ansehen). Nun leistet der Garbage Collector seine Arbeit und der Speicherverbrauch ist wie erwartet bei ca. 4-7 MB (FF als auch Chrome)

          Gruß Michael