# 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](https://github.com/vlsi/mat-calcite-plugin), which > uses [Apache Calcite](https://calcite.apache.org/docs/reference.html) 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] , ... FROM [INSTANCEOF] [] [WHERE ] ``` | 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](https://help.eclipse.org/latest/index.jsp?topic=%2Forg.eclipse.mat.ui.help%2Freference%2Foqlsyntax.html): ``` 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](https://github.com/vlsi/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](#join-operations) | Native SQL JOIN | | LIMIT / OFFSET | Simulated — see [LIMIT and OFFSET](#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](#group-by-simulation) | Native SQL GROUP BY | | COUNT | Simulated via `@length` — see [COUNT](#count-simulation) | 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](https://github.com/vlsi/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](https://github.com/vlsi/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](https://github.com/vlsi/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](https://github.com/vlsi/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](https://github.com/vlsi/mat-calcite-plugin)** — Adds a SQL query window to Eclipse MAT using [Apache Calcite](https://calcite.apache.org/docs/reference.html). 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](https://eclipse-jifa.github.io/jifa/)** — Hosts the MAT engine as a service for collaborative heap analysis.