CodeGym/Java Course/Module 3/Geheugen in de JVM, deel 2

Geheugen in de JVM, deel 2

Beschikbaar

Geheugenhardware-architectuur

De architectuur van moderne geheugenhardware verschilt van het interne geheugenmodel van Java. Daarom moet u de hardware-architectuur begrijpen om te weten hoe het Java-model ermee werkt. In dit gedeelte wordt de algemene architectuur van de geheugenhardware beschreven en in het volgende gedeelte wordt beschreven hoe Java ermee werkt.

Hier is een vereenvoudigd diagram van de hardware-architectuur van een moderne computer:

Geheugenhardware-architectuur

In de moderne wereld heeft een computer 2 of meer processors en dit is al de norm. Sommige van deze processors kunnen ook meerdere kernen hebben. Op dergelijke computers is het mogelijk om meerdere threads tegelijkertijd te laten draaien. Elke processorkern kan op elk moment één thread uitvoeren. Dit betekent dat elke Java-toepassing a priori multi-threaded is en dat binnen uw programma één thread per processorkern tegelijk kan worden uitgevoerd.

De processorkern bevat een reeks registers die zich in het geheugen bevinden (in de kern). Het voert bewerkingen op registergegevens veel sneller uit dan op gegevens die zich in het hoofdgeheugen (RAM) van de computer bevinden. Dit komt omdat de processor deze registers veel sneller kan benaderen.

Elke CPU kan ook zijn eigen cachelaag hebben. De meeste moderne processors hebben het. De processor heeft veel sneller toegang tot de cache dan het hoofdgeheugen, maar niet zo snel als de interne registers. De waarde van de cachetoegangssnelheid ligt ongeveer tussen de toegangssnelheden van het hoofdgeheugen en interne registers.

Bovendien hebben processors een plaats om een ​​cache op meerdere niveaus te hebben. Maar dit is niet zo belangrijk om te weten om te begrijpen hoe het Java-geheugenmodel interageert met hardwaregeheugen. Het is belangrijk om te weten dat processors een bepaald cacheniveau kunnen hebben.

Elke computer bevat op dezelfde manier ook RAM (hoofdgeheugengebied). Alle kernen hebben toegang tot het hoofdgeheugen. Het hoofdgeheugengebied is meestal veel groter dan het cachegeheugen van de processorkernen.

Op het moment dat de processor toegang nodig heeft tot het hoofdgeheugen, leest hij een deel daarvan in zijn cachegeheugen. Het kan ook enkele gegevens uit de cache in zijn interne registers lezen en er vervolgens bewerkingen op uitvoeren. Wanneer de CPU het resultaat terug naar het hoofdgeheugen moet schrijven, spoelt het de gegevens van het interne register naar de cache en op een gegeven moment naar het hoofdgeheugen.

Gegevens die in de cache zijn opgeslagen, worden normaal gesproken teruggespoeld naar het hoofdgeheugen wanneer de processor iets anders in de cache moet opslaan. De cache heeft de mogelijkheid om het geheugen te wissen en tegelijkertijd gegevens te schrijven. De processor hoeft tijdens een update niet elke keer de volledige cache te lezen of te schrijven. Gewoonlijk wordt de cache bijgewerkt in kleine geheugenblokken, deze worden "cachelijn" genoemd. Een of meer "cacheregels" kunnen in het cachegeheugen worden gelezen en een of meer cacheregels kunnen worden teruggespoeld naar het hoofdgeheugen.

Combinatie van Java-geheugenmodel en geheugenhardware-architectuur

Zoals eerder vermeld, zijn het Java-geheugenmodel en de architectuur van de geheugenhardware verschillend. De hardware-architectuur maakt geen onderscheid tussen threadstacks en heaps. Op hardware bevinden de threadstack en HEAP (heap) zich in het hoofdgeheugen.

Delen van stapels en threadheaps kunnen soms aanwezig zijn in caches en interne registers van de CPU. Dit is weergegeven in het schema:

draadstapel en HEAP

Wanneer objecten en variabelen in verschillende delen van het computergeheugen kunnen worden opgeslagen, kunnen bepaalde problemen ontstaan. Dit zijn de twee belangrijkste:

  • Zichtbaarheid van de wijzigingen die de thread heeft aangebracht in gedeelde variabelen.
  • Raceconditie bij het lezen, controleren en schrijven van gedeelde variabelen.

Beide kwesties zullen hieronder worden toegelicht.

Zichtbaarheid van gedeelde objecten

Als twee of meer threads een object delen zonder correct gebruik van vluchtige declaratie of synchronisatie, dan zijn wijzigingen in het gedeelde object die door één thread zijn aangebracht, mogelijk niet zichtbaar voor andere threads.

Stel je voor dat een gedeeld object in eerste instantie wordt opgeslagen in het hoofdgeheugen. Een thread die op een CPU draait, leest het gedeelde object in de cache van dezelfde CPU. Daar brengt hij wijzigingen aan het object aan. Totdat de cache van de CPU naar het hoofdgeheugen is leeggemaakt, is de gewijzigde versie van het gedeelde object niet zichtbaar voor threads die op andere CPU's worden uitgevoerd. Elke thread kan dus zijn eigen kopie van het gedeelde object krijgen, elke kopie bevindt zich in een aparte CPU-cache.

Het volgende diagram illustreert een schets van deze situatie. Eén thread die op de linker-CPU draait, kopieert het gedeelde object naar zijn cache en verandert de waarde van count in 2. Deze wijziging is onzichtbaar voor andere threads die op de rechter-CPU draaien, omdat de update voor count nog niet is teruggespoeld naar het hoofdgeheugen.

Om dit probleem op te lossen, kunt u het trefwoord vluchtig gebruiken bij het declareren van een variabele. Het kan ervoor zorgen dat een bepaalde variabele direct uit het hoofdgeheugen wordt gelezen en altijd wordt teruggeschreven naar het hoofdgeheugen wanneer deze wordt bijgewerkt.

Race conditie

Als twee of meer threads hetzelfde object delen en meer dan één thread variabelen in dat gedeelde object bijwerkt, kan er een raceconditie optreden.

Stel je voor dat thread A de count-variabele van het gedeelde object in de cache van de processor leest. Stel je ook voor dat thread B hetzelfde doet, maar dan in de cache van een andere processor. Nu voegt thread A 1 toe aan de waarde van count, en thread B doet hetzelfde. Nu is de variabele twee keer verhoogd - afzonderlijk met +1 in de cache van elke processor.

Als deze verhogingen opeenvolgend zouden worden uitgevoerd, zou de variabele count worden verdubbeld en teruggeschreven naar het hoofdgeheugen (oorspronkelijke waarde + 2).

Er werden echter twee incrementen tegelijkertijd uitgevoerd zonder de juiste synchronisatie. Ongeacht welke thread (A of B) zijn bijgewerkte versie van het aantal naar het hoofdgeheugen schrijft, de nieuwe waarde zal slechts 1 meer zijn dan de oorspronkelijke waarde, ondanks de twee verhogingen.

Dit diagram illustreert het optreden van het hierboven beschreven raceconditieprobleem:

Om dit probleem op te lossen, kunt u Java-gesynchroniseerd blok gebruiken. Een gesynchroniseerd blok zorgt ervoor dat slechts één thread tegelijkertijd een bepaald kritiek stuk code kan invoeren.

Gesynchroniseerde blokken garanderen ook dat alle variabelen waartoe toegang wordt verkregen in het gesynchroniseerde blok uit het hoofdgeheugen worden gelezen, en wanneer de thread het gesynchroniseerde blok verlaat, worden alle bijgewerkte variabelen teruggespoeld naar het hoofdgeheugen, ongeacht of de variabele vluchtig of nee is verklaard.

Opmerkingen
  • Populair
  • Nieuw
  • Oud
Je moet ingelogd zijn om opmerkingen te kunnen maken
Deze pagina heeft nog geen opmerkingen