Java Memory Management, med sin inbyggda garbage collection, är en av språkets bästa prestationer. Den gör det möjligt för utvecklare att skapa nya objekt utan att uttryckligen oroa sig för minnesallokering och -nedläggning, eftersom skräpplockaren automatiskt återtar minnet för återanvändning. Detta gör det möjligt att utveckla snabbare med mindre kod, samtidigt som minnesläckor och andra minnesrelaterade problem elimineras. Åtminstone i teorin.
Ironiskt sett verkar Java garbage collection fungera för bra, genom att skapa och ta bort för många objekt. De flesta problem med minneshantering löses, men ofta till priset av att allvarliga prestandaproblem skapas. Att göra garbage collection anpassningsbar till alla typer av situationer har lett till ett komplext och svåroptimerat system. För att förstå garbage collection måste du först förstå hur minneshanteringen fungerar i en Java Virtual Machine (JVM).
Hur Java Garbage Collection verkligen fungerar
Många tror att garbage collection samlar in och kasserar döda objekt. I verkligheten gör Java garbage collection tvärtom! Levande objekt spåras och allt annat betecknas som skräp. Som du kommer att se kan detta grundläggande missförstånd leda till många prestandaproblem.
Låt oss börja med heap, som är det minnesområde som används för dynamisk allokering. I de flesta konfigurationer allokerar operativsystemet heap i förväg för att hanteras av JVM medan programmet körs. Detta har ett par viktiga konsekvenser:
- Objektskapandet går snabbare eftersom global synkronisering med operativsystemet inte behövs för varje enskilt objekt. En allokering gör helt enkelt anspråk på en del av en minnesarray och flyttar offsetpekaren framåt (se figur 2.1). Nästa allokering börjar vid denna offset och gör anspråk på nästa del av arrayen.
- När ett objekt inte längre används återtar skräpplockaren det underliggande minnet och återanvänder det för framtida objektallokering. Detta innebär att det inte sker någon explicit radering och att inget minne ges tillbaka till operativsystemet.
Figur 2.1: Nya objekt allokeras helt enkelt i slutet av den använda heap-ytan.
Alla objekt allokeras på det heap-område som förvaltas av JVM. Varje objekt som utvecklaren använder behandlas på detta sätt, inklusive klassobjekt, statiska variabler och till och med själva koden. Så länge ett objekt refereras anser JVM att det är levande. När ett objekt inte längre refereras och därför inte kan nås av programkoden, tar sophämtaren bort det och återtar det oanvända minnet. Hur enkelt detta än låter så väcker det en fråga: vad är den första referensen i trädet?
Garbage-Collection Roots-The Source of All Object Trees
Varje objektträd måste ha ett eller flera rotobjekt. Så länge programmet kan nå dessa rötter är hela trädet nåbart. Men när anses dessa rotobjekt vara nåbara? Speciella objekt som kallas garbage-collection roots (GC roots; se figur 2.2) är alltid nåbara och det är alla objekt som har en garbage-collection root vid sin egen rot också.
Det finns fyra typer av GC roots i Java:
- Lokala variabler hålls vid liv av stacken i en tråd. Detta är inte en virtuell referens till ett verkligt objekt och är därför inte synlig. I alla avseenden är lokala variabler GC-rötter.
- Aktiva Javatrådar betraktas alltid som levande objekt och är därför GC-rötter. Detta är särskilt viktigt för lokala trådvariabler.
- Statiska variabler refereras av sina klasser. Detta faktum gör dem de facto till GC-rötter. Klasserna i sig själva kan bli garbage-collected, vilket skulle ta bort alla refererade statiska variabler. Detta är särskilt viktigt när vi använder applikationsservrar, OSGi-containrar eller klassladdare i allmänhet. Vi kommer att diskutera de relaterade problemen i avsnittet Problemmönster.
- JNI-referenser är Java-objekt som den inhemska koden har skapat som en del av ett JNI-anrop. Objekt som skapas på detta sätt behandlas speciellt eftersom JVM inte vet om det refereras av den inhemska koden eller inte. Sådana objekt utgör en mycket speciell form av GC-root, som vi kommer att undersöka närmare i avsnittet Problemmönster nedan.
Figur 2.2: GC-rots är objekt som själva refereras av JVM och därmed hindrar alla andra objekt från att bli skräpinsamlade.
En enkel Java-applikation har därför följande GC-rötter:
- Lokala variabler i huvudmetoden
- Huvudtråden
- Statiska variabler i huvudklassen
Märkning och sopning av skräp
För att avgöra vilka objekt som inte längre används kör JVM med jämna mellanrum det som mycket träffande kallas för en mark-and-sweep-algoritm. Som du kanske förstår är det en enkel process i två steg:
- Algoritmen går igenom alla objektreferenser, med början vid GC-rötterna, och markerar varje objekt som hittas som levande.
- Alt heapminne som inte är upptaget av markerade objekt tas tillbaka. Det markeras helt enkelt som fritt, vilket i huvudsak innebär att det sopas fritt från oanvända objekt.
Garbage collection är tänkt att ta bort orsaken till klassiska minnesläckor: oåtkomliga men inte borttagna objekt i minnet. Detta fungerar dock endast för minnesläckor i den ursprungliga betydelsen. Det är möjligt att ha oanvända objekt som fortfarande kan nås av ett program eftersom utvecklaren helt enkelt glömde att avreferera dem. Sådana objekt kan inte samlas in som skräp. Ännu värre är att en sådan logisk minnesläcka inte kan upptäckas av någon programvara (se figur 2.3). Även de bästa analysprogrammen kan bara lyfta fram misstänkta objekt. Vi kommer att undersöka analys av minnesläckage i avsnittet Analysera prestandapåverkan av minnesanvändning och Garbage Collection nedan.
.