Get number of objects referenced from ArrayList with size 1 grouped by class

java, oql, visualvm

Solution

There is no `group by` in VisualVM's OQL But you can use the build-in functions to make a JavaScript snippet and run it in the "OQL Console":

var c = {};

/* Filter to show only the first occurence (max count) */
filter(
  /* Sort by occurences desc */
  sort(
    /* Count class instances */
    map(
      heap.objects("java.util.ArrayList"),
      function(list) {
        var clazz = 'null';
        if (list.size = 1 && list.elementData[0] != null) {
          clazz = classof(list.elementData[0]).name;
        }

        c[clazz] = (c[clazz] ? c[clazz] + 1 : 1);
        return { cnt:c[clazz], type:clazz };
      }
    ), 'rhs.cnt - lhs.cnt'
  ),
  function (item) {
    if (c[item.type]) {
      c[item.type] = false;
      return true;
    } else {
      return false;
    }
  }
);

The output is an array of object like:

{
cnt = 3854.0,
type = null
}    
{
cnt = 501.0,
type = org.apache.tomcat.util.digester.CallMethodRule
}
{
cnt = 256.0,
type = java.lang.ref.WeakReference
}
{
cnt = 176.0,
type = sun.reflect.generics.tree.SimpleClassTypeSignature
}

Finally you can call `map` function again to format the output to something else like per exmpale as csv:

map(  
  filter(...),
  'it.type + ", " + it.cnt'
);

output:

null, 3854    
org.apache.tomcat.util.digester.CallMethodRule, 501    
java.lang.ref.WeakReference, 256    
sun.reflect.generics.tree.SimpleClassTypeSignature, 176    
org.apache.tomcat.util.digester.CallParamRule, 144    
com.sun.tools.javac.file.ZipFileIndex$Entry, 141    
org.apache.tomcat.util.digester.ObjectCreateRule, 78

Problem

I've got a heap dump from the application and found out that there's a huge number of ArrayLists with only 1 object in it. I know how to get the list of such arraylists and also show the class of the contained element: ``` SELECT list.elementData[0] FROM java.util.ArrayList list WHERE (list.size = 1) ``` The result looks like this: ``` java.lang.String [id=0x7f8e44970] java.lang.String [id=0x7f8e44980] java.lang.String [id=0x7f8e44572] java.io.File [id=0x7f8e43572] ... ``` What I would like is to get something like this: ``` Class | count ================================= java.lang.String | 100 java.io.File | 74 ... ``` but I'm not able to aggregate the results or do anything else on those. I've found here how to pass the values to outer select, but I can't figure out how to use anything else beside the `*` in the first select. ``` SELECT * from OBJECTS (SELECT OBJECTS list.elementData[0] FROM java.util.ArrayList list WHERE (list.size = 1)) ```

Original source