Hi Marco,
zuerst mal vielen Dank für Deine Mühe ::manklatsch
Ich muss mir das am Montag mal in Ruhe zu Gemüte führen, da ich jetzt noch in eine Besprechung muss und morgen wg. einer Beerdigung gar nicht im Haus bin …
Danke und Gruß
Klaus
Hi Marco,
zuerst mal vielen Dank für Deine Mühe ::manklatsch
Ich muss mir das am Montag mal in Ruhe zu Gemüte führen, da ich jetzt noch in eine Besprechung muss und morgen wg. einer Beerdigung gar nicht im Haus bin …
Danke und Gruß
Klaus
@vfl_freak : sry, dass ich nur so kurze Antworten geschrieben habe, ich hatte leider nicht genug Zeit, um ausführlicher zu sein.
Ein Speicherleck zu suchen, wie die Automatismen vom MAT es machen, ist an dieser Stelle nicht sinnvoll. Das Problem ist ja nicht, dass der Speicher überläuft, sondern dass wohl viele Instanzen von einer Klasse existieren, die nicht existieren sollten.
Mit der VisualVM kann man den HeapDump wahrscheinlich auch schon ausreichend analysieren und muss den MAT gar nicht bemühen. Wenn du dort einen Heapdump erstellt hast, kannst du in der Klassenansicht nach Anzahl oder Namen sortieren, zusätzlich unten einen Filter eingeben. Für den Anfang wäre vielleicht ein Filter nach “awt” sinnvoll. Theoretisch sollte die “schuldige” Klasse 10.000 mal instantiiert sein.
Auf dem folgenden Screenshot habe ich den Heapdump nach “knapsack” gefiltert, um nur noch Klassen aus dem richtigen Package zu finden. Du würdest hier dann awt oder etwas anderes passendes eintragen.
Wenn du in der Klassenansicht (wie auf dem Screenshot) auf eine Klasse doppelklickst, kannst du sehen, von wo die einzelnen Instanzen referenziert werden. Dort kann man sich dann “hochhangeln”, bis man zu der Instanz kommt, die verhindert, dass die Instanz noch nicht vom GC eingesammelt wird.
Im nachfolgenden Screenshot siehst du, dass für die angezeigte ArrayList keine Referenz mehr existiert. Diese ArrayList könnte vom GC also eingesammelt werden. Zu dieser Instanz bin ich gekommen, weil ich mich bei den “Piece” Instanzen zu den referenzierenden Objekten durchgehangelt habe. Da nur die ArrayList die Piece-Instanz, von der ich gekommen bin, referenziert, könnte die Piece-Instanz also auch abgeräumt werden.
Wenn Marco13s Vermutung zutrifft, dass der GC nur noch keine Veranlassung hatte aufzuräumen und daher die finalizer noch nicht aufgerufen wurden, dann sollte das so ähnlich aussehen, wie im Screenshot.
Guten morgen,
kein Problem - und erstmal Danke, für die ausführlichen Erklärungen
Na ja, ganz ausschließen kann ich ein Speicherleck wahrscheinlich leider auch nicht …
Der Speicher läuft (zumindest im Taskmanager) schon ein wenig mit hoch, nur kommt es nie zum Überlauf, da vorher die User-Handles auf 10000 hoch sind und das Ganze dann stoppt
Als Startparameter habe ich ja MIN 64MB und MAX 512MB Heapsize. Von anfangs ca. 140 MB komme dann bis auf ca. 320-350 MB - allerdings für die jp2launcher.exe, worin dann ja vermutlich auch die JVM mit enthalten ist, oder?
Im Heap-Monitor des jVisualVM sieht es gar nicht mal so schlecht aus, da arbeitet auch der GC auch immer wieder mit. Wenngleich auch hier ein leichtes aber stetiges Ansteigen zu sehen ist, was aber IMHO erstmal nicht kritisch ist ( siehe Anlage ‚Heap-Monitor‘)!
Ich werde jetzt mal unseren Stresstest längerer Zeit laufen lassen
Ein erster schneller Versuch eines Heapdumps, ergab nachfolgende Anzeige
bei der mir eigentlich nur die verschiedenen Buffer oben udn Strings interessant erscheinen.
Mal schauen, wie ich damit weiter kommen …
Danke und Gruß
Klaus
Wenn du den Speicherverbrauch im Taskmanager ansiehst, dann ist auch die Laufzeitumgebung mit dabei. Im Screenshot vom HeapMonitor hast du aber den aktuellen „Füllstand“ des Heap schon angezeigt bekommen (in dem Fall 92MB reserviert, von denen sind 78MB belegt).
Was genau liegen bleibt, kannst du mit dem „Compare with another heap dump“-Tool (der Button / Link ist auf deinem zweiten Screenshot oben rechts abgebildet) herausfinden.
Das ist allerdings etwas trickreich, weil die Anwendung ja während des Vorganges abstürzt. Du müsstest es hinbekommen, die Anwendung nichts tun zu lassen, ein GC laufen lassen (über den Button „Perform GC“ im Heapmonitor), einen Heapdump erstellen, ein „bisschen was“ machen lassen, dann noch einmal den GC laufen lassen und dann einen zweiten Heapdump erstellen. Die zwei Heapdumps kannst du dann vergleichen und siehst, was für Objekte im Speicher hinzugekommen sind.
Ansonsten sehe ich in deinem Screenshot auch keine auffällige Klasse.
Edit: der Speicherverbrauch geht laut dem ersten Screenshot gar nicht hoch. Es wird zwar zwischenzeitlich etwas mehr Heapspeicher alloziiert, die Belegung geht nach der GC aber sogar herunter.
Moin,
[QUOTE=cmrudolph]Wenn du den Speicherverbrauch im Taskmanager ansiehst, dann ist auch die Laufzeitumgebung mit dabei. Im Screenshot vom HeapMonitor hast du aber den aktuellen „Füllstand“ des Heap schon angezeigt bekommen (in dem Fall 92MB reserviert, von denen sind 78MB belegt).
[/QUOTE]
Danke, das hatte ich mir auch so gedacht
Aktuelle Werte nach rund 90 Minuten und knapp 1400 angezeigten Meldungen:
Taskmanager: 175 MB, 1900 User-Objekte
jVisualVM: reserviert ca. 120 MB, belegt zwischen 60 und 99 MB.
Ergo, der Verbrauch steigt schon leicht an, aber der Anstieg ist IMHO noch nicht wirklich kritisch (zumal ja immer malö wieder einiges freigegeben wird) …
Ok, dass werde ich dann gleich mal versuchen. „die Anwendung nichts tun zu lassen“ ist kein Problem, da ich in einen „Pause“-Modus schalten kann, da läuft dann nur noch ein zyklischen Timer …
Ich leider auch nicht, aber irgendwo muss ja ein Bock sein … melde mich später nach dem Vergleich!
Danke und Gruß
Klaus
*** Edit ***
tja, dass mich der Vergleich jetzt wirklich weiter bringt, kann ich nicht behaupten :twisted:
und hier nochmal bei kurzem Leerlauf
wass ich im Monitor dann so darstellt:
wobei mich hier erstaunt, dass im Leerlauf zwischen denbeiden GCs der verwendete Speicher doch spürbar steigt …
Gruß Klaus
Jetzt müsste man mehr Ahnung von Swing haben, als ich sie habe.
Es sind ja schon einige Swing-Objekte hinzugekommen. Beispielsweise kamen 64 JPanels hinzu (wenn ich mich recht an die Win32-API zurückerinnere, hat jedes Panel einen Windowhandle - ob das die “bösen” Handles sind, weiß ich wiederum nicht). Soll das so sein? Und wem gehören die 512 Instanzen der EventListenerList?
Insbesondere bei den JPanels lohnt es sich vielleicht einmal in die einzelnen Instanzen stichprobenartig hineinzuschauen. Ganz einfach per Doppelklick auf die Zeile und dann kannst du dir die Eigenschaften ansehen.
Der Zustand nach kurzem Leerlauf passt ja. Die veränderten Objekte sind lediglich welche, die nichts mit der Domäne der Anwendung zu tun haben. Wahrscheinlich gehören allesamt zur Netzwerkfunktionalität.
Die JPanels sollten KEINE Handles belegen (schnell ausprobiert). Die 512 EventListenerList Instanzen gehören irgendwelchen Components.
Jede JComponent hat so einige Fields…
[spoiler]
private boolean isAlignmentXSet;
private float alignmentX;
private boolean isAlignmentYSet;
private float alignmentY;
protected transient ComponentUI ui;
protected EventListenerList listenerList = new EventListenerList();
private transient ArrayTable clientProperties;
private VetoableChangeSupport vetoableChangeSupport;
private boolean autoscrolls;
private Border border;
private int flags;
private InputVerifier inputVerifier = null;
private boolean verifyInputWhenFocusTarget = true;
transient Component paintingChild;
private JPopupMenu popupMenu;
private InputMap focusInputMap;
private InputMap ancestorInputMap;
private ComponentInputMap windowInputMap;
private ActionMap actionMap;
private boolean aaText;
int ncomponents;
Component component[] = new Component[0];
LayoutManager layoutMgr;
private LightweightDispatcher dispatcher;
private transient FocusTraversalPolicy focusTraversalPolicy;
private boolean focusCycleRoot = false;
private boolean focusTraversalPolicyProvider;
private transient Set printingThreads;
private transient boolean printing = false;
transient ContainerListener containerListener;
transient int listeningChildren;
transient int listeningBoundsChildren;
transient int descendantsCount;
transient ComponentPeer peer;
transient Container parent;
transient AppContext appContext;
int x;
int y;
int width;
int height;
Color foreground;
Color background;
Font font;
Font peerFont;
Cursor cursor;
Locale locale;
transient GraphicsConfiguration graphicsConfig = null;
transient BufferStrategy bufferStrategy = null;
boolean ignoreRepaint = false;
boolean visible = true;
boolean enabled = true;
boolean valid = false;
DropTarget dropTarget;
Vector popups;
private String name;
private boolean nameExplicitlySet = false;
private boolean focusable = true;
private int isFocusTraversableOverridden = FOCUS_TRAVERSABLE_UNKNOWN;
Set[] focusTraversalKeys;
private boolean focusTraversalKeysEnabled = true;
Dimension minSize;
boolean minSizeSet;
Dimension prefSize;
boolean prefSizeSet;
Dimension maxSize;
boolean maxSizeSet;
transient ComponentOrientation componentOrientation = ComponentOrientation.UNKNOWN;
boolean newEventsOnly = false;
transient ComponentListener componentListener;
transient FocusListener focusListener;
transient HierarchyListener hierarchyListener;
transient HierarchyBoundsListener hierarchyBoundsListener;
transient KeyListener keyListener;
transient MouseListener mouseListener;
transient MouseMotionListener mouseMotionListener;
transient MouseWheelListener mouseWheelListener;
transient InputMethodListener inputMethodListener;
transient RuntimeException windowClosingException = null;
long eventMask = AWTEvent.INPUT_METHODS_ENABLED_MASK;
private PropertyChangeSupport changeSupport;
boolean isPacked = false;
transient private Object privateKey = new Object();
private int boundsOp = ComponentPeer.DEFAULT_OPERATION;
[/spoiler]
Habe gerade mal im Flight recorder geschaut, ob es dort eine Möglichkeit gibt, dediziert rauszufinden, wer die Handles verwendet. Aber mehr als Spekulation ist da erstmal nicht drin: Es wird wohl irgendein „peer“ sein - bei meinem Test oben waren es bei 5000 Labels eben 5000 Instanzen von „sun.awt.windows.WLabelPeer“ (das sind dann die „nativen“ Komponenten, und jede davon belegt ein Handle). Es ist nicht ganz klar, wann die Heap dumps erstellt wurden: Kommt die Meldung zu einer so reproduzierbaren Zeit, dass man kurz vorher einen Dump erstellen könnte (wo man dann vielleicht sähe, von welcher Klasse es 9999 Instanzen gibt ) ?
Guten morgen,
also die geposteten Dumps habe ich on-the-flight gemacht, also mittendrin!
reproduzierbarer Zeitpunkt … das ist eine sehr gute Frage (ich weiß, die stellst Du nur :D)
Ich habe wie gesagt den Eindruck, dass zu Anzeige Meldung ca. 12 - 15 Objekte angefordert werden und anschließend beim Beenden ca. 8 - 10 wieder freigegeben werden, also durchschnittlich 5 - 8 verbleiben. Somit ist eine exakte Vorhersage IMHO schwierig bis unmöglich (pauschal: es dauert durchaus einige Stunden …)
Das Erreichen von knapp unter 10000 Instanzen (mal war bei 9997 Schluß, mal schon 9995 - wohl je nach angeforderter Anzahl) könnte man natürlich im Taskmanager verfolgen und dann bei bspw. 9900 Objekten oder so den Dump erstellen.
Allerdings habe ich bislang eben noch kein Objekt mit einer so hohen Anzahl an Instanzen identifizieren können.
Gestern wurde leider ab Mittag durch unsere IT an der Firmen-Infrastruktur gebastelt, so dass ich es nicht weiter testen konnte.
Werde es gleich noch „solange wie möglich“ laufen lassen …
Gruß Klaus
Die 10000 sind eine Windowseinstellung pro Prozess, siehe [1]. Du kannst auch im Taskmanager unter Ansicht/Spalte auswählen, die Spalte Benutzer sowie Handles aktivieren und beobachten wenn du dich in der Applikation herumklickst ob sie sprunghaft ansteigen und nicht wieder abgebaut werden wenn Programmfenster geschlossen werden usw. So bekommt man erstmal ein gefühl aus welcher Richtung das kommen könnte.
[1] https://msdn.microsoft.com/de-de/library/windows/desktop/ms725486(v=vs.85).aspx
Moin,
[QUOTE=ThreadPool]Die 10000 sind eine Windowseinstellung pro Prozess, siehe [1]. Du kannst auch im Taskmanager unter Ansicht/Spalte auswählen, die Spalte Benutzer sowie Handles aktivieren und beobachten wenn du dich in der Applikation herumklickst ob sie sprunghaft ansteigen und nicht wieder abgebaut werden wenn Programmfenster geschlossen werden usw. So bekommt man erstmal ein gefühl aus welcher Richtung das kommen könnte.
[/QUOTE]
Ja, Danke … das ist mir alles schon klar und die entsprechenden Spalten sind auch aktiviert!
Nur ändern diese Werte halt nicht!
Keine Handles, keine GDI-Objekte … nur die BENUTZER-Objekte und der Arbeitsspeicher läuft langsam aber sicher hoch (hier knallt es nur nie, das vorher die BENUTZER-Objekte bei 10000 sind).
Wild rumklicken geht leider auch nicht, da die Oberfläche bei diesem Stresstest quasi gesperrt ist (alle relevanten Button werden disabled).
Im ‚normalen‘ Modus könnte ich zwar rumklicken, aber da läuft dann nix hoch …
Gruß Klaus
Schwierig, schwierig. Kann man die genauen Unterschiede zwischen der Stresstest-Konfiguration und der normalen genauer benennen? (Dass die Zahl bei normalem Klicken nicht/kaum steigt, könnte natürlich daran liegen, dass der Mensch nicht 1000 Klicks pro Minute macht, aber … wer weiß…)
Die JVM nutzt lediglich mehr vom Heap (Xmx), sieht IMHO normal aus.
Interessant waeren IMHO die " surviving Generations", also welche Objekte schon mehrere GCs „ueberlebt“ haben (pro uberlebten GC steigt der Generationscounter um 1), das kann auf Lecks hinweisen, selbst wenn der belegte Speicher gering ist.
Hab leider nur alte Doku dazu gefunden: Detect your Memory Leaks by counting Surviving Generations: Size matters! — munz & more
In den Kommentaren wird beschrieben wie bei der alten Version die „Generations“ Spalte eingeschaltet wird.
Naja man kann schon relativ viel und schnell klicken, vor allem wenn du das z.B. über ein GUI-Testtool abspulst. Jedenfalls eine weitere Alternative ist einfach nahezu alle UI-Elemente abzuschalten, den Stresstest durchzuführen, weitere Elemente einschalten bis man eine ungefähre Vorstellung hat aus welcher Ecke der Fehler kommt. So habe ich das mal bei einem ähnlichen Problem (aber in der SWT-Sphäre) gemacht.
Moinsen,
@maki : Danke für den Link!
@ThreadPool : ja, ich werde das wohl versuchen müssen … leider hat die Darstellung der Meldungen so ihre Tücken
Habe auch noch vor mich mal wirklich Stück für Stück im Debugger da durch zuwühlen, in der Hoffnung, Objekte zu finden, bei denen Speicher geholt und nicht sauber ‚freigegeben‘ wird.
Es gibt da auch einige unschöne Dinge, mit programmglobalen Variaben/Komponenten!
Das Blöde dabei ist, dass es nicht mein Code ist, sondern übernommener …
Gruß Klaus
Moin,
ich habe einige potentiell merkwürdige Stellen gefunden und habe mich jetzt nach längerer Websuche selbst verwirrt :sick:
Es gibt in der Anwendung div. selbstgebastelte Dialogklassen.
Ganz dumm gefragt: ist es (für den gc) ein Unterschied, ob ein entsprechendes Object auf **null **gesetzt oder disposed wird ??
Zudem: IMHO müsste das Object beim Verlassen des Scope doch eh’ automatisch zerstört werden, so dass sowas:
void myMethod( ... )
{
myTyp myObject = new myTyp;
// irgendwas
myObject = null; // überflüssig, oder ??
myObject.dispose(); // alternativ genauso witzlos ??
}
doch eigentlich überflüssig ist, oder nicht?
Danke und Gruß
Klaus
Je nachdem was der dispose macht und wie er implementiert wuerde, kann es sehr wichtig sein diesen aufzurufen (freigabe von resourcen, zB. Handles)
Das setzen auf null dagegen ist fuern *rsch
DAS ist ja schon mal 'ne Aussage … :o)
[QUOTE=maki;144311]Je nachdem was der dispose macht und wie er implementiert wuerde, kann es sehr wichtig sein diesen aufzurufen (freigabe von resourcen, zB. Handles)
[/QUOTE]
naja, die Dialoge sind allesamt von JDialog abgeleitet … somit ist es das dispose der Superklasse …
Was mir eben so nebenbei auffiel: macht sowas irgendeinen tiefen Sinn
setVisible( false ); // ist doch wohl auch "flüssiger als flüssig", oder ??
dispose();
Gruß Klaus
Bei JDialog gibt es gibt die DefaultCloseOperation, die wenn richtig gesetzt dispose() automatisch aufruft, wenn nötig. Frage ist wie diese gesetzt ist?
jdk8/jdk8/jdk: 43cb25339b55 src/share/classes/java/awt/Window.java
if (owner != null) {
Window parent = owner.get();
if (parent != null) {
parent.removeOwnedWindow(weakThis);
}
}
AppContext ac = context.get();
if (null != ac) {
Window.removeFromWindowList(ac, weakThis);
}
}```
Source von Window > Dialog > JDialog
Wird dispose nicht aufgerufen, dann besteht immer noch eine Reference im Parent, womit GC eigentlich nichts aufräumen kann.
Kann auch passieren wenn man dispose überschreibt und nicht in super aufruft
Um nochmal einen Link zum dritten Beitrag herzustellen: https://forum.byte-welt.net/java-forum/awt-swing-javafx-swt/23130-exception-der-eventqueue.html#post143955
Man muss “dispose” aufrufen (*). Auf null setzen muss man nicht. (Und dass er beim dispose() auch automatisch unsichtbar wird, stimmt. Ich meine mich dunkel zu erinnern, dass es da race-conditions geben könnte, aber vielleicht täusche ich mich da. Mit einem “setVisible(false); dispose()” ist man aber auf der sicheren Seite).
Das ganze hat mit dem GC nur indirekt zu tun. Wenn man sowas macht wie
// DON'T DO THIS!!!
public static void main(String args[])
{
createFrame();
}
private void createFrame()
{
JFrame f = new JFrame();
f.setVisible(true);
}
dann würde das “f” ja eigentlich auch aus dem Scope gehen, und eigentlich vom GC aufgeräumt werden - aber ein Fenster ist eben etwas “besonderes”, was das Programm am Leben hält, bis es zugeht - und demnach muss es auch irgendwo gespeichert werden. Selbst wenn das Fenster nicht sichtbar ist: Nach jedem “frame.setVisible(false)” könnte ja noch ein “frame.setVisible(true)” kommen. Nur mit einem “dispose()” sagt man klar und verbindlich: Das ist Müll, das kann weg.
(*) Außer, wenn man die Close-Operation setzt. Die setDefaultCloseOperation wurde ja erwähnt, und ist da auch relevant. Wird die auf irgendwas spezielles gesetzt? Ansonsten ist die HIDE_ON_CLOSE, d.h. das Fenster SCHEINT zuzugehen, es “lebt” aber noch. Es gibt eben auch DISPOSE_ON_CLOSE, wo das dispose() dann automatisch gemacht wird.
@Marco13 gibt es da vielleicht noch einen nicht freigeschalteten Beitrag? Hatte heute einen anderen Rechner, daher als Unregistriert gepostet.