Search code examples
javagarbage-collectionjvmallocation

how to measure method allocation rate?


Is there any profiler or tool (jconsole, jfr, VisualVM, ... ) for the JVM which shows methods along their allocation rate?

I mean, something like this

org.package1.Class1.method1, 145k
org.package2.Class3.method5, 34k

Also I am looking for a way to see which methods are being the most interrupted because of allocation pauses.

  • VisualVM, for instance, shows allocation rate by thread, not by method
  • JFR allows to track Garbage Collection Events and does some kind of memory and method profiling. But later, using JMC I am not able to see allocation rate by method. Method profiling is for cpu flame graph.

Maybe it is not possible, or does not have sense. But this is all just for gc learning purposes and I would like to investigate further.


Solution

  • JDK Flight Recorder (JFR) can measure allocation rate per allocation site (method), but it's sample based, so it trades accuracy for overhead. You need a few thousands samples. The overhead of recording the stack trace (or method) for every allocation is enormous and would bring the application to a halt.

    JDK 16 introduced a new event (ObjectAllocationSample) that caps the number of events per second. You should be able to see it in JMC. If not, here is a small program that will give you a histogram of allocation per method.

    import java.io.IOException;
    import java.nio.file.Path;
    import java.util.*;
    import jdk.jfr.consumer.*;
    
    public class AllocationHistogram {
      public static void main(String[] args) throws IOException {
        if (args.length != 1) {
          System.err.println("Must specify a recording file.");
          return;
        }
        Map<String, Long> histogram = new HashMap<>();
        try (var es = EventStream.openFile(Path.of(args[0]))) {
          es.onEvent("jdk.ObjectAllocationSample", e -> {
            RecordedStackTrace s = e.getStackTrace();
            if (s != null) {
              RecordedFrame topFrame = s.getFrames().get(0);
              if (topFrame.isJavaFrame()) {
                RecordedMethod m = topFrame.getMethod();
                String className = m.getType().getName();
                String key = className + "." + m.getName();
                histogram.merge(key, e.getLong("weight"), (a, b) -> a + b);
               }
             }
           });
           es.start();
        }
        var list = new ArrayList<>(histogram.entrySet());
        list.sort(Map.Entry.<String, Long>comparingByValue().reversed());
    
        for (var entry : list) {
          System.out.printf("%12dk %s\n", entry.getValue() /1024, entry.getKey());
        }
      }
    }
    

    Usage:

    $ java -XX:StartFlightRecording:filename=recording.jfr ...
    $ # Stop the program after two minutes (CTRL+C)
    $ java AllocationHistogram.java recording.jfr
    
    16688k java.util.HashMap.newNode 
     1022k java.lang.AbstractStringBuilder.<init>
      511k java.lang.Long.valueOf 
       ... ...