Skip to content
Advertisement

How to assert some Java method is garbage free?

I have a piece of code that (currently) use Java 8’s Optional and Stream. And we started having GC overhead issues since this code was deployed to production. See the gist of the code below (getters, constructors and overloads removed for clarity):

class DynamicFilter extends org.apache.logging.log4j.core.filter.AbstractFilter {

  private Config config;

  @Override
  public Result filter (LogEvent event) {
    Level threshold = config.threshold (event);

    return event.getLevel ().isMoreSpecificThan (threshold) ? onMatch : onMismatch;
  }

}

class Config {

  private Level defaultLevel;

  private List<Overwrite> overwrite;

  public Level threshold (LogEvent log) {
    return overwrites.stream ()
      .filter (o -> o.match (log))
      .findFirst ()
      .map (Overwrite::getThreshold)
      .orElse (defaultLevel);
  }

}

class Overwrite {

  private Level threshold;

  private List<Predicate<LogEvent>> conditions;

  public boolean match (LogEvent log) {
    return conditions.stream ()
      .allMatch (c -> c.test (log));
  }

}

While it could be unrelated, I’d like to make this code garbage free, especially since it’s a code ran inside every log statement. This shouldn’t be very hard using for loops and if statements, but is there a way I can assert I didn’t forgot some nasty object allocation? Eg. an int gets autoboxed?

TLDR: Is there a way for me to write a unit test like:

memory.startRecording ();
myCode.run ();
memory.assertNoCallToNewInCurrentThreadSinceRecordingStarted ();

PS

Disabling this code in production did not solve the GC overhead issues. I keep this question open because I believe being able to assert some piece of code doesn’t do any allocation can be useful to future readers, but I don’t need it at the moment.

Advertisement

Answer

Each thread in JVM maintain allocation counter, counting bytes allocated since thread creation. You can use this counter to measure amount of allocation done by piece of code (though it should run in parent thread).

Below is the snippet of code I usually use for that purpose.

import java.lang.management.ManagementFactory;

public class MemMeter {

    private static long OFFSET = measure(new Runnable() {
        @Override
        public void run() {
        }
    });

    /**
     * @return amount of memory allocated while executing provided {@link Runnable}
     */
    public static long measure(Runnable x) {
       long now = getCurrentThreadAllocatedBytes();
       x.run();
       long diff = getCurrentThreadAllocatedBytes() - now;
       return diff - OFFSET;
    }

    @SuppressWarnings("restriction")
    private static long getCurrentThreadAllocatedBytes() {
        return ((com.sun.management.ThreadMXBean)ManagementFactory.getThreadMXBean()).getThreadAllocatedBytes(Thread.currentThread().getId());
    }
}

Here you can find more details on this topic.

Advertisement