Heap

Logsift shows what fills a JVM’s memory in two ways:

Heap histogram of a running JVM

Connect to a JVM (see JVM connections), then click Heap… in its Threads tab, or Heap in the bar above its log.

A heap histogram of a running JVM, by class

Opening a heap dump

While it reads, the tab shows the stage (Reading the heap dump, Linking objects, Finding what holds what, Finding why objects are alive) with a progress bar and Cancel. A dump of several GB takes a minute or more.

The header shows the file size, when the dump was taken, how many objects, and how much memory is in use. The tab is named Heap: file.

A heap dump: one HashMap in a static field holds 85% of the heap, with the chain of references that keeps it alive

Two words first

Sizes are the JVM’s own: 12-byte headers, 4-byte references with compressed pointers, objects aligned to 8 bytes. When the dump was taken without compressed pointers, the header says sizes without compressed pointers.

What fills the heap

The first tab says in sentences what matters most. Each finding is a card with a coloured bar on its left (the same in light and dark mode):

Colour Finding When
red One object in where holds X (P of the heap) One object holds 10% of the heap or more
amber X is spent on copies of the same strings and empty or mostly empty collections Wasted memory is 3% of the heap or more
gray X isn’t strongly reachable More than 5% of the dump is reached only by weak or soft references, or is garbage the JVM hadn’t collected yet. It is freed when memory runs low, so it isn’t a leak

A red card reads like “One HashMap (20,000 entries) in static LeakApp.CACHE holds 13.6 MB (85% of the heap). Mostly 20,000 × LeakApp.Session, 616 bytes each with what they hold.” Logsift follows the chain down while one part holds 80% or more of its parent, so it names the collection that is filling up rather than its internal array. Buttons:

With no red card, it says “No single object holds 10% of the heap or more. The memory is spread out: see the biggest objects and the classes.”

Below the cards, What the memory is made of shows the 8 classes that take the most space themselves, as light blue bars ( dark blue in dark mode), as wide as their share of the heap.

What to do: a red card is where to look for a leak. Check that the collection it names has a limit, and that entries are removed when they are no longer needed.

Why is it alive?

The right side of the tab. Select any object (in any tab) and it shows the shortest chain of references from a GC root to it:

To free the object, one link in that chain has to go: usually the collection that keeps adding to it.

“Nothing keeps it alive through strong references” means only weak or soft references reach it, or it is garbage: it would be freed.

Biggest objects

A tree of every object with what it holds, biggest first, from All objects in use. Each row shows what it holds (bold), its share of the heap (gray; blank under 0.1%) and what it is (HashMap (20,000 entries), String "…", byte[1,024]). Open a row to see what is inside it. Each level shows the 200 biggest, then … and N smaller.

Map

The heap as boxes, each as big as what it holds, with what it holds inside it.

The heap as a map: a class holds a HashMap, which holds 20,000 sessions

What the colours mean:

How it is built: it looks through a collection’s own parts to what is kept in it, and puts many objects of one class in one box (20,000 × LeakApp.Session, 13.9 MB). The 3 biggest objects of a class get their own box when each is 3% or more of their parent. Classes under 0.1% of their parent go into smaller objects. It shows three levels at a time.

Classes

Every class, with Objects (how many), Size (their own bytes) and Retained (what they hold, freed with them). Type in Filter classes to find one. Click a header to sort. Select a class to see why its biggest object is alive.

Wasted memory

Memory spent for nothing, and the field it sits in. Logsift works it out in the background once the dump is open.

Wasted memory: copies of the string ACTIVE and empty ArrayLists, each with the field that holds them

The top line sums it up: “X (P of the heap) is spent for nothing”, with what to do.

Copies of the same strings: each text kept in 2 or more String objects. Columns: Text, Copies, Wasted (the bytes all but one copy take), Kept in (the field, such as Session.status). Keep one copy: a constant, an enum, or String.intern(); with G1, -XX:+UseStringDeduplication does it for you.

Empty and mostly empty collections: JDK lists, maps, sets and queues with nothing in them, or mostly empty: at least 8 unused slots and under a quarter used. Columns: Collections, How many, Wasted, Kept in. Create them when first needed, or give them a smaller starting size.

Kept in says mostly … when not all of them sit in that one place, and nothing strong when only weak references reach them.

Only the JDK’s own collections are counted (ArrayList, Vector, HashMap, LinkedHashMap, HashSet, Hashtable, WeakHashMap, IdentityHashMap, ConcurrentHashMap, ArrayDeque, CopyOnWriteArrayList, LinkedList, TreeMap, TreeSet), not those of other libraries.

Select a row to see why one of them is alive.

Threads

Threads (N) at the top right opens the dump’s threads in the Threads view: names, states and stacks at the moment of the dump. A heap dump doesn’t record which locks threads hold, so lock findings are missing.

Comparing two dumps

Compare with Another Dump… (top right), or JVM → Compare Two Heap Dumps…, takes an earlier dump of the same program and says what grew, and where. The dumps are put in order by when they were taken.

Two heap dumps compared: a HashMap in a static field grew by 11.8 MB, from 5,000 to 20,000 entries

Objects move between dumps, so places are matched by the chain of fields from a GC root that keeps them, not by address. When a place grows mostly because of a place inside it, the inner one is shown. Growth shows as +X, and a shrink as −X.

If nothing stands out: “No one place holds the growth: it is spread out.” Then look at Classes.

Comparing works with two dumps on this computer, not with dumps read on a server.

A dump on a server

JVM → Open Heap Dump on a Server… reads a dump where it is, over SSH, on a Linux or macOS server, so a 40 GB dump doesn’t have to come to your computer. Pick the file and click Read It There….

Logsift first looks at the server and asks before anything starts:

Reading a heap dump on a server: what it takes there, asked before it starts

Buttons: Read It There, Copy to Another Machine…, Use Another Folder… (for the index, on a disk with room), Cancel.

It can’t go ahead when there is no Java 8 or later on the server, or when the disk has less free space than the index needs plus 1 GB. Choose Use Another Folder… on a bigger disk, or copy the dump elsewhere.

How it runs: the reader is copied to ~/.logsift/heap on the server and runs at low priority (nice, ionice). It keeps going if you disconnect; opening the same dump again reconnects. It ends when you close the tab, or after 30 minutes with no one connected. Its index is temporary files that go when it ends. Nothing else is removed there.

Copy to Another Machine… is for production servers: the dump is read once on the first server at low priority, copied to a folder on another server, and read there. The other server fetches it itself when it can sign in to the first with its own key and already knows its host key; else Logsift offers Copy Through This Computer. Logsift never overwrites a file: if the name is taken, choose another folder. A stopped copy leaves NAME.part there; Logsift says where, and doesn’t remove it.

Big dumps

When a dump is bigger than a third of Logsift’s memory, its index goes to files on disk, in ~/.logsift/heap-index, removed when the tab closes. The status bar then says index on disk. It needs free disk about the dump’s size plus 256 MB; when there isn’t enough, Logsift says so before it starts. A 12 GB dump with 223 million objects opens in about 2½ minutes with the app’s usual 2 GB of memory.

Limits

If a dump can’t be read, the tab says “Could not read this heap dump: …” with the reason, such as not a heap dump (.hprof) for another kind of file.