Quantenphysik, Debugging und Memory-Leaks…

… wie passt das zusammen?

Doch fangen wir von vorne an. Im Laufe der letzten Jahre habe ich mir einige schöne Libs und einen in meinen Augen hübschen Stil für Objektorientiertes JavaScript angeeignet. Bislang gab es damit auch seltenst Probleme, jedoch jetzt jüngst in einem grossen Projekt auf Arbeit (ipeak) kam es dann doch zu einem Problem:

Einige Objekte wollten einfach partout nicht aus dem JavaScript-Heap verschwinden.

Fehlersuche und Debugging

Einen Vorteil hatte die Geschichte jedenfalls: Ich habe mich sehr intensiv mit den Memory-Tools von Google Chrome auseinandergesetzt. Bislang war das bei meinen Anwendungen meistens nicht sooo wichtig, in diesem Projekt aber sollte die Anwendung mehrere Stunden, ggf. Tage laufen – ohne Refresh. Da schaut man dann doch einmal ein wenig genauer auf die Speicherauslastung.

Zusätzlich zum Memory-Profiling habe ich natürlich auch noch via Debugging und einige console.log Statements versucht, das Code-Verhalten nachzuvollziehen und zu verstehen, warum die Objekte nicht gelöscht werden.

Die „Retainers“-Ansicht im Memory-Profile war diesmal seltsamerweise völlig unsinnig. Chrome konnte nicht einmal mehr sagen, welche Distance die Objekte zu meinem Window hatten, und es gab extrem tief verschachtelte Strukturen.

Zunächst hatte ich die verschiedenen Libraries in Verdacht (klar, man vermutet den Fehler immer zunächst bei anderen):

Lodash und Bind

Ich nutze sehr sehr gerne ein _.bindAll(this) in den Konstruktoren meiner JavaScript-Klassen, um das this-Verhalten aus Sprachen wie Java nachzuahmen. Irgendwie liegt mir das besser und ist verständlicher…

Und tatsächlich gibt es hier sogar einen Bug in der aktuellen lodash Version – siehe Stackoverflow! Also zunächst manuelles Einbauen des Fixes, dann Switch auf die 3.0-pre. Doch der Fehler blieb…

Promises, Nested Promises, Q.js

Auch hier vermutete ich den Fehler… Genau an der Stelle wo der Fehler auftrat, spielten diverse Promises in teilweise tiefen Verschachtelungen eine Rolle. Aber auch ein Schritt-für-Schritt-weises Auflösen der Promises brachte keinen Erfolg. Die Promises scheinen soweit in Ordnung.

Der Ausgang eines Experimentes ist niemals unabhängig von der Betrachtung…

… so einer der Grundsätze aus der Quantenphysik.

Eigentlich ist es logisch, und doch… bin ich darauf reingefallen. Ein console.log auf ein Objekt stellt natürlich auch eine Referenz dar. Das heisst genau dadurch, dass ich meine Objekte beobachten und den Speicherverbrauch / die Abläufe besser verstehen wollte, habe ich sie zerstört.

Objekte, die via console.log ausgegeben werden, werden  – zumindest im Chrome – *nicht* vom Garbage-Collector erfasst. Und dementsprechend seltsam sieht dann auch (wie das bei mir war) die „Retainers“-Tabelle aus.

Das besonders gemeine: Natürlich auch alle von diesem Objekt referenzierten anderen Objekte nicht. Und da spielte dann wieder meine Promise-Chain mit rein, durch die sehr viel am Leben blieb, obwohl nur ein kleines Objekt ausgegeben wurde.

Hier ein Fiddle als Beweis -> Chrome-Tools, Profiles -> HeapSnapshot -> nach „My“ suchen. Und sehen: Das gelogte Objekt wird nicht freigegeben.

Hier noch ein Screenshot aus dem Profiler:

memory-leak2

 

Wer also selber mal über solch seltsame Retainers mit „_idToWrappedObject“ und „InjectedScript“ stolpert, sollte vielleicht auch mal auf sein console.log/console.err schauen :).

Anmerkung: Sobald man die Console löscht/cleared ist im übrigen auch das Memory-Profile wieder sauber, und das Objekt wird wie gewünscht freigegeben.

 

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.