![]() |
JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo zusammen,
ich stehe vor einem mittelprächtigem Problem heute morgen und komme auch nach Stunden des Debuggens kein Stück weiter. Ich habe ein Klassenkonstrukt, welches aus einer JSON-Datei mit Leben gefüllt wird (Einstellungen). Nun kontrollierte ich mittels FastMM4 {$define FullDebugMode} und es bleiben reichlich Speicherlecks. Also eigentlich bleibt so ziemlich alles über, was da reingeladen wurde :o 1.) Im Konstruktor kann man die TObjectList<..>.Create komplett weglassen, diese werden vom JSON-Loader erzeugt 2.) Versuche ich die Objekte vom Typ TObjectList im Destruktor freizugeben bekomme ich einen abstrakten Fehler. Ist im Quelltext momentan auskommentiert, da es eh nur kracht. Ich habe mir mal ein kleines Testprojekt erzeugt, welches den Sachverhalt mit einer simplen, verschachtelten Klasse nachstellt. Dachte erst es liegt an der Komplexität meiner grossen Klasse und ich habe irgendetwas übersehen. Aber funktioniert auch mit diesem simplen Beispiel nicht korrekt. Das Testprojekt braucht das Verzeichnis C:\Temp, Populate legt die JSON-Datei an, danach Load und anschliessend Free. Habe das Ganze mal als fertiges Projekt angehängt, allerdings wird FastMM4.pas im Pfad erwartet und steht wiegesagt bei mir auf FullDebug. Wie werde ich den Speicher wieder los, den mir der JSON-Loader durch dynamische Creates freundlicherweise belegt hat? |
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
(ignoriert mich)
|
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
-- OK, dann ignoriert mich auch :-D --
|
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
War gerade eine am Hochladen, da fangt Ihr hier mit Eurer "Ignore"-Orgie an :-D
|
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
Einfach ignorieren ;)
|
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
Elende Ignoranten! :o
Zu den Speicherlecks: populate 1) In btnPopulateSaveClick wird die lokale entryList nicht freigegeben 2) Der Destruktor von TEntryList gibt seine FEntryLists nicht frei -> Kein LEck mehr beim fröhligen populieren load 1) Du überschriebst das FEntryList-Feld deiner Form mit einer neuen Instanz ohne eine evtl bereits bestehende freizugeben Weiterhin gibt das Formular sein
Delphi-Quellcode:
nicht frei. Damit sind wir bei deinem Punkt 2)
FEntryLists
Woher der
Delphi-Quellcode:
kommt verstehe ich auch nicht.
EAbstractError
Bei mir (XE7) kracht es dann in System.Generics.Collections:1111 durch das FArrayManager.Move(..) FArrayManager ist vom Typ TArrayManager<formHaupt.TEntries>. Keine Ahnung was da schief läuft. Jemand eine Idee? Ein "normales"
Delphi-Quellcode:
klappt ja auch ohne Probleme. Ich hätte gedacht dass der Json-Mechanismus da irgendwie die TObjectList nicht richtig aufbaut...
TObjectList<TEntries>.Create().Free();
|
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
OK, 1) hätte ich Dir/Euch ersparen können :(
Zu 2) Ja, ist ja auskommentiert, weil es nach dem JSON-Load immer knallt. Ansonsten würde sich das Objekt sauber in Wohlgefallen auflösen. Zu der Form-Variablen FEntryList - die wird nur mit dem "Free"-Button freigegeben. Also quasi auf Anforderung, weil ich dann besser debuggen konnte. "Load" und "Free" (Breakpoint) drücken, also quasi nach dem Motto "Jeder nur ein Kreuz!". Bei mir in XE5 kommt der abstrakte Error in Generics.Collections beim Zuweisen von Capacity, in der Folge wohl die gleiche Stelle wie bei XE7:
Delphi-Quellcode:
Ein normales .Free klappt ja auch ohne Speicherlecks, auch bei den Generics. Solange ich nicht zwischendrin das Ganze per REST.Json aus einer Datei habe laden lassen :(
procedure TList<T>.DeleteRange(AIndex, ACount: Integer);
var oldItems: array of T; tailCount, I: Integer; begin if (AIndex < 0) or (ACount < 0) or (AIndex + ACount > Count) or (AIndex + ACount < 0) then raise EArgumentOutOfRangeException.CreateRes(@SArgumentOutOfRange); if ACount = 0 then Exit; SetLength(oldItems, ACount); FArrayManager.Move(FItems, oldItems, AIndex, 0, ACount); // Die hier knallt Ich würde, um himitsu's Fußzeile herauszukramen, meinen Müll gerne wegräumen, wenn ich denn wüsste wie :D Zitat:
|
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
Wenn man sich das produzierte JSON ansieht merkt man aber auch dass das Feld
Delphi-Quellcode:
komplett leer ist:
arrayManager
Code:
Da läuft also was falsch.
"arrayManager": {}
Vorhin hatten wir ja auch im Debugger gesehen, dass wir zur Laufzeit (nach dem Zusammenbasteln des Objekts aus dem String) hier einen
Delphi-Quellcode:
haben. Der ist komplett abstrakt. Wir sollten einen
TArrayManager<>
Delphi-Quellcode:
haben.
TMoveArrayManager<>
Ich bin noch kompletter "Delphi vs. JSON"-Anfänger. Kann es sein, dass man dem Automatismus eine
Delphi-Quellcode:
schlichtweg nicht anvertrauen darf? Dass er
TObjectList<>
Delphi-Quellcode:
als ein JSON-Array serialisiert sah zwar schon vielversprechend aus, aber über diesen eigentlich unnötigen ArrayManager stolpert er irgendwie...
items
== PS Wir entfernen uns hiermit aber auch eigentlich vom Thema Speicherlecks hin zu "Die JSON-Bilbiothek kann keine TObjectList<> serialisieren". Ob man den Titel anpassen sollte? |
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
Hallo,
ich glaube nicht, dass die Angabe von "arrayManager" im Json-String unnötig ist. Habe ihn gerade in der json-Textdatei mal händisch entfernt. Dann kommt er schon beim Laden damit nicht parat. Es wäre äußerst bitter, wenn Du Recht hast und man keine generischen Listen aus dem Objekt in einen String und vice versa lesen kann, ohne jedesmal ein massives Speicherleck zu produzieren :evil: Ich benutze das an vielen Stellen in meinem Programm, nicht nur für die Einstellungen. U.a. werden Objekte über das Netz von einem Rechner zum anderen serialisiert. Und die Objekte sind nicht immer so übersichtlich wie in der kleinen Demo hier. Frage mich dann aber wieder warum es denn eingebaut ist, wenn es nicht fehlerfrei funktionieren sollte? Und das doch schon mal jemanden hätte auffallen müssen (XE5..XE7) und suche den Fehler dann in erster Linie erstmal in meinem Code. Habe mit einiger Suche jetzt auch noch ein paar Info's dazu gefunden, hier ist auch von MemoryLeaks die Rede und dem Rat auf den Umstieg auf das SuperObject? OMG, wäre das ein Angang :( ![]() ![]() Und hoffe immer noch inständig, das noch eine Idee dazu kommt, um es ohne einen solchen Zusatzaufwand in den Griff zu bekommen. Ach ja, für XE5 sind alle verfügbaren Patches eingepielt, aber das nur am Rande. Die Leaks werden auch ohne FastMM4 beim Beenden des Programms angezeigt. Habe das Archiv eben nochmal erneuert, sollte jetzt so überall lauffähig sein. Gruß |
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Zitat:
Das erste erzeugen von FEntrylist ist unnögtig und erzeugt ein Speicherleck, da TJsonToObject das übernimmt. Geht also auch mit Kommentaren.
Delphi-Quellcode:
// FEntryList := TEntryList.Create;
sl := TStringList.Create; sl.LoadFromFile('C:\Temp\loading.json'); try FEntryList := TJson.JsonToObject<TEntryList>(sl.Text); |
AW: MemoryLeak (FastMM4) nach Befüllen einer Klasse aus JSON-String
<noch nicht ganz korrekte Fehlerbeschreibung> (daher beitrag gelöscht)
|
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Das hat wohl keine Relevanz, aber das Property SecondClassEntries verweist auf das falsche Feld.
|
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Liste der Anhänge anzeigen (Anzahl: 1)
Danke für den Hinweis Uwe. Das habe ich durch Blick in die json-Datei auch schon bemerkt und in der "aktuellen" Version des kleinen Testprogramms korrigiert.
Habe auch die bisherigen Info's von stoxx mit eingearbeitet (2 x FEntryList). Der abstrakte Error ist jetzt weg, aber das Speicherleck bleibt nach wie vor erhalten, weil die destructor-Routinen der untergeordneten Objekte beim .Free des aus json erzeugten Objektes nicht aufgerufen werden. Nach Erzeugung durch den überladenen Konstruktor wird jetzt zwar TEntryList.destroy ohne abstrakten Fehler aufgerufen, die enthaltene Liste FEntryLists ist aber (bereits?) nil. Werde das Archiv mal eben aktualisieren. Schade geht nicht, kann den ersten Beitrag nicht mehr bearbeiten :( Also dann hier dran. |
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Liste der Anhänge anzeigen (Anzahl: 1)
also nun nochmal komplett.
im Anhang eine Version ohne Speicherleck. Zum einen ist zu beachten, dass eine Objectlist keine TCollection ist, also die Elemente der Liste müssen manuell selbst im Destructor gelöscht werden. Delphi hat aber ein Bug in "DeleteRange" in TList<> und TObjectList<>, welcher den Abstrakten Fehler erzeugt. Umgehen kann man ihn, indem man jedes Element einzeln im Destructor löscht. (absteigend durchgehen) Erzeugung über JSON jetzt besser über eine class function. Im Anhang ein funktionierendes Projekt (ohne Speicherlecks)
Delphi-Quellcode:
destructor TObjectsTEntry.destroy;
var i : Integer; entry : TEntry; begin for i := self.Count-1 downto 0 do begin entry := self.Items[i]; entry.Free; self.Delete(i); end; self.Clear; inherited; end;
Delphi-Quellcode:
class function TEntryList.Create(aJSON: string) : TEntryList;
begin result := TJson.JsonToObject<TEntryList>(aJSON); end; |
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Hallo,
vielen Dank für Eure Mühe und stoxx für das Projekt ohne Speicherleck! Ich bin bis eben gerade noch davon ausgegangen, dass TObjectList<TIrgendwas> beim Erzeugen genauso OwnsObjects als Default auf True hat und sich beim .Free der ObjectList damit auch die Listenelemente selbst freigeben? Bei allen anderen Objektkonstrukten, die bei mir kein Json nutzen, funktioniert das mit dieser Automatik auch bisher wunderbar und ohne Speicherverluste. Siehe in meiner Demo z.B. beim Populate-Button, lokale entryList wird am Ende freigegeben und erzeugt kein MemLeak. Kommentiere ich das am Ende gibt es ein MemLeak. Oder taucht der von Dir angesprochene Bug nur auf, wenn man zusätzlich Json nutzt und muss man sich deshalb selbst um die Freigabe kümmern? Zitat:
|
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Zitat:
Ich bin aber sowieso eher klassisch unterwegs und geb alles, was ich erzeugt habe, auch wieder frei. Gleich nach dem Schreiben vom Constructor fülle ich ebenso den Destructor. Bei Klassen mit Treestrukturen würde die Automatik sicher gut funktionieren, aber nicht immer sind ja Objekte gleich Owner, nur weil irgendwo ein Link drauf ist, da muss man eh selber freigeben. |
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Zwei Dinge konnte ich feststellen:
1. Der abstrakte Fehler kommt, weil der
Delphi-Quellcode:
mit im JSON-String steht. Dadurch wird beim Konvertieren in das Objekt eine
arrayManager
Delphi-Quellcode:
instantiiert, obwohl dies nur die abstrakte Klasse ist.
TArrayManager
2. Das
Delphi-Quellcode:
in
inherited
Delphi-Quellcode:
und
TObjectsEntries.create
Delphi-Quellcode:
ruft
TObjectsTEntry.create
Delphi-Quellcode:
auf und nicht
TList<T>.Create
Delphi-Quellcode:
. Dadurch wird
TObjectList<T>.Create
Delphi-Quellcode:
nicht gesetzt und man muss die
OwnsObjects
Delphi-Quellcode:
im
Items
Delphi-Quellcode:
selbst wegräumen. Ersetzt man das
Destroy
Delphi-Quellcode:
durch
inherited
Delphi-Quellcode:
funktioniert es.
inherited Create
Den abstrakten Fehler bekommt man nicht ganz so einfach weg. Eigentlich müsste das in
Delphi-Quellcode:
durch ein entsprechendes Attribut geregelt werden. Alternativ kann man das
System.Generics.Collections
Delphi-Quellcode:
wegschmeißen und den Spaß von Hand nachbilden. Würde
TJson.ObjectToJsonString
Delphi-Quellcode:
in der ersten Zeile statt
TJson.ObjectToJsonObject
Delphi-Quellcode:
besser
TJSONMarshal.Create(TJSONConverter.Create)
Delphi-Quellcode:
aufrufen, könnte man ja noch was mit den MarshalFlags machen - ist leider nicht.
TJSONConverters.GetJSONMarshaler
|
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Ich denke ich werde auch dazu übergehen mich um alles selbst zu kümmern. Da will man einmal Verantwortung abgeben :? Automatismus hin und her, man sieht, was man davon hat sich auf diesen zu verlassen. Werde alles auf TList<..> umstellen, sauber hinter mir aufräumen und fertig ist.
Nichts desto trotz sehe ich das hier beschriebene Problem im Endeffekt als Bug und habe es für die zukünftige Version XEx++ mal in der qc gemeldet. Auch wenn wir nicht jeden Update-Zyklus mitnehmen, vielleicht wird es irgendwann gefixt werden. Das meines Erachtens Blöde daran ist, dass man es gar nicht so offensichtlich mitbekommt. Solange man nicht gezielt nach Speicherlecks sucht, was man immer mal wieder tun sollte :P Die Stelle der Freigabe in einer ObjectList ist imho nicht der destructor, sondern die Nachricht, dass ein Objekt aufgelöst wurde
Delphi-Quellcode:
procedure TObjectList<T>.Notify(const Value: T; Action: TCollectionNotification);
begin inherited; if OwnsObjects and (Action = cnRemoved) then Value.DisposeOf; end; Zitat:
|
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Um das nochmal deutlich zu machen: Das Problem taucht bei der Kombination von TJON und TList<T> auf. Die aktuelle Implementierung des JSON-Marshaling stellt bestimmte Anforderungen an die zu konvertierenden Klassen, die von TList<T> (noch) nicht erfüllt werden.
Es ist halt schon immer so gewesen, daß die einfachen Lösungen nur für einen Teil der Anwendungsfälle funktionieren. Für die übrigen muss man dann wieder selbst Hand anlegen. |
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Zitat:
|
AW: JSON-Serialisierung von generischen Listen nur mit MemoryLeaks (FastMM4)
Na gerne doch
Hier die QC-Meldung ![]() Und die beiden Projektuploads sind hier zu finden ![]() Werde dort noch eben die letzten Erkenntnisse ergänzen |
Alle Zeitangaben in WEZ +1. Es ist jetzt 14:23 Uhr. |
Powered by vBulletin® Copyright ©2000 - 2025, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz