Object Query Language (OQL) in Memory Analyzer

Object Query Language (OQL) is used for advanced heap dump analysis by seasoned developers to identify memory problems. In this post let’s learn: What is OQL? How to execute OQL statements? What are its functions and more. 

What is OQL?

OQL (Object Query Language) is a specialized query language that is used to extract and analyze objects in a Java heap dump. Similar to how SQL (Structured Query Language) retrieves data from a database, OQL retrieves information about objects, such as their fields, values, references, sizes, and relationships. It’s useful for diagnosing memory leaks, identifying large object graphs, and exploring how objects are interconnected in the JVM.

How to execute OQL?

Below are the steps to execute OQL in the Heap Dump Analysis Tool HeapHero:

  1. Upload the Heap Dump file to the HeapHero.
  2. Once the tool parses the Heap Dump and generates a memory analysis report, navigate to the ‘Object Query Language (OQL)’ section at the bottom of the report.
  3. In this section enter the OQL String that you want to execute. Example: ‘SELECT * FROM java.lang.String’
  4. The tool will execute the query and render the results in a new page. From this point you can execute the new SQL queries on this new page and see the results in the same page. 

Fig: Executing Object Query Language in HeapHero

How to use the SELECT clause in OQL?

SELECT is the foundational keyword in OQL. It works in the exact same manner as in SQL. It will facilitate you to select objects from the heap dump. Example:

SELECT * FROM java.lang.String

SELECT clause in OQL can be invoked in the following formats as well:

SELECTDescription & Example
Specific FieldsInstead of returning the entire object, you can choose to display only selected fields. This is useful when you’re interested in specific attributes like a counter, value, or identifier. Example: SELECT s.id, s.status FROM com.example.Order s
Built-in AttributesOQL provides built-in attributes like @usedHeapSize and @retainedHeapSize that reveal memory usage at the object level. Example: SELECT s.id, s.@usedHeapSize, s.@retainedHeapSize FROM com.example.Order s
Renaming ColumnsYou can assign custom names to the output columns using the AS keyword. This improves readability, especially when presenting results to others. Example: SELECT s.id AS OrderID, s.status AS Status FROM com.example.Order s
Retained SetUse AS RETAINED SET to get the set of objects retained by your selection. This is helpful when you’re exploring memory retention chains. Example: SELECT AS RETAINED SET * FROM com.example.Order
OBJECTSSome OQL functions like dominators(), inbounds(), or outbounds() return arrays or object lists. Use the OBJECTS keyword to flatten these lists into a single sequence of individual objects. Without OBJECTS, each row of the result may contain an entire array. With OBJECTS, you get a clean, row-per-object output, enabling better readability and filtering. Example: SELECT OBJECTS dominators(s) FROM com.example.Order s. This flattens the list of objects dominated by each Order object, showing each dominated object as its own row.
DISTINCTDISTINCT filters out repeated objects based on identity, not content. If the same object appears more than once in the query result, only one copy will be returned. This avoids clutter and improves clarity, especially in analyses involving shared references or dominator trees.  Example: SELECT DISTINCT * FROM OBJECTS 10, 20, 20, 30. This returns only objects with IDs 10, 20, and 30 once, even though 20 appeared twice in the input.
DISTINCT OBJECTSDISTINCT OBJECTS removes duplicate entries from a result set after applying a transformation function like classof(). This is useful when you’re interested in unique results post-transformation. For instance, multiple objects may belong to the same class, so classof() might return the same class reference repeatedly. Example: SELECT DISTINCT OBJECTS classof(s) FROM com.example.Order s This returns the unique classes of all Order objects. Even if multiple Order objects belong to the same class, only one class will appear in the result.
ExpressionsOQL supports inline computations in the SELECT clause. You can multiply values, add constants, or concatenate strings to existing fields. This is useful for creating derived fields or readable labels in the output.  Example: SELECT s.@objectId, s.@objectId * 2, “Order ID: ” + @objectId FROM OBJECTS 10,20,20,30 s This returns:  – The original object ID  – A computed field that doubles the object ID  – A human-readable label string like “Order ID: 20”

How to use FROM clause in OQL?

FROM clause in OQL lets you specify the classes or objects from which data should be retrieved. Say suppose you want to find the contents of all strings in the heap dump, you can issue the command:

SELECT * FROM java.lang.String

FROM clause in OQL can be invoked in the following formats as well:

FROMDescription & Example
Class NameUse this form when you want to extract all objects of a specific class from the heap. It’s helpful when analyzing commonly used data types like String, HashMap, or custom application classes.  Example: SELECT * FROM java.lang.String
Class Name (Regex)When you’re not sure of the full class name or you want to analyze all classes under a certain package, regex can help.  Example: SELECT * FROM “com\.example\..*”
Object AddressIf you have identified a memory address (e.g., from the dominator tree or another visualization), you can directly query that object.  Example: SELECT * FROM 0x10000000
Object IDEach object in the heap has a unique numeric identifier. If you’re debugging from a previous analysis and want to inspect an object by its ID, this query lets you go straight to it. Example: SELECT * FROM 12345
Sub-SelectThis pattern allows you to dynamically build a result set using conditions. It’s useful when you want to filter objects based on their type hierarchy or fields.  Example: SELECT * FROM (SELECT * FROM java.lang.Class c WHERE c.classLoader != null)
INSTANCEOF Use this when you’re interested not just in a single class but in its entire inheritance tree. This includes subclasses as well.  Example: SELECT * FROM INSTANCEOF java.util.Collection
Class OBJECTSometimes, you want to examine the class definition itself, not its instances. The OBJECTS keyword lets you retrieve the class metadata object.  Example: SELECT * FROM OBJECTS java.lang.String
Specific OBJECTSUsing OBJECTS with an address or ID lets you retrieve exact object instances. Ideal for investigating memory issues at the object level.  Example: SELECT * FROM OBJECTS 0x10000000
Sub-Select OBJECTSAllows feeding a sub-query into OBJECTS for dynamic exploration. Useful for working with nested fields or derived object lists.  Example: SELECT v, v.@length FROM OBJECTS (SELECT OBJECTS s.value FROM java.lang.String s) v

How to use WHERE clause in OQL?

The WHERE clause in OQL operates in a similar manner to WHERE clause in SQL. It facilitates you to filter the objects returned by the query based on specific conditions such as comparisons, logical operators, and object attributes. Example: 

SELECT * FROM com.example.Order o WHERE o.@amount > 1000

This OQL retrieves only the ‘order’ objects within the heap, whose ‘amount’ is greater than 1000.

WHERE clause in OQL can be invoked in the following formats as well:

TypeDescription & Example
Condition FilteringUse WHERE to filter objects based on field values. This helps you narrow down results to only those that meet specific conditions. Example: SELECT * FROM com.example.User u WHERE u.age >= 18. This filters all User objects and returns only those where the age is 18 or older. 
LIKEThe LIKE operator allows regex-style pattern matching. Use it to filter text fields like names, descriptions, or class names. Example: SELECT * FROM com.example.User u WHERE toString(u.name) LIKE “.*admin”. This uses regex-like matching to find all users whose name ends with “admin”. Since u.name might be an object, toString(u.name) converts it to a string for pattern matching.
IN / NOT INUse IN or NOT IN to check whether a value exists in a list or another query result. This is useful for containment logic. Example: SELECT * FROM com.example.User u WHERE u.id NOT IN dominators(u). This finds users whose IDs are not in the list of objects dominating them. It’s often used to detect root-like objects or remove self-referencing entries.
IMPLEMENTSUse IMPLEMENTS to filter classes or objects that implement a specific interface. Useful when analyzing class hierarchies. Example: SELECT * FROM java.lang.Class c WHERE c IMPLEMENTS com.example.MyInterface. This selects all Class objects that implement the MyInterface interface. It’s helpful when you’re trying to find all classes following a particular contract or behavior.
Equality / InequalityYou can filter based on exact matches or non-matches using = and != operators. Example: SELECT * FROM com.example.User u WHERE u.status = “ACTIVE”. This retrieves all users whose status field equals “ACTIVE”. It’s a straightforward equality filter, commonly used for status or type fields.
ANDUse AND to apply multiple conditions. All conditions must be true for the object to match. Example: SELECT * FROM com.example.User u WHERE u.age > 18 AND u.country = “US”. Both conditions must be satisfied: users must be over 18 and from the US. This narrows down the result more precisely than using a single condition.
ORUse OR to include objects that satisfy at least one of multiple conditions. Example: SELECT * FROM com.example.User u WHERE u.age > 60 OR u.vip = true. This query returns users who are either older than 60 or marked as VIP. It’s inclusive—if either condition is true, the user will be included.
Literal Value OQL supports a variety of literal types including Boolean, String, Integer, Long (L), Float (F), Double (D), Character, and null. These literals can be used in WHERE conditions to form precise filters, including nested field access and complex comparisons.  Example: SELECT * FROM com.example.Order o  WHERE (o.total > 1000) = true     OR o.status = “DELIVERED”     OR dominators(o).size() = 0     OR o.@retainedHeapSize > 2048L     OR o.customer != null AND o.customer.name.get(0) = ‘A’
This is a rich example using various literal values and complex logic: (o.total > 1000) = true: Evaluates a boolean condition. o.status = “DELIVERED”: Checks a string value. dominators(o).size() = 0: Calls a method and checks numeric result. o.@retainedHeapSize > 2048L: Uses a long literal to compare retained heap size. o.customer != null AND o.customer.name.get(0) = ‘A’: Combines null checks and character comparison.

What is the UNION Clause in OQL?

The UNION clause in OQL works similarly to the UNION clause in SQL. It combines the results of two OQL queries into a single result set. This is especially useful when you are trying to bring together different object types that share a common set of attributes. You just need to make sure that both queries must return the same number of columns with compatible structures. Example:

SELECT b, b.name, b.age, "Boy" 

FROM com.example.Boy b 

UNION 

(SELECT g, g.name, g.age, "Girl" 

FROM com.example.Girl g)

In this example, we are merging two different objects: ‘Boy’ and ‘Girl’ to create one unified result. The following details are shown in each row:

  • Object reference (b or g)
  • Person’s name
  • Age
  • Label indicating gender.

Sometimes two classes can have the same kind of data but in different fields. In such circumstances you can use the AS keyword to standardize the column names & render the results. Example: 

SELECT b, b.firstName AS name, b.years AS age, "Boy" AS gender 

FROM com.example.Boy b 

UNION 

(SELECT g, g.name AS name, g.age AS age, "Girl" AS gender 

FROM com.example.Girl g)

In this example we are combining:

  • ‘firstName’ and ‘years’ member variables from the ‘Boy’ object 
  •  ‘name’ and ‘age’ member variables from the ‘Girl’ object

The AS keyword makes both queries output columns with the same names: name, age, gender. 

Accessing Array and Collection Elements in OQL

Memory leaks primarily happen in Java Collection data structures like ArrayList, HashMap… OQL gives you the flexibility to investigate & navigate through these data structures using intuitive index-based, range-based access syntax. 

With OQL, you can extract a single element, a subrange of an array, or even walk through Java Lists and Maps, just like you would in regular Java code. The syntax supports direct access ([index]), subranges ([start:end]), and method-style retrieval (get(index)). This enables you to do precise slicing and filtering of memory structures.

Below table shows the different ways to access elements from arrays and collections:

TypeDescription & Example
Primitive ArrayAlternative to index notation that works in all versions. Uses getValueAt() to access primitive array values.  Example: SELECT s.getValueAt(2) FROM int[] s WHERE (s.@length > 2) fetches the 3rd element if the array has more than 2 elements.
Object ArrayAccesses a specific object from an object array using index notation. Works in Memory Analyzer 1.3 or later.  Example: SELECT s[2] FROM java.lang.Object[] s WHERE (s.@length > 2) accesses the 3rd object if array length is greater than 2.
CollectionAccesses first element from Java collection like ArrayList.  Example: SELECT a[0] FROM java.util.ArrayList a retrieves element at index 0.
Collection SubrangeUses [0:-1] to convert collection to list and fetch all elements.  Example: SELECT a[0:-1] FROM java.util.ArrayList a  gets all entries as list.
HashMapAccesses key and value from a HashMap entry using index notation.  Example: SELECT h[0].@key, h[0].@value FROM java.util.HashMap h fetches first key-value pair.

OQL Built-In Functions

OQL provides built-in functions that will help you to extract insights and navigate object relationships directly from the heap dump queries. Each function takes a parameter (usually an object) and returns useful metadata & navigational information. Those functions are:

FunctionDescription
toHex(number)Converts a numeric value to its hexadecimal representation. This is useful for displaying object IDs or memory addresses in a readable format.  Example: SELECT toHex(u.@objectId) FROM com.example.User u. This query returns the hexadecimal version of each user’s object ID.
toString(object)Returns the string form of the given object. Often used to convert values into human-readable form.  Example: SELECT toString(u.name) FROM com.example.User u. This retrieves the ‘name’ field from each user and converts it to a String.
dominators(object)Retrieves a list of objects that are immediately dominated by the given object, helping identify memory retention chains.  Example: SELECT dominators(u) FROM com.example.User u. This fetches all objects retained because of the user object.
outbounds(object)Returns all objects that the given object directly references. Useful to explore dependencies and outbound relationships.  Example: SELECT outbounds(u) FROM com.example.User u. This returns all objects that are referenced by the user object.
inbounds(object)Returns all objects that reference the given object. Helps explain why an object is still in memory.  Example: SELECT inbounds(u) FROM com.example.User u. This shows which other objects hold a reference to the user.
classof(object)Returns the class metadata for the given object. Handy when checking object types in mixed collections.  Example: SELECT classof(u) FROM com.example.User u. This query fetches the Class instance representing the type of each user.
dominatorof(object)Finds the immediate dominator of the object in the heap graph. If none exists, it returns -1.  Example: SELECT dominatorof(u) FROM com.example.User u. This finds the single object that retains the user object in memory.
eval(expression)Dynamically evaluates and returns the result of an expression. Supports operations like string concatenation.  Example: SELECT eval(“User: ” + u.name) FROM com.example.User u. This creates a custom string for each user, e.g., ‘User: Alice’.

Reading Heap Metadata Attributes

OQL allows you to query internal metadata about objects stored in the heap. These attributes aren’t part of your application code, instead they’re exposed by the HeapHero tool to give you a deeper view on how objects are managed in the memory.

You can access these built-in metadata properties using the @ symbol followed by the attribute name. They help to inspect critical metadata such as:

  • Object memory addresses
  • Object IDs
  • Class information
  • Heap usage (shallow and retained sizes)
  • Reference relationships (inbound and outbound)

These attributes can be helpful when you try to investigate memory leaks, retained objects, or GC root paths. Example: 

SELECT u.@retainedHeapSize 

FROM com.example.User u 

WHERE u.@retainedHeapSize > 1024

This query retrieves the ‘@retainedHeapSize’ property for each ‘User’ object in the heap and filters out only those objects that retain more than ‘1024’ bytes of memory. The ‘@retainedHeapSize’ is not a user-defined field in the ‘User’ object, but an internal property provided by the HeapHero tool. It shows the total memory that would be freed when this object is garbage collected, it will include the size of this object and all the child objects that it is keeping alive. This makes it especially helpful for spotting large objects that are holding onto significant portions of memory. 

The table below highlights other metadata attributes available for querying on the object:

Attribute AccessDescription
@objectIdReturns the unique ID of the object in the snapshot. Useful for direct reference or comparisons. Example: SELECT u.@objectId FROM com.example.User u
@objectAddressReturns the memory address of the object in the heap. Often used with toHex() for better readability. Example: SELECT toHex(u.@objectAddress) FROM com.example.User u
@classReturns the Java class of the object as a Memory Analyzer representation. Example: SELECT u.@class FROM com.example.User u
@clazzReturns the IClass interface of the object, providing more detailed class metadata. Example: SELECT u.@clazz FROM com.example.User u
@outboundReferencesReturns all outbound references from the object. Each entry contains the referenced field or array entry. Example: SELECT u.@outboundReferences FROM com.example.User u
@usedHeapSizeReturns the shallow heap size used by the object. Example: SELECT u.@usedHeapSize FROM com.example.User u
@retainedHeapSizeReturns the retained heap size of the object. Example: SELECT u.@retainedHeapSize FROM com.example.User u
@technicalNameReturns a string combining class name and object address for technical identification. Example: SELECT u.@technicalName FROM com.example.User u
@classSpecificNameReturns a readable name for the object (e.g., actual value if it’s a String or similar). Example: SELECT u.@classSpecificName FROM com.example.User u
@displayNameReturns a human-readable name combining technical and class-specific names. Example: SELECT u.@displayName FROM com.example.User u
@snapshotReturns the snapshot object containing this object. Useful for chaining to methods like getClasses(). Example: SELECT u.@snapshot.getClasses() FROM com.example.User u
@lengthReturns the length of the array. Applies to any array object. Example: SELECT arr.@length FROM com.example.User[] arr
@valueArrayReturns the contents of a primitive array. Example: SELECT arr.@valueArray FROM int[] arr
@referenceArrayReturns addresses of objects in a reference array. Example: SELECT arr.@referenceArray FROM com.example.User[] arr
@definedClassesReturns all classes defined by a class loader. Example: SELECT cl.@definedClasses FROM org.example.MyClassLoader cl
@objectReturns the actual object pointed to by a reference. Example: SELECT ref.@object FROM com.example.ReferenceWrapper ref
@nameReturns the field name or array index as a string for a named reference. Useful for understanding the source of a reference (e.g., field name or array slot). Example: SELECT r.@name FROM com.example.NamedReference r

Invoking Heap Analysis Methods

HeapHero also exposes special helper methods to inspect and manipulate objects, arrays, classes, and the snapshot itself. These aren’t part of your application code, but they’re built into the analysis tool.

When you use these methods, they run inside HeapHero (not in the original JVM that created the heap dump). To call them, simply use the @ symbol followed by the method name and parentheses. Example:

SELECT u.@getObjectAddress() FROM com.example.User u

Above OQL, will invoke the ‘getObjectAddress()’ method in every ‘User’ object in the heap and returns the memory address where the object resides in the snapshot. This makes it easier to trace objects or cross-reference them based on their memory location. 

The table below highlights some commonly used helper methods:

MethodDescription
getClasses()Returns a collection of all class objects available in the heap. Useful for analyzing what classes are loaded.  Example: SELECT getClasses() FROM OBJECTS ${snapshot}
${snapshot}.getClassesByName(“java.lang.String”, true)Returns a collection of classes matching the given name. Helps you find class metadata by name.  Example: SELECT ${snapshot}.getClassesByName(“java.lang.String”, false)
c.@hasSuperClass()Checks if a class object has a superclass. Helps identify base types in inheritance hierarchies.  Example: SELECT c.@hasSuperClass() FROM OBJECTS java.lang.Class c
c.@isArrayType()Checks whether the class represents an array type. Useful when differentiating between object and array types.  Example: SELECT c.@isArrayType() FROM OBJECTS java.lang.Class c
o.@getObjectAddress()Returns the memory address of a heap object as a long integer. Useful for tracking references.  Example: SELECT o.@getObjectAddress() FROM com.example.User o
arr.@getValueAt(0)Returns the value at a given index in a primitive array. Helps inspect array contents.  Example: SELECT a.@getValueAt(2) FROM int[] a
list.@get(0)Fetches an element at a specific index from an array or list. Allows access to array content.  Example: SELECT list.@get(0) FROM OBJECTS java.util.ArrayList list

Conclusion

As we have discussed in this post, OQL brings powerful SQL-like capabilities to help you investigate memory issues in heap dumps. It allows you to filter large sets of objects, analyze collections, and recognize memory retention patterns. With HeapHero’s user-friendly interface, you will be able to run these advanced queries with ease and resolve even complex memory problems more effectively.

Share your Thoughts!

Up ↑

Index

Discover more from HeapHero – Java & Android Heap Dump Analyzer

Subscribe now to keep reading and get access to the full archive.

Continue reading