[linux-l] Re: Ruby: sehr cool, aber laaaahm... wie geht's schneller?! - D?

Steffen Dettmer steffen at dett.de
So Aug 27 15:58:53 CEST 2006


* Oliver Bandel wrote on Sun, Aug 27, 2006 at 14:40 +0200:
> > >> object = nil;
> > >> System.runFinalization (); // needed?
> > >> System.gc ();
> > 
> > Sowas macht schon Sinn wenn man den Verdacht hat, dass man
> > irgendwo noch Referenzen herumfliegen hat, die man
> > eigentlich nicht haben will.  

Ich hab sowas alles probiert und nichts hat funktioniert. In "hello
world" funktioniert es, klar, aber in komplexen Systemen weniger.
Erstmal natürlich das Problem mit "komisch zyklischen Referenzen", die
sind systembedingt ja nicht erkennbar (selbst mark-and-sweep kann nicht
wissen, ob ein vergessener Listeneintrag vergessen wurde und gelöscht
werden müsste). Das Problem ist meiner Meinung nach, dass man dank GC
solche Probleme nicht mehr lokalisieren kann. Hat man eine
multi-thread-Umgebung mit vielen Funktionen, schmiert halt /irgendein/
Thread mit OutOfMemory ab - nicht der Schuldige. Der Schulige an sich
kann aber schon hunderttausende Zeilen Code haben!

Bei der Analyse stellt man dann fest, dann nicht-triviale Klassen vor
allem Strings und sowas haben, mit Hotspot-Debugger stellt man fest,
dass die nicht-freien Strings mehr werden - aber nicht warum. Selbst
wenn man eine Referenz explizit auf null setzt, kann ein anderer noch
eine Referenz haben und benutzen (oder auch nicht), ohne das man das
merkt, insbesondere, wenn eine Javalib das macht (ganz oben auf meiner
Verdachtsliste steht CORBA).

Nun kann man ein Object "totmachen", in dem man delete selbst
implementiert. Man macht ein "public destroy", dass einen bool setzt,
der in jeder Methode geprüft wird und ggf. abbricht. Leider erkennt man
dann leaks auch wieder nur, wenn das Objekt noch benutzt wird, aber
nicht, wenn es "nur noch existiert". Wenn man versucht, OutOfMemory zu
fangen, ist der freie Speicher natürlich ausreichend gross, hilft auch
nicht. Statistiken zeigen Millionen von temporären StringBuffern,
normal.

Meiner Meinung nach kann man Speicherlöchern in Java im Prinzip nicht
debuggen. Man ist dann einfach am Ende. Vielleicht hat man Glück, und
löst das Problem nach Mannmonaten (!) durch "schlaues Hinsehen". Für
mich ist das ein Risiko in Java-Anwendungen. In C++ kann ich notfalls
ein eigenes New implementieren, objecte "kaputt-memsetten", mit dem gdb
alles sehen, und da ich ja delete aufrufen muss, hab ich eine Chance,
vergessene zu lokalisieren (weil mein new z.B. merken kann, wo ein
Object allokiert wurde). Oder was auch immer.

> > Aber viel mehr als feststellen, dass man ein mem leak hat kann man
> > wohl nicht.

Nicht mal das! Es ist normal, dass der Speicher voller wird. Ist er
voll, läuft die GC an. Das sieht im hotspot aus wie ein Sägezahn, auch
im Gutfall. Der Speicher wird /immer/ voll. Eigentlich dürfte es
OutOfMemory gar nicht geben (solange nach dem GC genug frei wäre). Bei
OutOfMemory weiss man auch nicht, ob es Speicherlöcher sind, oder jemand
versucht, einen 4 GB grossen String zu allokieren oder intern was null
war oder was auch immer. Und wenn man mit JNI und CORBA kämpfen muss, wo
die GC nicht "richtig" laufen kann, hat man wohl ganz verlorern. Ohne
JNI und CORBA kommt man u.U. aber nicht weiter.

BTW, multi-threading Probleme (angefangen mit deadlocks) lassen sich
meiner Meinung nach weder in C++ noch in Java sinnvoll debuggen.

> Unter memLeak verstehe ich aber das, was man in C auch so nennt.
> 
> Bei einer Sprache mit GC dürfte doch sows garnicht auftauchen,
> denn da werden die Ressourcen ja vom System verwaltet.

Na ja, in der Theorie. In der Praxis funktioniert das halt nicht
wirklich, sonst gäbe es ja gar kein OutOfMemory...

> Also kann es da allenfalls Speicherhungrige Coideteile geben;
> das sind dann aber keine leaks im klassischen C-Sinne (malloc()).

Man kann es meiner Meinung nach nichtmal unterscheiden. In C kann ich
unterscheiden, ob ich ein Loch habe oder ob ich ein zu grosses Objekt
(Struktur, egal) allokieren möchte. Ich kann malloc umlinken (efence
oder so), mit loggen, was auch immer. Bei Bedarf nehme ich ein
debug_malloc (mpatrol) oder ich logge die Aufrufe. Ich kann ein malloc
implementieren, was auf statischem Speicher arbeitet. Ich kann sogar je
Modul ein anderes malloc verwenden. Damit kann ich im Notfall viele
Tricks rauskramen, um leaks zu finden. Bei Java konnte mir keiner
wirklich helfen (und ich hab wirklich viele gefragt).

> Oder hat Java da doch die typischen C-Schwächen beibehalten,
> das GC-Konzept also nicht richtig verinnerlicht?!

Meiner Meinung nach versucht Java Dinge zu vereinfachen, die nicht
gehen. Es wirkt (gegenüber dem "sauberen" C++) wie ein Workaround. GC
z.B. geht halt nicht immer, rein logisch nicht. Was logisch nicht immer
geht, kann man natürlich auch nicht so implementieren, dass es immer
geht. Zum Kompott erkauft man sich dafür deftige Nachteile. Designer
denken nicht mehr über Lebenszyklen nach, RAII geht nicht und wird nicht
benutzt, an Stelle von symetrischen und eleganten definierten
Konstruktionen entstehen "Factories" und unklare Ownership-Relationen
usw. Am Ende kann man das nichtmal richtig mit UML oder sonstwas
modellieren, weil man ja gar nicht weiss, wer irgendwas freigibt.
Weiterhin hat man troz Referenzen noch ne Menge temp-Objekte (Java ist
ja nicht umsonst so langsam). Und am Ende wird einem das noch als
Feature verkauft. Klar, Disziplin muss vom Programmierer kommen, nicht
(nur) von der Sprache, aber der Programmierer muss diszipliniert und
sauber arbeiten /können/. Zwangs-GC erfüllt meiner Meinung nach dieses
Feature nicht, vor allem, weil es fälschlicherweise den logischen und
"implementierungsdetailspeicher-" Lebenszyklus eines Objects verbindet.
Dabei sind das zwei Sachen. 

In C++ werden die Zyklen "ein bisschen" verbunden, aber das delete oder
free muss den Speicher ja gar nicht sofort freigeben. In der Praxis
macht die libc das auch gar nicht (das System kriegt den Speicher
vielleicht irgendwann mal zurück, wenn paar 64 KB pages frei sind oder
so, aber nie sofort). Das find ich besser: ich als Entwickler bin der
absolute Boss über den logischen Lebenszyklus. Das System kann in seinen
Implementierungsdetails (Speicherverwaltung) Optimierungen machen (wie
malloc und free und damit new/delete das auch tun), davon merke ich
nichts. Und ich meine nichts: ich habe noch nie ein Problem gehabt, weil
malloc falsch Systemspeicher anfordert. Und warum? Meiner Meinung nach,
weil das malloc/free oder new/delete Konzept einfach ist: KISS. Man kann
eine malloc-Implementierung gut und tief testen. Bei GC geht das nicht,
weil zutiefst von der "Benutzung" abhängt. Ich kann bei einem GC
Programm nicht "beweisen", dass der Speicher (oder auch nur die
Objekt-Lebenszyklen) korrekt verwendet werden. Meiner Meinung nach ist
es ein Bug, weil es schon logisch nicht möglich ist.

oki,

Steffen

-- 
Dieses Schreiben wurde maschinell erstellt,
es trägt daher weder Unterschrift noch Siegel.




Mehr Informationen über die Mailingliste linux-l