Stack and Heap: waar bevindt mijn code zich in het geheugen?

📖 17min read

Armoede in overvloed, vergeten herinneringen

Ik leerde ‘geheugenstructuur’ tijdens lessen besturingssysteem of systeemprogrammering op school. Ik herinner me ook dat termen als stapel en heap als examenvragen werden gebruikt.

Maar eerlijk gezegd besteedde ik zelden serieuze aandacht aan het geheugen totdat ik afstudeerde. Tegenwoordig is het RAM-geheugen van een persoonlijke laptop in principe 16 GB of 32 GB. Er was vrijwel geen tekort aan geheugen tijdens het uitvoeren van opdrachten op bachelorniveau. Bovendien ruimt de Garbage Collector (GC) in talen als Java de ruimte automatisch op, zodat ik me geen zorgen hoefde te maken over het geheugenadres.

Dus, bedwelmd door de overvloed aan hardware en het gemak van taal, stortte ik me op praktisch werk en vergat ik het ‘geheugengevoel’, de basisvaardigheid van een ontwikkelaar.

Pijnlijke lessen geleerd uit de praktijk

Tijdens het oefenen voor codeertoetsen op school kon ik alleen maar een paar getallen in een array zetten en ze ronddraaien. Het aantal invoergegevens bedroeg maximaal 100.000. Maar de praktijk was anders. Hoe klein een startup ook was, er werden tientallen of miljoenen stukjes daadwerkelijke klantgegevens in de database verzameld.

In de praktijk wordt een technologie genaamd ‘ORM (Object Relational Mapping)’ gebruikt om deze grote hoeveelheid gegevens gemakkelijk te verwerken. Het is een zeer nuttig hulpmiddel waarmee u gegevens zoals Java-objecten kunt verwerken zonder dat u zelf SQL-query’s hoeft te schrijven. (Hibernate is een representatief voorbeeld in het Java-kamp.)

Het probleem was dat deze tool ‘te handig’ was. Met slechts één druk op de knop werden alle gegevens in de database in de lijst gebracht, dus ik kon het gewicht van de gegevens die erachter verborgen lagen niet inschatten. Door slechts één regel code te schrijven met de tekst ‘Klantinformatie opvragen’ werden de gegevens van tienduizenden mensen en de bijbehorende bestelgegevens allemaal in het geheugen geladen.

De resultaten waren desastreus. Het 32 ​​GB RAM-geheugen was in een mum van tijd vol. De server had last van ‘extreme vertraging’ doordat de GC (cleaner) het geheugen probeerde te beveiligen en stopte uiteindelijk.

Bekende stapel, onbekende stapel

Terwijl ik naar het foutenlogboek keek dat de server naar buiten bracht, werd mijn aandacht getrokken door één woord.

java.lang.OutOfMemoryError: Java-heapruimte

Eigenlijk was het woord ‘stack’ heel bekend. Ik heb het ad nauseam geleerd tijdens de datastructuurklas, en het is ook de naam van een site (Stack Overflow) die ontwikkelaars één keer per dag bezoeken. Het was ook algemeen bekend dat als een recursieve functie verkeerd werd geschreven, de stapel zou barsten.

In de praktijk is het echter zo dat wanneer een server uitvalt, de gevonden boosdoener niet de stack is. Het foutenlogboek wees altijd naar ‘Heap’.

“Het is niet zo dat de stapel vol is, maar dat er niet genoeg heapruimte is?”

Op dat moment schoot er een vraag door mijn hoofd. Ik begrijp de stapel, maar wat is de stapel? Waarom ontploft het in de praktijk zo vaak? Is dit hetzelfde als de heap in de datastructuur? Waarom hindert mijn code de heap en niet de stack?

Die vraag leidde me terug naar de wereld van stoffige grote boeken en Googlen. En toen kwam ik erachter. Het feit dat ‘Memory (RAM)’, het huis waar mijn code woont, eigenlijk niet één kamer is, maar een ‘aparte ruimte’ die grondig is verdeeld en wordt beheerd volgens het doel.

Geheugen is geen enkele spatie. Het is verdeeld in een snelle en smalle ‘stapel’ en een langzame en brede ‘hoop’.

Werkbank en magazijn in digitaal distributiecentrum

Laten we terugkeren naar het wereldbeeld van ons ‘digitale logistieke centrum’. Hier is ‘RAM (geheugen)’ de ruimte waar de werknemer (CPU) items uitspreidt om werk te doen. Omwille van de efficiëntie wordt deze ruimte echter in twee afzonderlijke ruimtes geëxploiteerd.

1. Stapel: de persoonlijke werkbank van de werknemer

2. Hoop: Openbaar magazijn

De ‘stapel’ die verdwijnt als de functie eindigt, en de ‘hoop’ die pas verdwijnt als de schoonmaker hem opruimt.

[Codeverificatie] Twee werelden bewezen door fouten

Is het geheugen echt zo verdeeld? Het bestaan van deze twee spaties kan duidelijk worden bewezen door opzettelijk een fout in de code te veroorzaken.

1. Stack OverflowError

De stapel wordt een ‘werkbank’ genoemd. De werkbank is smal. Als de functie niet eindigt en zichzelf blijft aanroepen (recursie), stapelen de documenten zich op de werkbank op tot aan het plafond en storten uiteindelijk in.

public class StackTest {
    public static void recursiveCall(int depth) {
        // Oneindige recursieve aanroep: de functie stopt niet en blijft op de Stack stapelen
        System.out.println("Stack Depth: " + depth);
        recursiveCall(depth + 1);
    }

    public static void main(String[] args) {
        recursiveCall(1);
    }
}

Resultaat: na enkele duizenden ronden spuugt het StackOverflowError uit. Hoe leeg de heap-ruimte ook is, als de stapel (werkbank) vol is, sterft het programma.

2. Heap-explosie (OutOfMemoryError)

Laten we deze keer de nachtmerrie die ik had opnieuw creëren. Vergelijkbaar met de situatie waarin tienduizenden objecten tegelijk worden geladen als gevolg van onjuist gebruik van ORM, zullen we doorgaan met het maken van enorme lijsten en deze in de hoop proppen.

import java.util.ArrayList;
import java.util.List;

public class HeapTest {
    public static void main(String[] args) {
        List<byte[]> warehouse = new ArrayList<>();
        
        while (true) {
            // Blijft 1MB aan data genereren en in het magazijn (Heap) stapelen
            // Praktijkvoorbeeld: gebeurt wanneer tienduizenden records uit de DB worden geladen zonder paginering
            warehouse.add(new byte[1024 * 1024]);
        }
    }
}

Resultaat: java.lang.OutOfMemoryError: Java-heapruimte. Dit is geen stapelprobleem. Deze fout treedt op omdat er geen ruimte meer is om artikelen in het magazijn te laden. Dit is het moment waarop ik met eigen ogen zie hoe de code die ik heb geschreven de heap hindert.

Garbage Collector (GC)

Er is hier een belangrijk verschil. De stapel wordt ‘automatisch’ geleegd als de functie eindigt. U hoeft zich geen zorgen te maken. Als iemand de hoop echter niet opruimt, blijft het afval zich opstapelen.

In oude talen zoals de C-taal moesten ontwikkelaars deze rechtstreeks opschonen met het commando free(). Als je het vergeet, raakt het magazijn vol met afval en explodeert (geheugenlek). Aan de andere kant gebruiken moderne talen zoals Java, Python en JavaScript (JS) professionele schoonmaakmiddelen genaamd ‘GC (Garbage Collector)’.

“Niemand gebruikt dit object meer?” GC loopt periodiek door de hoop, vindt voorwerpen die geen eigendom zijn en gooit ze weg. Hierdoor hoeven we geen geheugenreleasecode te schrijven.

Maar niets is gratis. Op het moment dat de GC de dieptereiniging doet, liggen alle werkzaamheden in het logistieke centrum even stil (Stop-the-world). Deze schoonmaaktijd is de reden dat het spel plotseling vertraging oploopt of dat de server ongeveer een seconde vastloopt.

Concluderend: Ogen die het onzichtbare zien

Nadat ik de stapel en de heap had begrepen, begon de code op mijn monitor er anders uit te zien. Als ik in het verleden naar de code new Student() keek, dacht ik gewoon: “Ik heb een object gemaakt.” Maar nu kan ik het zien.

“Er is nu een doos het magazijn binnengekomen dat de Heap heet. Als ik deze niet verwijder (of als GC niet komt), zal deze geheugen blijven opslokken.”

Nadat ik ‘ogen had gekregen om het onzichtbare te zien’, kon ik mezelf bevrijden van vage angsten. Zelfs als de server OutOfMemory uitspuugt, raak ik niet in paniek en druk ik op de reboot-knop zoals vroeger. Schakel in plaats daarvan de analysetool in en vraag rustig: “Welk object neemt de hoop in beslag?” Dit komt omdat als je de oorzaak kent, je deze kunt oplossen.

We hebben nu de ruimte (geheugen) veroverd waar de code is opgeslagen. Het magazijn is volledig voorbereid. Dus wie zijn de ‘arbeiders’ die daadwerkelijk artikelen in dit magazijn transporteren en assembleren? Wat is het verschil tussen alleen werken en met meerdere mensen tegelijk werken (multitasking)?

Laten we de volgende keer naar de wereld van ‘Process & Thread’ gaan, de bloem van het besturingssysteem en het eeuwige huiswerk van back-end-ontwikkelaars.

Plaats een reactie