PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Instanzen


grakaman
2003-04-06, 12:39:13
Hallo Leute,

ich bin gerade dabei, das Buch über WindowsForms mit C# von MS Press zu lesen. Da bin ich über ein Code Beispiel gestolpert, was ich nicht recht verstehe. Und zwar soll es einfach das Prinzip des Garbage Collectors veranschaulichen. Hier ein Ausschnitt:

int Counter;
Demo aDemo;
for(Counter = 0; Counter < 1000; Counter++)
{
aDemo = new Demo();
}

Im Buch wird davon gesprochen, dass 1000 Instanzen der Klasse Demo im Arbeitsspeicher existieren. In Zeile 2 erstelle ich ja aber eine Variable, die auf einen Speicherbereich im Heap verweist. In der Schleife wird dann das Objekt instanziert, also initialisiert. Trotzdem bleibt doch die Referenz immer die selbe, der Speicherbereich wird doch also nur jedes mal überschrieben. Also existiert doch im Endefekt nur ein Objekt und somit nur eine Instanz?! Oder wo liegt mein Denkfehler?

MfG

Demirug
2003-04-06, 12:57:43
ja das CLS und der Garbage Collectors. Glücklicherweise konnte ich mir da mal einiges von einem der Entwickler von dem Teil erklären lassen.

Das CLS unterscheidet zwischen Wert und Verweistypen. Verweistypen werden dabei immer auf dem Heap angelegt. Werttypen in abhängkeit von der Verwendung auch auf dem Stack.

Ich nehme mal an das Demo ein Verweistyp ist. Das heist das bei jedem new eine neue Instanz auf dem Heap angelegt wird. Also liegen nach der Schleife 1000 Instanzen auf dem Heap. Gespeichert wird aber nur ein Verweis auf eine Instanz. Bei C++ hätte man nun 999 Speicherleaks. Bei C# wird aber der Garbage Collector diese 999 Instanzen wieder vom Heap entfernen.

grakaman
2003-04-06, 13:05:19
Hallo Demirug,

nach meinen Informationen wird aber der Speicherbereich auf dem Heap nur einmal reserviert, nämlich bei der Deklaration der Variable des Wertetyps. New ruft ja nur den Konstruktor auf und initalisiert das Objekt. Ich weiss, bei C++ reserviert new den Speicherbereich auf dem Heap, das ist aber meines Wissens nach bei C# nicht der Fall, da komplexe Typen wie Klassen dort immer auf dem Heap gespeichert werden, also schon bei deren Deklaration der Speicher auch reserviert wird. Also komme ich nur auf "ein" Objekt und einer Instanz. Klar, die statische Variable wird trotzdem inkrementiet/dekrementiert, das dürfte dann aber nicht der tatsächliche Wert der vorhandenen Instanzen sein.

MfG

Demirug
2003-04-06, 13:29:05
grakaman, dann sind deine Informationen leider falsch.

Der Speicher von Referenztypen wird immer erst beim new allokiert. Versuche mal folgendes:


while (true)
{
aDemo = new Demo();
}


und dann im Taskmamanger zuschauen wie der Speicherverbrauch des Process ansteigt.

Bei Werttypen wird dagegen der Speicher auf dem Stack beim eintritt in die Methode reserviert. Beim Boxen wird dann aber auch für Werttypen Speicher auf dem Heap dafür angelegt.

Bei Arrays wird das Ganze dann noch komplizierter.

grakaman
2003-04-06, 13:38:44
Das würde ja dann aber bedeutend, dass die Variable von Verweistypen erst bei der Instanzierung eine Referenz zugewiesen bekommt und nicht bei der Deklaration, wie es in vielen Büchern zu lesen ist.

MfG

Demirug
2003-04-06, 13:51:45
Ja der Speicher von Referenztypen wird erst bei der Instanzierung angelegt. Die Deklaration führt lediglich dazu das Speicher für die Referenz vorgesehen wird. Was dabei aber wie genau passiert hängt davon ab ob man es nun mit lokalen oder Member variablen zu tun hat.

Anders macht das auch keinen Sinn wenn man bestimmte Verhalten von Referenztypen berücksichtigt.

grakaman
2003-04-06, 13:56:36
In dem selben Buch mit dem obigen Beispiel steht ebenfalls:

"
Eine Typinstanz wird in zwei Schritten erzeugt: Zuerst wird die Variable als der gewünschte Typ deklariert. Dadurch wird zwar, der für die Variable passende Speicherplatz reserviert, aber noch nicht das Objekt erzeugt. Mit folgender Syntax deklarieren Sie ein Objekt.

System.Windows.Forms.Form myForm;

Die obige Zeile weist die Laufzeitumgebung an, Speicher für eine Variable vom Typ Form zu reservieren, und gibt der Variable den Namen myForm, legt aber das eigentliche Form Objekt noch nicht im Speicher an. Dieses wird erst in einem zweiten, als Instanzierung bezeichneten Schritt erzeugt. Im Folgenden sehen Sie ein Beispiel für eine Instanzierung:

myForm = new System.Windows.Forms.Form()

Diese Zeile ruft mit dem Schlüsselwort New (new) die Konstruktormethode des Typs System.Windows.Forms.Form auf.
"
...

Genauso steht auch in dem Buch von John Sharp und einigen anderen, die ich habe. Ich stoße mich halt nur an dem Beispiel, was ich am Anfang genannt habe, weil das imho nicht sein kann.

MfG

Demirug
2003-04-06, 14:11:44
Ah jetzt verstehe ich dein problem. :)

"Die obige Zeile weist die Laufzeitumgebung an, Speicher für eine Variable vom Typ Form zu reservieren"

Eine Variable vom Type Form ist hier eine Referenzvariable. Für diese wird natürlich Speicher angelegt. Dabei handelt es sich aber nur um Speicher für den Verweis nicht für das Object selbst. AFAIR brauchen Variablen von Referenztypen bei einer 32 Bit CPU 32 Bit.

"legt aber das eigentliche Form Objekt noch nicht im Speicher an"

Das bezieht sich nicht nur auf das Füllen des Speichers mit den richtigen Werten durch einen Konstruktur sondern auf den Speicher selbst.

Hast du was grösseres mit C# vor wenn du dir schon so viele Bücher zulegst?

grakaman
2003-04-06, 14:22:06
Hallo Demirug,


Ah jetzt verstehe ich dein problem. :)


gut ;)


"Die obige Zeile weist die Laufzeitumgebung an, Speicher für eine Variable vom Typ Form zu reservieren"

Eine Variable vom Type Form ist hier eine Referenzvariable. Für diese wird natürlich Speicher angelegt. Dabei handelt es sich aber nur um Speicher für den Verweis nicht für das Object selbst. AFAIR brauchen Variablen von Referenztypen bei einer 32 Bit CPU 32 Bit.


Mhhm, aber wenn die Variablen nur Refernzen sind (Sie sind es, dass ist mir schon klar ;)), warum sind sie dann auch vom Typ Form?
Ich habe das so verstanden, dass der Speicher schon auf dem Heap angelegt wird, nur nicht initialisiert. Un weil das Objekt da eben noch keine Standardwerte hat, ist es eben noch kein benutzbares Objekt sondern erst nach der Instanzierung.



"legt aber das eigentliche Form Objekt noch nicht im Speicher an"

Das bezieht sich nicht nur auf das Füllen des Speichers mit den richtigen Werten durch einen Konstruktur sondern auf den Speicher selbst.


JA gut, meinen die mit Objekt jetzt die initialisierten Werte eines vorhandenen Speicherbereiches oder eben den Speicher selbst? Kann man mehrfach deuten. In meiner Sicht hab ichs halt bisher anders gedeutet :D


Hast du was grösseres mit C# vor wenn du dir schon so viele Bücher zulegst?

Ich hätte darüber wohl nicht weiter nachgedacht, wenn ich jetzt nicht das Buch von vorne bis hinten lesen wollte. Die vielen Bücher habe ich vor allem für mein ASP.NET MCP gebraucht, jetzt kommt halt WindowsForms dran ;)

MfG

Xmas
2003-04-06, 14:29:04
Originally posted by grakaman
Eine Typinstanz wird in zwei Schritten erzeugt: Zuerst wird die Variable als der gewünschte Typ deklariert. Dadurch wird zwar, der für die Variable passende Speicherplatz reserviert, aber noch nicht das Objekt erzeugt. Mit folgender Syntax deklarieren Sie ein Objekt.

System.Windows.Forms.Form myForm;

Die obige Zeile weist die Laufzeitumgebung an, Speicher für eine Variable vom Typ Form zu reservieren, und gibt der Variable den Namen myForm, legt aber das eigentliche Form Objekt noch nicht im Speicher an. Dieses wird erst in einem zweiten, als Instanzierung bezeichneten Schritt erzeugt.
Die Formulierung ist IMO falsch oder zumindest sehr missverständlich.

Bei einer solchen Variablendeklaration, also wenn der Typ eine class ist, wird im Speicher (und wenn es eine lokale Variable ist, auf dem Stack) lediglich Platz für eine Referenzvariable reserviert, die als solche ein Pointer auf die Instanz ist. Der Initialwert ist null.

Der Speicher für die eigentliche Instanz wird erst durch new reserviert.
Bei der Zuweisung (=) wird dann der Reference Count der rechten Seite um 1 erhöht und der der linken Seite um 1 erniedrigt. Erreicht er 0, "stirbt" das Objekt, der Destruktor wird aufgerufen und der Speicherplatz als frei markiert. Dann wird dem Pointer die Adresse der Instanz auf der rechten Seite zugewiesen.
Von Zeit zu Zeit sammelt die Garbage Collection die als frei markierten Blöcke ein und versucht sie so gut wie möglich zusammenzufassen.

Demirug
2003-04-06, 14:36:57
Originally posted by grakaman
Mhhm, aber wenn die Variablen nur Refernzen sind (Sie sind es, dass ist mir schon klar ;)), warum sind sie dann auch vom Typ Form?
Ich habe das so verstanden, dass der Speicher schon auf dem Heap angelegt wird, nur nicht initialisiert. Un weil das Objekt da eben noch keine Standardwerte hat, ist es eben noch kein benutzbares Objekt sondern erst nach der Instanzierung.

Es sind eben typisierte Referenzen. Eine "normale" Variable vom Type Form kann es aufgrund der ganzen CLR/CLS Vorgaben ja auch nicht geben.

Das der Speicher noch nicht angelegt wird macht ja durchaus auch Sinn. Stell dir vor das du das gleiche Object mehr als einer Variable zuweist. Dann müsste der GC ja einen Speicherbreich freigeben der nie benutzt wurde. Reine Verschwendung.


JA gut, meinen die mit Objekt jetzt die initialisierten Werte eines vorhandenen Speicherbereiches oder eben den Speicher selbst? Kann man mehrfach deuten. In meiner Sicht hab ichs halt bisher anders gedeutet :D

Beides. Um die Speichersache macht man sich nur meistens keine Gedanken. Das ist normalerweise auch nur relevant wenn man mit unsafe blöcken arbeitet.

Demirug
2003-04-06, 14:41:33
Originally posted by Xmas
Bei der Zuweisung (=) wird dann der Reference Count der rechten Seite um 1 erhöht und der der linken Seite um 1 erniedrigt. Erreicht er 0, "stirbt" das Objekt, der Destruktor wird aufgerufen und der Speicherplatz als frei markiert. Dann wird dem Pointer die Adresse der Instanz auf der rechten Seite zugewiesen.
Von Zeit zu Zeit sammelt die Garbage Collection die als frei markierten Blöcke ein und versucht sie so gut wie möglich zusammenzufassen.

Die Garbage Collection von .net arbeitet ein bischen anders. Da das Referenzcountmodel grosse Probleme mit Zirkularbezügen hat. Bei COM kommt man damit öfter in Berührung.

grakaman
2003-04-06, 14:44:03
Gut, dann bin ich in der Beziehung erleuchtet wurden, danke.
Um das noch mal zusammenzufassen: In dem obigen Bsp. werden also 1000 Instanzen erstellt, aber die Variable referenziert nur auf eine Instanz (Objekt), nämlich der letzten?
Xmas, könntest Du das mit dem Ref Count noch mal genauer beschreiben? Wenn, dann möchte ichs jetzt schon ganz genau wissen :D

MfG

grakaman
2003-04-06, 14:48:08
Originally posted by Demirug


Die Garbage Collection von .net arbeitet ein bischen anders. Da das Referenzcountmodel grosse Probleme mit Zirkularbezügen hat. Bei COM kommt man damit öfter in Berührung.

So im allgemeinen geht die Garbage Collection doch vor, dass sie schaut, ob es auf dem Stack auch Verweise zu den Objekten auf den Heap gibt. Wenn nicht, werden dann die Bereiche auf dem Heap freigegeben. Bei Zirkularbezügen, müssten doch die Bereiche auf dem Stack automatisch gelöscht werden, weil es ja kein dazugehöriges Objekt gibt oder?

MfG

Demirug
2003-04-06, 15:02:15
Originally posted by grakaman


So im allgemeinen geht die Garbage Collection doch vor, dass sie schaut, ob es auf dem Stack auch Verweise zu den Objekten auf den Heap gibt. Wenn nicht, werden dann die Bereiche auf dem Heap freigegeben. Bei Zirkularbezügen, müssten doch die Bereiche auf dem Stack automatisch gelöscht werden, weil es ja kein dazugehöriges Objekt gibt oder?

MfG

Nach diesem Grundprinzip arbeitet die .net Garbage Collection ja auch. Was Xmas aber beschrieben hat ist ein anderes Verfahren des Object Lifetime managment das zum Beispiel in Verbindnung mit COM zum Einsatz kommt. Dabei wird das Entfernen eines Objects nicht von einer übergeordneten Stelle veranlasst sondern das Object entscheidet aufgrund eines eigenen Referenzcounters selbst wenn es Zeit ist sich zu löschen. Nur bei Zirkularbezügen gibt es da gerne mal Probleme.

Xmas
2003-04-06, 15:42:10
Originally posted by Demirug
Nach diesem Grundprinzip arbeitet die .net Garbage Collection ja auch. Was Xmas aber beschrieben hat ist ein anderes Verfahren des Object Lifetime managment das zum Beispiel in Verbindnung mit COM zum Einsatz kommt. Dabei wird das Entfernen eines Objects nicht von einer übergeordneten Stelle veranlasst sondern das Object entscheidet aufgrund eines eigenen Referenzcounters selbst wenn es Zeit ist sich zu löschen. Nur bei Zirkularbezügen gibt es da gerne mal Probleme.
Hm, da hast du wohl recht.

Theoretisch würde es aber doch reichen, wenn das zerstören von Objekten serialisiert wird und erst am Ende dieses serialisierten Prozesses die Speicherbereiche freigegeben werden. D.h. wenn beim Löschen eines Objekts ein weiteres Objekt gelöscht werden muss, wird dies nur zum Löschen vorgemerkt. Oder wäre das noch nicht ausreichend?

Demirug
2003-04-06, 16:03:48
Stell dir folgendes vor.

Object A wird erzeugt und eine Referenz gehalten. (A = 1)
Object A erzeugt ein Object B (A = 1 B = 1)
Object B bekommt Object A übergeben und hält eine Referenz (A = 2 B = 1)
Die ursprüngliche Referenz auf A wird freigeben (A = 1 B = 1)

Im ganzen Programm gibt es jetzt keinen aktiven Verweis auf A oder B mehr. Diese beiden Objekte halten sich aber gegenseitig selbst am leben. Auflösen lässt sich dieses Problem nicht automatisch. Das heist die Objekte erkennen gar nicht das sie gelöscht werden müssten.

.net arbeitet deshalb anders.

Wenn eine GC durchgeführt werden soll müssen zuerst alle Threads des Processes auf pause gesetzt werden.

Nun beginnt der Collector zuerst durch die Liste der globalen (static in C#) Objekteverweise zu laufen. Die Objekte auf die Verwiessen wird bekommen eine Makierung. Zudem wird gebrüft ob dieses Object selbst wieder auf andere Objekte verweist. Falls ja geht das ganze Rekursive weiter.

Nun wird für jeden Thread der Stack nach Objectverweisen durchsucht und das gleiche wiederholt.

Als letztes wird dann noch geprüft ob auf den Register der CPU Objectverweise liegen.

Nachdem nun alle Objekte die noch gebraucht werden makiert sind wird der Heap durchlaufen und alle nicht makierten Objekte entfernt und der Speicher als Frei makiert.

Als letztes werden die nun entstandenen Löcher aufgefüllt in dem man die noch benötigten Objekte entsprechend verschiebt.

Der Heap kann also niemals fragmentieren und allokationen sind sehr schnell weil immer nur direkt von der Spitze des Heaps Speicher reserviert wird was einer einfachen Addition entspricht.

grakaman
2003-04-07, 13:03:39
Warum muss das Leben nur so schwierig sein. Da hatte ich nun anfgenommen, unser Thema wäre hiermit beendet, aber Pustekuchen :D
Da hat mir nun jemand im Microsoft Forum zu C# gepostet, dass mit new kein Speicher allokiert wird. Dieser wird beim Boxing allokiert. Du könntest also einem Objekt einfach die Variable eines Werte Typ zuweisen und der Wert wird dann auf den Heap kopiert. Das Schlüsselwort new ruft tatsächlich nur den Konstruktor auf und ist für die Initialisierung zuständig.

MfG

Demirug
2003-04-07, 13:14:14
Jetzt bis du aber bei Werttype. Diese werden wie ich ja schon geschrieben habe anders behandelt.

grakaman
2003-04-07, 13:24:13
Entschuldigung, Du hast Recht ;)

// Jetzt bin ich erleuchtet wurden :D

stabilo_boss13
2003-04-07, 14:25:51
Originally posted by grakaman


So im allgemeinen geht die Garbage Collection doch vor, dass sie schaut, ob es auf dem Stack auch Verweise zu den Objekten auf den Heap gibt. Wenn nicht, werden dann die Bereiche auf dem Heap freigegeben. Bei Zirkularbezügen, müssten doch die Bereiche auf dem Stack automatisch gelöscht werden, weil es ja kein dazugehöriges Objekt gibt oder?

MfG Mir hat es mal einer von MS so erklärt:

Zunächst geht der GC von .net davon aus, dass alle Objekte auf dem Managed Heap gelöscht werden können und kennzeichnet diese entsprechend. Dann durchsucht er alle Speicherorte, an denen Referenzen zu den Objekten liegen könnten: Globale und statische Objektverweise, lokale Objektverweise und -parameter auf dem Stack des Threads, CPU-Register die Zeiger auf Objekte im Managed Heap enthalten usw.
Wenn der GC also z.B. eine globale Variable auf dem Stack findet, die sich auf ein Objekt im Managed Heap bezieht, wird dieses in einen Baum aufgenommen und als nicht verwerfbar gekennzeichnet. So hangelt sich der GC Stück für Stück durch die einzelnen Speicherorte.
Nachdem alle Verweise gefunden und vermerkt wurden, sind auf dem Managed Heap nur noch die Objekte als löschbar gekennzeichnet, die keinerlei Bezug mehr haben. Dann geht der GC davon aus, dass diese entfernt werden können und löscht sie vom Heap. So dürften Zirkelbezüge eigentlich keine Probleme bereiten.