Geheugenanalyse – met Eclipse Memory Analyzer (MAT)

Je manager komt in paniek je kantoor binnen. De server is down! Een kritiek bedrijfsproces ligt stil, en de directeur is woedend, want eerder dit jaar is dit ook al eens gebeurd. De log bevat OutOfMemoryErrors. Die had je de vorige keer ook gezien. De systeembeheerder heeft toen de hoeveelheid geheugen van de Java Virtual Machine (JVM) verhoogd, en je dacht dat daarmee het probleem was opgelost. Niet dus! Met de profiler is niets vreemds te vinden en de productieserver mag je niet profilen. Wat nu? In dit soort gevallen biedt heap dump-analyse uitkomst en één van de betere tools hiervoor is Eclipse Memory Analyzer (MAT).

Geheugendumps (heap dumps)
Een geheugendump (of heap dump) is een bestand dat het interne geheugen van een applicatie representeert. Het is een momentopname van alle classes en objecten die op de heap aanwezig zijn. De verschillende JVM’s hebben interne mechanismen om dumps te genereren, die je kunt triggeren door commando’s van buitenaf of door buitengewone omstandigheden. Omdat dumps een accuraat beeld geven van de interne staat van een applicatie, kunnen ze ons helpen met het identificeren van problemen in de wijze waarop de applicatie met geheugen omgaat. Hoewel profilers vaak niet gekoppeld mogen worden aan productieservers, vanwege de bijbehorende performance impact, is het vaak wel toegestaan om geheugendumps te (laten) genereren.

Hoe kom ik aan dumps?
Geheugendumps kun je op twee manieren genereren. Automatisch bij OutOfMemory-errors en op commando met de hand. De automatische variant kun je aanzetten door een setting toe te voegen aan de JVM command line. Deze setting is verschillend per JVM en zeker aan te bevelen voor productiesystemen, omdat de performance impact nihil is en de toegevoegde waarde groot. Op de website van Eclipse MAT is gedetailleerde uitleg te vinden over de verschillende methoden van het genereren van geheugendumps, per JVM. Daarbij valt op te merken dat er soms meerdere methoden zijn die dumps genereren van verschillende formaten. Het loont in die gevallen de moeite om even te onderzoeken welk formaat de beste informatie bevat, want dat kan nog wel eens verschillen. De IBM JVM geeft bijvoorbeeld de mogelijkheid om heap dumps of core dumps te genereren, waarbij core dumps meer informatie bevatten en dus beter bruikbaar zijn. Het genereren van dumps gebeurt doorgaans rechtstreeks op de server en wordt daarom meestal door systeembeheerders gedaan.

Geheugendumps hebben de beperking dat ze slechts een momentopname geven van de toestand van een applicatie, niet een live view. Om deze beperking te verminderen, is het handig om meerdere geheugendumps te genereren, met een tijdsinterval daartussen. Hoe groot dat tijdsinterval moet zijn, verschilt per applicatie en moet je dus proefondervindelijk vaststellen, maar in mijn ervaring is een dagdeel voldoende. Verder geldt: hoe meer data, hoe beter, dus overweeg om op dezelfde server een groter aantal dumps te genereren dan strikt noodzakelijk is, bijvoorbeeld vier op een dag. Ook loont het om, ter vergelijking, een dump te hebben van korte tijd na het opstarten van de server, bijvoorbeeld een uur. Als laatste: geheugenproblemen zijn gemakkelijker te spotten op servers die een langere uptime gehad hebben op het moment dat de dump werd gegenereerd.

Geheugenanalyse
Analyse van geheugendumps geeft ons zelden sluitende antwoorden, maar wel aanwijzingen. We gebruiken deze aanwijzingen in combinatie met andere bronnen van informatie, zoals het internet, broncode, documentatie, en aanwezige kennis in ontwikkelteams en bij collega’s uit andere disciplines. Ook helpt het om te achterhalen wat de startdatum en tijd van de server is geweest, en om de exacte datum en tijd van het genereren van de dump ergens te noteren. Niet alle soorten dumps slaan namelijk intern hun eigen datum en tijd op. Daarnaast is het handig om JVM- parameters, zoals max heapsize te noteren.

Shallow heap en retained heap
In dit artikel refereer ik soms aan shallow heap en retained heap. Shallow heap is de hoeveelheid geheugen die een object inneemt. Een object gebruikt 32 or 64 bits (afhankelijk van de OS-architectuur) per referentie, 4 bytes per Integer, 8 bytes per Long, etcetera.

De retained set van een Object X is de set objecten die zou worden verwijderd door de garbage collector als X verwijderd wordt door de garbage collector. Het zijn objecten waar X een directe of indirecte referentie naar heeft. Retained heap is de som van de shallow sizes van alle objecten in de retained set van X. Kort gezegd is retained heap de hoeveelheid geheugen die zou worden vrijgegeven als X wordt verwijderd door de garbage collector.

Garbage collector roots
Garbage collector roots zijn bepaalde systeemclasses en -objecten die speciaal gemarkeerd zijn om genegeerd te worden door de garbage collector. Via directe en indirecte referenties is elk ‘live’ object op de heap terug te traceren naar één van de GC-roots. Een object is niet vatbaar voor verwijdering zolang dit pad bestaat. Verliest het object de connectie met de GC-root, dan wordt het op den duur door de garbage collector opgeruimd, samen met de retained set van het object.

Aan de slag met MAT
MAT is een zware tool en heap dumps zijn vaak grote bestanden. Behalve het inlezen van deze bestanden moet je ook indexen en objectbomen aanmaken. Dit kost veel RAM. Hoewel er geen officiële specificatie is, raad ik aan om tenminste 16GB te hebben. Maar voor serieus analysewerk is 32GB beter. Zorg ervoor dat je MAT extra RAM toebedeelt in MemoryAnalyzer.ini, een bestandje dat te vinden is in de MAT-root.

MAT is gepubliceerd onder de Eclipse-licentie en is gratis te downloaden op http://www.eclipse.org/mat/. Voor het analyseren van IBM heap dumps is een plug-in nodig, die wordt aangeboden op http://www.ibm.com/developerworks/java/jdk/tools/mat.html.

Je bent nu klaar om MAT te starten en je eerste dump in te laden.

Overview
Na het laden van een dump kom je op het overzichtsscherm terecht. Dit scherm geeft een aantal globale waarden en overzichten en links naar veelgebruikte functies. Size, classes, objects, en class loader geven totaalwaarden die op deze heap dump gevonden zijn. Size is de consumptie van de heap op het moment dat de dump was gegenereerd. Dit is een andere waarde dan max heapsize. Als deze waarde gelijk is aan max heapsize, dan weet je dat je naar een OutOfMemory-dump kijkt.

In het midden van het scherm staat een cirkeldiagram, waar de grootste objecten een eigen taartpuntje hebben. Let hierbij op dat het om retained heap gaat. Een soms gemaakte misvatting is dat het grijze gedeelte niet-gebruikte heapspace is, maar dat is niet zo. Door met de muis over de taartpunten te hoveren, krijg je wat extra informatie te zien. Dit is handig om even snel een beeld te krijgen, maar een beter overzicht is te vinden in de andere schermen.


Figuur 1: Overview-scherm. Door met de muis over een taartpunt te hoveren, verschijnt links onder het cirkeldiagram extra informatie.

De toolbar boven het overview-scherm geeft een aantal opties. We gaan ze één voor één langs.

Class histogram
Het class histogram geeft een overzicht van geheugengebruik per class. Het scherm biedt de mogelijkheid te filteren met een reguliere expressie, om bijvoorbeeld eigen classes te kunnen inspecteren. Dit scherm heeft op basis van mijn ervaring nog niet veel bruikbare informatie opgeleverd en het geeft vaak de impressie dat er een probleem is met strings (en char[]), terwijl dat niet zo hoeft te zijn.

Dominator tree
De dominator tree is  het meest informatieve scherm. Dit is een uitgebreider overzicht van het cirkeldiagram uit het overview-scherm. De lijst is standaard gesorteerd op retained heap, wat in een oogopslag inzichtelijk maakt welke objecten geheugenproblemen aan het veroorzaken zijn. Potentiele geheugenlekken staan in deze lijst vaak bovenaan, maar dat is niet altijd zo. Ook hier kun je filteren op reguliere expressies. Let op eigengemaakte classes die hoog in deze lijst staan. Die zijn verdacht en moet je verder onderzoeken.

Figuur 2: In de dominator tree kun je objecten openklappen (drill down) om te onderzoeken welke subobjecten veel geheugen consumeren.

Om meer te weten te komen over de top dominators, kun je ze openklappen, om te kijken welke subobjecten het grote geheugengebruik veroorzaken. Blijf openklappen totdat je bij de ware veroorzaker bent aangekomen en ga deze verder onderzoeken.

Object Query Language-studio
Een heel interessante feature is de OQL-studio. MAT ondersteunt een eigen taal, vergelijkbaar met SQL, waarmee je queries kunt uitvoeren op de heap. Voorbeeld:


select * from org.hibernate.impl.SessionFactoryImpl

 

Hoewel geavanceerde SQL-paradigma’s als joins niet worden ondersteund, is dit toch een zeer handige feature om informatie boven te halen die in de normale schermen niet voor handen zou zijn. Neem bijvoorbeeld deze query die een lijst maakt van alle ongebruikte ArrayList-collecties:


select * from java.util.ArrayList where size=0 and modCount=0

 

Op de site van Eclipse MAT zijn documentatie en query-voorbeelden te vinden.

Expert system test
Dit zijn enkele geautomatiseerde tests die overzichten maken van een variëteit van aspecten van de heap dump. Een van de meer belangrijke hiervan is het leak suspects report, dat overigens ook vanuit het overview-scherm te bereiken is. Maar, zoals eerder gezegd, dit scherm heeft de neiging om false positives te geven.

Query browser
Query browser is een collectie van veel gebruikte OQL-queries die voor het gemak zijn gestandaardiseerd in een menu. Dit menu kun je ook vanuit lijsten zoals class histogram en dominator tree aanroepen, door rechts te klikken op objecten in de lijst. Het object in kwestie wordt dan als onderwerp van de query gebruikt.

Figuur 3: Query browser. Opent door rechts te klikken op objecten in lijsten.

Handige queries zijn ‘path to GC roots’ en ‘merge shortest paths to GC roots’ (vindt het kortste pad naar de GC roots; handig als je wilt uitzoeken waarom objecten die eigenlijk verwijderd zouden moeten worden op de heap blijven), leak identification, immediate dominators (handig in class histogram om te zien welke objecten ‘eigenaar’ zijn van het onderzochte object).

Find object by address
Hier kun je objecten zoeken op geheugenadres. Ik heb nog geen nuttig gebruik kunnen maken van deze functie.

Extra functies om met lijsten te werken
In schermen zoals class histogram en dominator tree komen een aantal extra functies beschikbaar. Als je bijvoorbeeld een object of class selecteert met de muis, kun je links in het inspector-scherm de eigenschappen van het betreffende object bekijken. Hierbij zijn vooral de ‘attributes’ informatief, omdat die informatie geven over de interne staat van het object, wat belangrijke aanwijzingen kan geven. Verder verschijnen extra knoppen met lijst-specifieke functies.

Group by
Hiermee kun je de lijst op bepaalde manieren groeperen, wat erg handig is in bepaalde gevallen.

De standaard is no grouping. Group by class loader is handig als je bijvoorbeeld te maken hebt met een applicatieserver waarin meerdere applicaties draaien. In de meeste gevallen heeft elke applicatie dan zijn eigen classloader. Op deze manier krijg je dus een overzicht waarin de classloaders gescheiden zijn en kun je snel zien welke applicatie bijvoorbeeld veel geheugen consumeert. Group by class is om memory leaks te identificeren die om de een of andere reden niet aan een dominator zijn gekoppeld.

Calculate retained size
In bepaalde overzichten wordt de retained size niet standaard berekend omdat dat te lang zou duren. Met deze knop kun je forceren dat dat wel gebeurt.

Export
Hiermee kun je de lijst die je op het scherm ziet exporteren naar HTML-, CSV-, of TXT-files. Erg handig als je meer complexe analyses wilt doen op de gegenereerde overzichten. Let wel, hij exporteert alleen de lijst op het scherm, dus soms is het nodig om een ‘Expand all’ te doen om alle waarden te krijgen. Bestanden die je op deze manier genereert, kunnen soms erg groot zijn waardoor je ze niet altijd in tools zoals Excel kunt openen.

Show as histogram
Geef de huidige lijst weer als histogram. Vooral handig na OQL-queries.

Dumps met elkaar vergelijken
MAT biedt de mogelijkheid om meerdere dumps in te laden, simpelweg door ze na elkaar te openen. Elke dump krijgt dan een eigen tab, wat switchen tussen dumps gemakkelijk maakt. De tool staat toe om een dump te openen terwijl een andere dump aan het laden is. Dat leidt echter tot NullPointerExceptions en werkt dus niet. Binnen MAT zijn er mogelijkheden om overzichten automatisch met elkaar te vergelijken, bijvoorbeeld door de compare basket te gebruiken. Echter, omdat de compare basket nogal traag is, en in bepaalde gevallen domweg niet werkt, heb ik de voorkeur om gewoon op het oog te vergelijken en geen gebruik te maken van deze functionaliteit.

Geheugenlekken (Memory leaks) vinden
Door de introductie van de garbage collector komen geheugenlekken minder vaak voor dan in talen waar de programmeur zelf verantwoordelijk is voor het geheugenbeheer, maar ze zijn ook in Java nog steeds mogelijk. Waar je ze moet zoeken in de broncode is moeilijk te zeggen. Ze zitten vaak verborgen op plaatsen waar je ze niet verwacht.

Een geheugenlek manifesteert zich als een monotone toename van geheugengebruik binnen een bepaalde class of instantie, of toename van aantal instanties van een bepaalde class. Hier is het dus belangrijk om meerdere dumps met een tijdsinterval te hebben. Geheugenlekken zijn gemakkelijker te spotten op een applicatie die langere tijd heeft gedraaid, omdat het geheugengebruik van het lek dan groter is. Zoals eerder gezegd: het is vaak handig om ook een dump bij de hand te hebben die korte tijd na startup is gegenereerd, zodat je de eerste stage van het lek kunt observeren.

Open de dominator tree, sorteer op ‘retained heap’ (doet hij standaard al). Lekken vind je vaak nabij de top van de lijst. Vergelijk verschillende dumps met elkaar en zoek naar de monotone toename. Heb je iets gevonden, dan ben je meestal nog niet klaar. Het enige dat je hebt is een mogelijk lek. Om uitsluitsel te krijgen, moet je je bevindingen combineren met informatie uit andere bronnen.

Als je geen lek vindt maar wel een lek verwacht, groepeer dan op class. Bepaalde soorten lekken zijn in eerste instantie onzichtbaar, maar openbaren zichzelf door deze groepering.

Conclusie
MAT is een zeer bruikbare tool die aantoonbaar veel meerwaarde biedt in ‘hopeloze’ situaties, waarbij andere analysemethoden hebben gefaald. Het is gratis, dus alle reden om het zelf eens uit te proberen. Hopelijk heeft dit artikel je een beetje een idee gegeven van wat MAT kan en wat handvaten om er zelf mee aan de slag te gaan.