Vraag Hoe worden de GC-verwijzingen bijgewerkt nadat verdichting heeft plaatsgevonden


De .NET Garbage Collector verzamelt objecten (reclaimt hun geheugen) en voert ook geheugenverdichting uit (om geheugenfragmentatie tot een minimum te beperken).

Ik vraag me af, aangezien een toepassing mogelijk veel verwijzingen naar objecten bevat, hoe de GC (of de CLR) deze verwijzingen naar objecten beheert, wanneer het adres van het object verandert als gevolg van compactie door de GC.


11
2018-05-19 22:03


oorsprong


antwoorden:


Het concept is eenvoudig genoeg, de vuilnisman werkt eenvoudigweg eventuele objectreferenties bij en verwijst deze opnieuw naar het verplaatste object.

Implementatie is een beetje lastiger, er is geen echt verschil tussen native en beheerde code, het zijn beide machinecode. En er is niets speciaals aan een objectreferentie, het is slechts een aanwijzer tijdens runtime. Wat nodig is, is een betrouwbare manier voor de verzamelaar om deze aanwijzingen terug te vinden en ze te herkennen als de soort die verwijst naar een beheerd object. Niet alleen om ze bij te werken wanneer het gepunte object tijdens het comprimeren wordt verplaatst, maar ook om live-referenties te herkennen die ervoor zorgen dat een object niet te snel wordt verzameld.

Dat is eenvoudig voor alle objectreferenties die zijn opgeslagen in klasseobjecten die zijn opgeslagen op de GC-heap, de CLR kent de lay-out van het object en in welke velden een aanwijzer is opgeslagen. Het is niet zo eenvoudig voor objectreferenties die zijn opgeslagen op de stapel of in een cpu-register. Zoals lokale variabelen en methodeargumenten.

De sleuteleigenschap van het uitvoeren van beheerde code die deze onderscheidt van de native code, is dat de CLR op betrouwbare wijze de stackframes van beheerde code kan herhalen. Gedaan door het soort code te beperken dat wordt gebruikt voor het instellen van een stapelframe. Dit is meestal niet mogelijk in native code, de optimalisatieoptie "frame pointer omissie" is bijzonder smerig.

Stapelframe-lopen zorgt er in de eerste plaats voor dat objectreferenties worden gevonden die op de stapel zijn opgeslagen. En laat het weten dat de thread momenteel beheerde code uitvoert, zodat de cpu-registers ook op referenties moeten worden gecontroleerd. Een overgang van beheerde code naar native code houdt in dat een speciaal 'cookie' wordt geschreven op de stapel die de verzamelaar herkent. Het weet dus dat eventuele volgende stapelframes niet moeten worden gecontroleerd omdat ze willekeurige aanwijzerwaarden bevatten die nooit naar een beheerd object verwijzen.

U kunt dit terugzien in de foutopsporing wanneer u ongecodeerde code debugging inschakelt. Bekijk het venster Call Stack en noteer de annotaties [Native to Managed Transition] en [Managed to Native Transition]. Dat is de debugger die deze cookies herkent. Ook belangrijk omdat het moet weten of het venster Locals iets zinvols kan weergeven. De stapeling van de stapel is ook zichtbaar in het kader, let op de klassen StackTrace en StackFrame. En het is erg belangrijk voor sandboxing, terwijl Code Access Security (CAS) stapsgewijze wandelingen uitvoert.


9
2018-05-20 14:49



Voor de eenvoud ga ik uit van een GC waar de wereld niet wordt vastgezet, elk object wordt gescand en verplaatst naar elke GC-cyclus en geen enkele bestemming overlapt een van de bronnen. In werkelijkheid is de .NET GC een beetje ingewikkelder, maar dit zou een goed gevoel moeten geven voor hoe de dingen werken.

Telkens wanneer een referentie wordt onderzocht, zijn er drie mogelijkheden:

  1. Het is nul. In dat geval is geen actie vereist.

  2. Het identificeert een object waarvan de header zegt dat het iets anders is dan een verplaatsingsmarkering (een speciaal soort object dat hieronder wordt beschreven). Verplaats in dat geval het object naar een nieuwe locatie en vervang het oorspronkelijke object door een verplaatsingsmarkering van drie woorden die de nieuwe locatie bevat, de oud locatie van het object dat de zojuist waargenomen bevat referentie naar het huidige object en de offset binnen dat object. Start vervolgens het scannen van het nieuwe object (het systeem kan het object dat werd gescand op dit moment vergeten, omdat het net zijn adres heeft vastgelegd).

  3. Het identificeert een object waarvan de header aangeeft dat het een verplaatsingsmarkering is. Werk in dat geval de referentie bij die wordt gescand om het nieuwe adres weer te geven.

Als het systeem klaar is met het scannen van het huidige object, kan het naar de oude locatie kijken om erachter te komen wat het aan het doen was voordat het het huidige object begon te scannen.

Als een object eenmaal is verplaatst, is de eerste inhoud van de eerste drie woorden beschikbaar op de nieuwe locatie en is deze niet langer nodig op de oude. Omdat de verschuiving in een object altijd een veelvoud van vier is en individuele objecten beperkt zijn tot 2 GB elk, is slechts een fractie van alle mogelijke 32-bits waarden nodig om alle mogelijke offsets te behouden. Op voorwaarde dat ten minste één woord in de header van een object ten minste 2 ^ 29-waarden heeft, kan deze nooit voor iets anders dan een marker voor objectreportering worden bewaard en mits elk object ten minste twaalf bytes is toegewezen, is het mogelijk om objecten te scannen diepte van de boom zonder diepte-afhankelijke opslag buiten de ruimte die wordt ingenomen door oude kopieën van objecten waarvan de inhoud niet langer nodig is.


4
2018-03-16 18:58



Garbage collection

Elke applicatie heeft een aantal wortels. Roots identificeren opslaglocaties, die verwijzen naar objecten op de beheerde heap of naar objecten die zijn ingesteld op null. Alle globale en statische objectaanwijzers in een toepassing worden bijvoorbeeld beschouwd als onderdeel van de hoofdmap van de toepassing. Bovendien worden alle lokale variabele / parameterobjectwijzers op de stapel van een thread beschouwd als onderdeel van de hoofdmap van de toepassing. Ten slotte worden CPU-registers met verwijzingen naar objecten in de beheerde heap ook beschouwd als onderdeel van de hoofdmap van de toepassing. De lijst met actieve roots wordt onderhouden door de just-in-time (JIT) -compiler en de gemeenschappelijke taalruntime en wordt toegankelijk gemaakt voor het algoritme van de garbage collector.

Wanneer de garbage collector begint te lopen, wordt ervan uitgegaan dat alle objecten op de hoop afval zijn. Met andere woorden, het gaat ervan uit dat geen van de wortels van de toepassing verwijzen naar objecten in de heap. Nu begint de garbagecollector met het lopen van de wortels en het bouwen van een grafiek van alle objecten die vanaf de wortels te bereiken zijn. De garbage collector kan bijvoorbeeld een globale variabele vinden die naar een object in de heap wijst.

Zodra dit deel van de grafiek is voltooid, controleert de garbagecollector de volgende root en loopt hij de objecten opnieuw af. Terwijl de vuilnisman van object naar object loopt, probeert hij een object toe te voegen aan de grafiek die hij eerder heeft toegevoegd, dan kan de vuilnisman stoppen om dat pad af te lopen. Dit dient twee doelen. Ten eerste helpt het de prestaties aanzienlijk, omdat het niet meerdere keren door een reeks objecten loopt. Ten tweede voorkomt het oneindige lussen als er circulair gekoppelde lijsten met objecten zijn.

Nadat alle wortels zijn gecontroleerd, bevat de grafiek van de garbage collector de verzameling van alle objecten die op de een of andere manier bereikbaar zijn vanuit de root van de applicatie; objecten die niet in de grafiek staan, zijn niet toegankelijk voor de toepassing en worden daarom als rommel beschouwd. De garbagecollector loopt nu lineair door de heap, op zoek naar aangrenzende blokken met afvalobjecten (nu beschouwd als vrije ruimte). De garbage collector verschuift vervolgens de niet-vervuilde objecten naar het geheugen (met behulp van de standaard memcpy-functie die je al jaren kent) en verwijdert alle openingen in de heap. Als u de objecten in het geheugen verplaatst, worden alle verwijzingen naar de objecten natuurlijk ongeldig. Dus moet de garbagecollector de wortels van de applicatie wijzigen, zodat de wijzers wijzen naar de nieuwe locaties van de objecten. Als een object bovendien een pointer naar een ander object bevat, is de garbage collector ook verantwoordelijk voor het corrigeren van deze aanwijzers.

C # vaste verklaring

De vaste instructie stelt een aanwijzer in voor een beheerde variabele en "pikt" die variabele tijdens de uitvoering van de instructie. Zonder vaste, zou een verwijzing naar verplaatsbare beheerde variabelen van weinig nut zijn, omdat garbage collection de variabelen onvoorspelbaar zou kunnen verplaatsen. Met de C # -compiler kunt u alleen een aanwijzer toewijzen aan een beheerde variabele in een vaste instructie.

Garbage Collection: automatisch geheugenbeheer in Microsoft .NET Framework

vaste verklaring (C # referentie)


3
2018-05-19 22:24