Skip to content
Jason Koch edited this page Mar 2, 2026 · 1 revision

Object Query Language (OQL)

OQL is an SQL-like query language built into Eclipse Memory Analyzer for exploring Java heap dumps.

For full SQL support with a familiar syntax — native JOINs, GROUP BY, COUNT, SUM, ORDER BY, LIMIT/OFFSET — see the MAT Calcite Plugin, which uses Apache Calcite to query heap dumps with standard SQL. Many of the patterns below simulate features that Calcite provides natively.

Quick Start

The simplest OQL query selects all objects of a class:

SELECT * FROM java.lang.String

Alias the class and project specific fields:

SELECT s AS String, s.value AS characters FROM java.lang.String s

Include inbound reference counts:

SELECT s AS String, s.value AS characters, inbounds(s).@length AS inbound_count
FROM java.lang.String s

OQL queries can return two kinds of values:

  • Heap objects (IObject) — objects from the snapshot, displayed as links (e.g. java.lang.String [id=0x22e58820]). Produced when the expression resolves to a snapshot object.
  • Regular Java objects — values generated during OQL processing, such as int arrays. These display using standard Java notation (e.g. [I@620f7a39) and do not link into the heap. inbounds(s) returns this kind of value — an int[] of object IDs, not the objects themselves. Use SELECT OBJECTS to force heap-object resolution.

Core Syntax

SELECT … FROM … WHERE

SELECT [DISTINCT] [OBJECTS | AS RETAINED SET] <expr>, ...
FROM [INSTANCEOF] <class-or-pattern> [<alias>]
[WHERE <condition>]
Keyword Meaning
OBJECTS Force results to be treated as heap objects, not plain Java values
DISTINCT Remove duplicate rows
AS RETAINED SET Return the retained set of the matched objects
INSTANCEOF Include all subclass instances, not just the exact class
UNION Combine two result sets

Class patterns

SELECT * FROM java.lang.String s                    -- exact class
SELECT * FROM INSTANCEOF java.util.AbstractList s   -- class and subclasses
SELECT * FROM "java.lang\\..*" s                    -- regex: all java.lang classes

Built-in attributes and functions

Expression Description
toString(o) String representation of the object
classof(o) The class of the object as an IClass
toHex(o.@objectAddress) Object address formatted as a hex string
o.@objectAddress Object memory address (long)
o.@objectId Internal MAT object ID (int)
o.@usedHeapSize Shallow heap size in bytes
o.@retainedHeapSize Retained heap size in bytes
o.@clazz Class of the object
o.@clazz.@name Class name as a string
o.@fields Array of field descriptors
o.@length Length of an array
o.getField("name") Read a named field
inbounds(o) Array of inbound referencing object IDs
outbounds(o) Array of outbound referenced object IDs
o[0:-1] Expand a map or collection to its entries
${snapshot} The current ISnapshot instance

Snapshot API access

The ${snapshot} variable exposes the full ISnapshot API:

SELECT OBJECTS r FROM OBJECTS ${snapshot}.getGCRoots() r

OQL vs SQL

OQL is built into MAT and requires no additional installation. The MAT Calcite Plugin adds a separate query window with native SQL via Apache Calcite.

Feature OQL SQL (Calcite plugin)
Basic SELECT/FROM/WHERE SELECT s FROM java.lang.String s SELECT s.this FROM "java.lang.String" s
toString, class toString(s), classof(s) toString(s.this), getType(s.this)
Object address s.@objectAddress getAddress(s.this)
Shallow / retained size s.@usedHeapSize, s.@retainedHeapSize shallowSize(s.this), retainedSize(s.this)
Map / field access h[0:-1].size(), h.getField("x") getSize(h.this), getField(h.this,'x')
Multi-line comment /* comment */ /* comment */
Single-line comment // comment -- comment
JOIN Simulated — see JOIN operations Native SQL JOIN
LIMIT / OFFSET Simulated — see LIMIT and OFFSET Native SQL LIMIT/OFFSET
ORDER BY Click column headers in the UI Native SQL ORDER BY
GROUP BY Simulated — see GROUP BY Native SQL GROUP BY
COUNT Simulated via @length — see COUNT Native COUNT()
MAX, MIN Not supported Native MAX(), MIN()
AVG, SUM Not supported Native AVG(), SUM()

Advanced Features

SELECT DISTINCT

DISTINCT operates on full rows, treating each unique combination of column values as distinct.

Get unique classes matching a name pattern:

SELECT DISTINCT OBJECTS classof(s) FROM "java.lang\\.S.*" s

Equivalent using FROM OBJECTS with a pattern (no alias needed):

SELECT * FROM OBJECTS "java.lang\\.S.*"

De-duplicate on class objects without forcing heap-object mode:

SELECT DISTINCT classof(s) FROM "java.lang\\.S.*" s
Sub-SELECT with select items

A sub-SELECT inside FROM OBJECTS (...) produces RowMap objects — rows with named key-value pairs — which can then be queried as a table in the outer SELECT.

Access the length of an intermediate array result:

SELECT v, v.@length FROM OBJECTS ( SELECT OBJECTS s.value FROM java.lang.String s ) v

Project named columns from the inner rows:

SELECT v, v.s, v.val FROM OBJECTS ( SELECT s, s.value AS val FROM java.lang.String s ) v

Use SELECT * in the outer query to flatten the RowMap into individual columns:

SELECT * FROM OBJECTS ( SELECT s, s.value AS val FROM java.lang.String s ) v
LIMIT and OFFSET

OQL has no native LIMIT/OFFSET, but array slice notation [start:end] can simulate it on a sub-select result.

Return a single row at index 3:

SELECT eval((SELECT * FROM OBJECTS ( SELECT s, s.value AS val FROM java.lang.String s ) v))[3]
FROM OBJECTS 0

Return 20 rows starting at index 10 (skip the first 10):

SELECT z.s FROM OBJECTS ( eval((SELECT s FROM "java.lang.String" s ))[10:29] ) z

For native LIMIT/OFFSET, use the MAT Calcite Plugin.

Context Menu for object columns

When a query result contains columns that are heap objects (IObject), right-clicking a cell in the result table offers context menu actions. These let you filter the current query to the selected object or open a new OQL query targeting that specific value.

This is available automatically for any OQL query whose projected columns resolve to heap objects.

Map processing and flattening

Maps such as java.util.HashMap can be expanded using [0:-1], which returns Map.Entry items accessible via getKey() and getValue().

List each map alongside a nested collection of its key-value pairs:

SELECT h AS map,
  (SELECT e.getKey() AS key, e.getValue() AS value FROM OBJECTS ${h}[0:-1] e) AS kv
FROM java.util.HashMap h
WHERE (h[0:-1].size() > 0)

Flattening

When a sub-SELECT column contains a list or array, OQL auto-flattens it: each element of the array becomes its own row in the outer result.

Split each ThreadGroup into one row per member thread:

SELECT group AS Group, thread AS Thread
FROM OBJECTS (
  SELECT t AS group, t.threads[0:-1] AS thread FROM java.lang.ThreadGroup t
)
WHERE ((thread != null) AND (thread.group != group))
GROUP BY simulation

OQL has no native GROUP BY, but the same result can be achieved by combining SELECT DISTINCT (to produce unique group keys) with a correlated sub-SELECT (to collect the matching rows for each key).

Group HashMap instances by their entry count:

SELECT s.sz AS Size,
  (SELECT OBJECTS m FROM java.util.HashMap m WHERE (m[0:-1].size() = s.sz)) AS Maps
FROM OBJECTS ( SELECT DISTINCT h[0:-1].size() AS sz FROM java.util.HashMap h ) s

Group all objects by their inbound reference count:

SELECT s.sz AS Size,
  (SELECT OBJECTS m FROM INSTANCEOF java.lang.Object m WHERE (inbounds(m).@length = s.sz)) AS Objects
FROM OBJECTS ( SELECT DISTINCT inbounds(h).@length AS sz FROM INSTANCEOF java.lang.Object h ) s

For native GROUP BY, use the MAT Calcite Plugin.

COUNT simulation

Counts are obtained via the @length attribute on the array returned by a sub-SELECT, or via .size() on a collection.

Count HashMap instances grouped by size, showing both methods:

SELECT z.size AS Size,
  z.maps AS Maps,
  z.maps.@length AS Count,
  z.maps[0:-1].size() AS "Count (another way)"
FROM OBJECTS ( eval((
  SELECT s.sz AS size,
    (SELECT OBJECTS m FROM java.util.HashMap m WHERE (m[0:-1].size() = s.sz)) AS maps
  FROM OBJECTS ( SELECT DISTINCT h[0:-1].size() AS sz FROM java.util.HashMap h ) s
)) ) z

For native COUNT(), use the MAT Calcite Plugin.

JOIN operations

OQL has no JOIN keyword. The following patterns simulate standard SQL join semantics using nested SELECTs. For native JOIN support see the MAT Calcite Plugin.

CROSS JOIN

Every combination of rows from two result sets:

SELECT z.i AS Integer, z.i.value AS "Integer value", z.lv.l AS Long, z.lv.l.value AS "Long value"
FROM OBJECTS (
  SELECT i, (SELECT l FROM java.lang.Long l) AS lv FROM java.lang.Integer i
) z

LEFT OUTER JOIN

Every row from the left set, with matching rows from the right (null when there is no match):

SELECT z.i AS Integer, z.i.value AS "Integer value", z.lv.l AS Long, z.lv.l.value AS "Long value"
FROM OBJECTS (
  SELECT i, (SELECT l FROM java.lang.Long l WHERE (l.value = i.value)) AS lv
  FROM java.lang.Integer i
) z

INNER JOIN

Only rows where both sides match:

SELECT z.i AS Integer, z.i.value AS "Integer value", z.lv.l AS Long, z.lv.l.value AS "Long value"
FROM OBJECTS (
  SELECT i, (SELECT l FROM java.lang.Long l WHERE (l.value = i.value)) AS lv
  FROM java.lang.Integer i
) z
WHERE (z.lv != null)

RIGHT OUTER JOIN

Every row from the right set, with matching rows from the left:

SELECT z.iv.i AS Integer, z.iv.i.value AS "Integer value", z.l AS Long, z.l.value AS "Long value"
FROM OBJECTS (
  SELECT (SELECT i FROM java.lang.Integer i WHERE (i.value = l.value)) AS iv, l
  FROM java.lang.Long l
) z

FULL OUTER JOIN

All rows from both sides, with null where there is no match. Implemented as a LEFT OUTER JOIN UNION a filtered RIGHT OUTER JOIN:

SELECT z.i AS Integer, z.i.value AS "Integer value", z.lv.l AS Long, z.lv.l.value AS "Long value"
FROM OBJECTS (
  SELECT i, (SELECT l FROM java.lang.Long l WHERE (l.value = i.value)) AS lv
  FROM java.lang.Integer i
) z
UNION (
  SELECT z.iv.i AS Integer, z.iv.i.value AS "Integer value", z.l AS Long, z.l.value AS "Long value"
  FROM OBJECTS (
    SELECT (SELECT i FROM java.lang.Integer i WHERE (i.value = l.value)) AS iv, l
    FROM java.lang.Long l
  ) z
  WHERE (z.iv != null)
)

Practical Examples

Finding unreachable objects of a specific type

Objects unreachable from GC roots still appear in the heap dump when MAT is run with -keep_unreachable_objects. These queries use root type 2048 (Unreachable) to isolate them.

Inspect all GC roots:

SELECT OBJECTS r FROM OBJECTS ${snapshot}.getGCRoots() r

Examine root info for a specific object ID:

SELECT t, t.@type FROM OBJECTS ${snapshot}.getGCRootInfo(21800) t

Select only the Unreachable roots (type = 2048):

SELECT OBJECTS r FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null

Get the full retained set of those roots:

SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null

Filter the retained set to ArrayList only:

SELECT * FROM java.util.ArrayList o WHERE o IN
(SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
 WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null)

Alternatively, wrap the retained set and filter by class name string:

SELECT * FROM OBJECTS (
  SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
  WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null
) u
WHERE u.@clazz.@name = "java.util.ArrayList"

Filter to any AbstractCollection subclass using INSTANCEOF:

SELECT * FROM INSTANCEOF java.util.AbstractCollection o WHERE o IN
(SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
 WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null)

Or equivalently, using doesExtend on the wrapped result:

SELECT * FROM OBJECTS (
  SELECT AS RETAINED SET * FROM OBJECTS ${snapshot}.getGCRoots() r
  WHERE (SELECT * FROM OBJECTS ${snapshot}.getGCRootInfo(r) t WHERE t.@type = 2048) != null
) u
WHERE u.@clazz.doesExtend("java.util.AbstractCollection")
Extracting thread stack frames and locals

List every thread with each stack frame as a separate row:

SELECT u.Thread AS Thread, u.Frame.@text AS Frame
FROM OBJECTS (
  SELECT t AS Thread, ${snapshot}.getThreadStack(t.@objectId).@stackFrames AS Frame
  FROM java.lang.Thread t
) u

Include local variables at each frame (one row per local per frame):

SELECT v.Thread AS Thread, toString(v.Thread) AS Name, v.Frame AS Frame,
  ${snapshot}.getObject(v.Objs) AS Local
FROM OBJECTS (
  SELECT u.Thread AS Thread, u.Frame.@text AS Frame, u.Frame.@localObjectsIds AS Objs
  FROM OBJECTS (
    SELECT t AS Thread, ${snapshot}.getThreadStack(t.@objectId).@stackFrames AS Frame
    FROM java.lang.Thread t
  ) u
) v
WHERE (v.Objs != null)
Displaying all fields of matched objects

List every field name and value for all instances of classes matching a pattern. The @fields attribute returns an array of field descriptors; flattening produces one row per field.

SELECT t.s AS Object,
  toHex(t.s.@objectAddress) AS "Object address",
  t.f.@name AS "Field name",
  t.f.@value AS "Field value"
FROM OBJECTS (
  SELECT s, s.@fields AS f FROM "java.util\\..*" s
  WHERE (s implements org.eclipse.mat.snapshot.model.IInstance)
) t

Related Tools

  • MAT Calcite Plugin — Adds a SQL query window to Eclipse MAT using Apache Calcite. Supports native JOINs, GROUP BY, COUNT/SUM/AVG, ORDER BY, LIMIT/OFFSET, LATERAL TABLE for collection unnesting, and heap-specific functions such as retainedSize(), shallowSize(), getMapEntries(), and asArray(). Install via Eclipse's Install New Software dialog.

  • Eclipse JIFA — Hosts the MAT engine as a service for collaborative heap analysis.

Clone this wiki locally