-
Notifications
You must be signed in to change notification settings - Fork 34
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.
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 — anint[]of object IDs, not the objects themselves. UseSELECT OBJECTSto force heap-object resolution.
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 |
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
| 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 |
The ${snapshot} variable exposes the full
ISnapshot API:
SELECT OBJECTS r FROM OBJECTS ${snapshot}.getGCRoots() r
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() |
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)
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.
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
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
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)
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
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)
)
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
-
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(), andasArray(). Install via Eclipse's Install New Software dialog. -
Eclipse JIFA — Hosts the MAT engine as a service for collaborative heap analysis.