De Foreign Function & Memory (FFM) API introduceert MemorySegment om veilig toegang te krijgen tot off-heap geheugen. Elk segment is gekoppeld aan een MemorySession (of Arena in nieuwere versies) die de levenscyclus definieert. Wanneer een arena wordt gesloten, markeert de ScopedMemoryAccess laag alle gekoppelde segmenten als "niet levend."
Elke daaropvolgende toegangspoging activeert een ScopedMemoryAccess.Scope controle die onmiddellijk een IllegalStateException gooit. Om te voorkomen dat de garbage collector een segment terugwint terwijl een native operatie gaande is, maakt de JVM impliciet gebruik van reachabilityFence semantiek. De compiler voegt keep-alive barrières in bij kritieke grenzen, zodat het segmentobject sterk bereikbaar blijft totdat de native aanroep is voltooid.
Deze coördinatie maakt expliciete deterministische opruiming via close() mogelijk terwijl gebruik-na-vrijgave-fouten worden voorkomen die zouden optreden als de GC het segment voortijdig zou finaliseren. Het ontwerp zorgt ervoor dat de geheugensafety wordt behouden zonder handmatige synchronisatie voor elke toegang te vereisen. Deze architectonische keuze overbrugt de kloof tussen handmatig geheugenbeheer en Java's geautomatiseerde garbage collection paradigma.
Stel je een hoogfrequente handelsapplicatie voor die marktdata verwerkt via MemorySegment die is gekoppeld aan off-heap buffers die worden gedeeld met een C++ beursgateway. Het probleem doet zich voor wanneer meerdere threads proberen prijzupdates te lezen terwijl een achtergrondonderhoudthread periodiek de buffer vernieuwt door de oude Arena te sluiten en een nieuwe toe te wijzen. Zonder juiste temporele veiligheid kan een lezerthread een segment proberen te benaderen waarvan het onderliggende geheugen is teruggegeven aan de besturingssysteem, wat kan leiden tot een JVM-crash of stille gegevenscorruptie.
Een overwogen oplossing was expliciete referentietelling met AtomicInteger. Elke leesoperatie zou de teller verhogen en verlagen na voltooiing. De voordelen omvatten eenvoudige logica en onmiddellijke detectie van lekken. De nadelen daarentegen zijn significante concurrentie op de atomische variabele onder hoge belasting, en het kan niet integreren met de garbage collector; een vergeten decrement lekt nog steeds geheugen, en het voorkomt niet dat de arena sluit terwijl native code een rauwe pointer vasthoudt.
Een andere aanpak omvatte try-with-resources blokken die elke toegang omhulden, waardoor de arena open blijft tijdens de operatie. De voordelen zijn deterministische scoping en schone syntaxis. De nadelen omvatten overmatig sluiten en openen van arena's voor kortdurende operaties, wat prohibitief kostbaar is bij het toewijzen van duizenden segmenten per seconde. Verder kan dit patroon niet beschermen tegen asynchrone callbacks van native code die mogelijk langer leven dan de Java-scope.
De gekozen oplossing maakte gebruik van Arena.ofShared() met de juiste plaatsing van reachabilityFence en scoped toegang controles. Door de sluiting van de arena te beperken tot een speciale onderhoudthread en te zorgen dat alle leesoperaties de levendigheid van het segment valideerden voordat ze dereferencen, elimineerde het systeem racecondities. Het ScopedMemoryAccess mechanisme bood nul-kosten controles op het snelle pad terwijl de reachability garanties van de JVM GC-invloed voorkwamen. Het resultaat was een stabiel systeem dat miljoenen berichten per seconde verwerkte zonder native crashes of geheugenlekken.
Waarom gooit MemorySegment WrongThreadException zelfs wanneer het segment niet expliciet beperkt is, en hoe bepaalt het type Arena de thread-beperkingssemantiek?
Veel kandidaten veronderstellen dat alle segmenten standaard thread-veilig zijn. In werkelijkheid creëert Arena.ofConfined() segmenten die alleen toegankelijk zijn voor de oorspronkelijke thread, afgedwongen door thread-id controles in ScopedMemoryAccess. Arena.ofShared() staat toegang tussen threads toe maar vereist externe synchronisatie. De uitzondering treedt op wanneer het adres van een beperkt segment naar een andere thread wordt doorgegeven via een lambda of callback.
Hoe verschilt het reachabilityFence mechanisme van PhantomReference bij het waarborgen dat off-heap bronnen geldig blijven tijdens native aanroepen?
Kandidaten verwarren deze twee mechanismen vaak. PhantomReference stelt post-mortem opruiming mogelijk nadat een object onbereikbaar wordt, wat te laat is om gebruik-na-vrijgave te voorkomen tijdens een actieve operatie. reachabilityFence fungeert als een door de compiler ingevoegde barrière die het object sterk bereikbaar houdt totdat de fence wordt uitgevoerd. In FFM voegt de JVM deze fences automatisch in rond MemorySegment toegangsmethoden, zodat het segment levend blijft tijdens de toegang tot native geheugen zonder dat handmatige plaatsing in gebruikerscode vereist is.
Wat is het onderscheid tussen het direct sluiten van een MemorySegment versus het sluiten van zijn bovenliggende Arena, en waarom maakt het sluiten van een arena alle afgeleide segmenten tegelijkertijd ongeldig?
Een veelvoorkomende misvatting is dat segmenten onafhankelijke bronnen zijn. In feite delen segmenten die zijn afgeleid via slice() of reinterpret() dezelfde ScopedMemoryAccess.Scope als hun bovenliggende arena. Wanneer Arena.close() wordt aangeroepen, maakt het de hele scope ongeldig, wat zich verspreidt naar alle afgeleide segmenten. Het sluiten van een individueel segment markeert alleen dat specifieke zicht als ongeldig, maar het onderliggende geheugen blijft toegewezen totdat de arena sluit.