ich habe grade einen Blog Eintrag zum Thema Memory Leak bei (nicht)
Verwendung von Threads verfasst. Die Auflösung kommt nächste Woche, wer wil
mitraten? :)
http://itblog.eckenfels.net/archives/51-Achtung-Java.html
Die "Achtung Java" Serie soll als persönlches Notizheft dienen, für all die
versteckten API Unebenheiten die es so gibt.
Gruss
Bernd
> ich habe grade einen Blog Eintrag zum Thema Memory Leak bei (nicht)
> Verwendung von Threads verfasst. Die Auflösung kommt nächste Woche,
> wer wil mitraten? :)
>
> http://itblog.eckenfels.net/archives/51-Achtung-Java.html
Hm, du erzeugst einen Thread, der nie terminiert wird?
--
Alle gegen Alle.
http://www.fightclub-berlin.de
http://www.ozzfest.com/timages/page/media/WallofDeath.mov
http://www.tombofthemutilated.net/Cannibal-Corpse-Ace-Ventura-Video.html
Er wird auch nicht gestartet, aber ja das kommt der Lösung schon sehr nahe.
Gruss
Bernd
> ich habe grade einen Blog Eintrag zum Thema Memory Leak bei (nicht)
> Verwendung von Threads verfasst. Die Auflösung kommt nächste Woche, wer wil
> mitraten? :)
Keine Ahnung, aber ein anderes (IMHO besseres) Design hätte
wahrscheinlich dein Problem vermieden. Statt Thread-Objekte als Chunks
zu missbrauchen, wäre es IMHO besser gewesen, sowas zu machen:
public void start(boolean async, Runnable r) {
if (async) {
new Thread(r).start();
} else {
r.run();
}
}
--
Stefan Matthias Aust // Lassen Sie uns durch, wir sind Arzt!
Der Thread wird nicht aus der ThreadGroup ausgetragen,
da das in einem JVM spezifischen Hook beim Threadende
passiert ? Da die JVM ihn nicht startet, terminiert
sie ihn auch nicht.
Gruss Gitta
> Der Thread wird nicht aus der ThreadGroup ausgetragen,
> da das in einem JVM spezifischen Hook beim Threadende
> passiert ? Da die JVM ihn nicht startet, terminiert
> sie ihn auch nicht.
Sag ich doch :)
> new Thread(r).start();
Nun wissen wir, warum "Miau extends Thread" als böse[TM] gebrandmarkt ist :)
> Der Thread wird nicht aus der ThreadGroup ausgetragen,
> da das in einem JVM spezifischen Hook beim Threadende
> passiert ? Da die JVM ihn nicht startet, terminiert
> sie ihn auch nicht.
Das hätte ich auch gedacht, aber ich habe eben nachgeschaut und nicht
gesehen, dass der Thread hinzugefügt wird. Es wird nur
ThreadGroup#addUnstarted() aufgerufen, was eben nicht den Thread
hinzufügt, sondern nur einen Zähler erhöht.
Ich sehe auf den ersten Blick auch kein Problem. Der Thread()
Konstruktor ruft init(ThreadGroup, Runnable, name, stacksize) auf. Dort
wird der Vater-Thread und der Security-Manager bestimmt, was IMHO
unkritisch ist. Dann wird ThreadGroup#checkAccess aufgerufen, was
nochmal den SecurityManager bestimmt, aber auch unkritisch ist. Dann
ThreadGroup#addUnstarted(), dann werden ein paar Exemplarvariablen
initialisiert, die alle harmlos sind. Einzig name = name.toCharArray()
erzeugt ein neues Objekt, aber so lang wird der Name schon nicht sein.
Es wird ein neuer AccessControlContext angelegt - könnte das ein Problem
sein? Dann wird the Thread-Priorität gesetzt, was auch harmlos
erscheint. Dann werden vererbbare Thread-lokale Variablen kopiert
(wahrscheinlich keine) und schließlich wird eine ID vergeben.
Ich sehe (bei JDK 1.6, in dessen Source ich geschaut habe) kein Problem :(
> Ich sehe (bei JDK 1.6, in dessen Source ich geschaut habe) kein
> Problem :(
Also ich sehe da ein ThreadGroup#add.
1.5 oder 1.4, keine Ahnung, zu faul zum Nachsehen ;)
Ja, das ist tatsaechlich in 1.5ff gefixed. (Was mit leider nichts nützt :)
In 1.4 wird der Thread schon im Constructor mittels ThreadGroup#add(Thread)
in das "Thread threads[]" Attribut der Thread Group geadded.
Gruss
Bernd
Natürlich, Runable ist die Lösung. Ich hätte das auch nicht geschrieben.
Schon alleine deswegen weil Runables deutlich flexibler auf workload manager
gemapped werden können.
Wir haben noch ein 2. Projekt inspiziert, und eine weitere Stelle mit dem
Problem gefunden. Von daher gehe ich davon aus das Problem haben noch andere
Anwendungen.
Gruss
Bernd
> In 1.4 wird der Thread schon im Constructor mittels
> ThreadGroup#add(Thread) in das "Thread threads[]" Attribut der Thread
> Group geadded.
Wie jetzt, die Woche schon um? ;)
Na, im Blog warte ich länger. Besonders frustrierend an der Aktion war, dass
der Memory Profiler mir genau
ThreadGroup
threads
Processor 400MB
irgendwas
anderes
angezeigt hat, und ich trotzdem ewig rumgsucht habe weil ich mit dessen
Ausgabe nicht vertraut war. Dabei isses ja offensichtlich :)
BTW: kennt jemand nen Artikel der die "roots" des Java Heaps genau
idendifiziert? Neben der Hauptklass (an der dann der Classloader mit den
ganzen Klassen und statics haengt) sind das auch die Threads und die Stacks.
Sonst noch was?
Gruss
Bernd
> Stefan Matthias Aust wrote:
>
>> new Thread(r).start();
>
> Nun wissen wir, warum "Miau extends Thread" als böse[TM] gebrandmarkt ist
> :)
Ich wollt's gerade schreiben. Es hat vor allem auch was mit selbständiger
Testbarkeit und Abhängigkeitsmanagement zu tun - mir ist immer wieder
rätselhaft, warum Leute von Thread erben und sich hinterher wundern, warum
ihre Klassenhierarchie so komisch aussieht und warum das alles immer
untestbarer wird. Multithreadingfähigmachbarkeit :) ist eine zentrale
Entwurfsqualität.
-h
Bernd Eckenfels wrote:
> BTW: kennt jemand nen Artikel der die "roots" des Java Heaps genau
> idendifiziert? Neben der Hauptklass (an der dann der Classloader mit den
> ganzen Klassen und statics haengt) sind das auch die Threads und die Stacks.
> Sonst noch was?
Mit "an der dann der Classloader mit den ganzen Klassen und statics
haengt" meinst Du alle Klassen, die irgendwelche static members haben?
Wieso zählst Du dann die Threads extra? Die müssten doch auch irgendwie
über ein statisches ThreadGroup-Feld oder so referenziert sein, oder?
Und welche Stacks? Die Methoden-Aufruf-Stacks? Seit wann liegen die auf
dem "Java Heap"?
Ciao,
Ingo
Kann sein, wo hängt dann die System Threadgroup dran? Waren ja nur
Vermutungen, deswegen hab ich ja nach Details gefragt.
> Und welche Stacks? Die Methoden-Aufruf-Stacks? Seit wann liegen die auf
> dem "Java Heap"?
Jedes Object auf dem Heap das in einer lokalen Variable eines Stacks (also
einer Methode die Aktiv ist) referenziert wird, wird vom GC ja als benutzt
markiert. Ich weiss nicht ob es einen Pfad (via Objectreferenzen) von der
Haupt-Instanz zum Haupt-Thread Stack und von dort zu anderen Threads gibt,
oder ob das für den GC ein weitere Startknoten für die Mark Phase sein muss.
Gruss
Bernd
Bernd Eckenfels wrote:
>>Wieso zählst Du dann die Threads extra? Die müssten doch auch irgendwie
>>über ein statisches ThreadGroup-Feld oder so referenziert sein, oder?
>
> Kann sein, wo hängt dann die System Threadgroup dran? Waren ja nur
> Vermutungen, deswegen hab ich ja nach Details gefragt.
Ein Blick in die Sourcen (oder zwei) hilft da vielleicht. Soweit ich
sehen kann, hat ThreadGroup überhaupt keine statischen Felder. Aber an
die ThreadGroup kommst Du ja über den Thread, und der hat wiederum eine
statische Methode getThreads(). Die ist native, und ich vermute, da
gehts stark in die Interna... (*)
>>Und welche Stacks? Die Methoden-Aufruf-Stacks? Seit wann liegen die auf
>>dem "Java Heap"?
>
> Jedes Object auf dem Heap das in einer lokalen Variable eines Stacks (also
> einer Methode die Aktiv ist) referenziert wird, wird vom GC ja als benutzt
> markiert. Ich weiss nicht ob es einen Pfad (via Objectreferenzen) von der
> Haupt-Instanz zum Haupt-Thread Stack und von dort zu anderen Threads gibt,
> oder ob das für den GC ein weitere Startknoten für die Mark Phase sein muss.
An den Stack kommst Du AFAIK gar nicht ran, über Reflection nicht, und
über die Thread-Klasse schon gar nicht.
(*) Mit JNI habe ich mich noch nicht intensiv beschäftigt, inwieweit da
statische Felder/Konstanten/globale Variablen angelegt werden können,
kann ich also nicht beurteilen. Selbst wenn das möglich ist - der GC ist
doch an der Stelle sowieso völlig ausgehebelt! Die native Methode
Thread.getThreads() wird also vermutlich nicht viel mehr (oder weniger)
tun, als auf irgendwelche Interna der JVM zuzugreifen, auf die Du von
Java aus sowieso nicht zugreifen kannst.
Kurz: An die Startpunkte des GCs kommst Du vermutlich so oder so nicht.
Du kannst Dich nur von der "Main-Klasse" über diverse Referenzen
durchhangeln und erreichst damit eine gewisse (sicher nicht 100%ige)
Abdeckung...
Ciao,
Ingo
Starter.start(true) startet einen Thread, während Starter.start(false)
das schlicht weg nicht tut, das ist dort einfach nur ein simpler
Methodenaufruf.
(Sorry bin gerade zu faul die komplette Diskussion durchzulesen, die
ersten antworten enthielten das zumindestens nicht. ;) )
Christoph
Naja, ich will nicht rankommen, es hat mich interessiert welche es sind. Ich
such ein Paper zu dem Thema, interessiert mich ob es noch weitere solche
Stellen gibt an denen sich Referenzen verklemmen können.
Gruss
Bernd
Eben, und dieser simple methodenaufruf erzeugt ein Memory Leak.
Gruss
Bernd
Und was lehrt uns das? Du nutzt ein veraltetes JDK. Die aktuelle
Version hätte dir das suchen (und mir das wundern, warum ich das nicht
nachvollziehen kann) erspart. Und jetzt komme mir nicht damit, das du
ein OS hast, wo es kein JDK 1.5 für gibt oder das du irgend so einen
komischen Application Server hast, der ebenfalls noch in der
Vergangenheit lebt :)
Nur bei veralteten Java-Versionen :)
So eine richtige Beschreibung, was alles in Java
zur Rootset gehoert, habe ich auch nicht gefunden.
Hier ist es implizit beschrieben:
https://hat.dev.java.net/doc/README.html
(The Roots Query)
Gruss Gitta
Nein ich nutze das JDK was meine Kunden nutzen.
> Und jetzt komme mir nicht damit, das du
> ein OS hast, wo es kein JDK 1.5 für gibt oder das du irgend so einen
> komischen Application Server hast, der ebenfalls noch in der
> Vergangenheit lebt :)
Warum nicht, wenn es doch so ist?
Gruss
Bernd