Core Java

Measuring Allocations Programmatically

I picked up this tip from the The Java Specialists’ Newsletter written by Heinz Kabutz.  (For all Java developers wanting to learn what goes on under the covers of the JDK this newsletter is an absolute must!)

Especially for developers writing low latency code but even for normal Java code, allocations is the thing you really want to avoid. See my previous posts ‘The First Rule of Optimisation‘ and ‘Revisiting the First Rule of Performance Optimisation: Effects of Escape Analysis‘ for more details.

Previous to this tip I had always used a profiler to work out my allocations or I suppose you could have used the a call to Runtime to see how much heap memory had been allocated by the JVM.

Using MBeans we are able to query an individual thread for it’s allocations.  This gives us a very precise way of measuring whether a specific thread has allocated and if so how much has it allocated.  In situations where you are coding for zero allocation you can include a call to this code in your tests asserting that there has been no allocation.

Below is a simple class you can use based on the tip from the newsletter.

You’ll notice that the constructor does a calibration which adjusts for the amount of allocation created by the bean itself.

There is also some defensive code to ensure that the class is only called from a single thread.

You can call the method markAllocations to find the amount of bytes that have been allocated since the the last mark. printAllocations is a handy method to print the allocations since the last mark to standard out.  Allocations are rebased after the class is constructed, a call to reset or a call to markAllocations or printAllocations.

In a test you might have code like this:

Allocations measure = new AllocationsMeasure();
...
//critical code
...
assertEquals(0, measure.markAllocations());

Full code for AllocationsMeasure below:

package util;
 
import javax.management.*;
import java.lang.management.ManagementFactory;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
 
/**
 * Created by daniel on 06/07/2015.
 */
public class AllocationMeasure {
 
    private final String GET_THREAD_ALLOCATED_BYTES = "getThreadAllocatedBytes";
    private final String[] SIGNATURE = new String[]{long.class.getName()};
    private final String threadName = Thread.currentThread().getName();
    private final Object[] PARAMS = new Object[]{Thread.currentThread().getId()};
    private MBeanServer mBeanServer;
    private ObjectName name = null;
    private AtomicLong allocated = new AtomicLong();
    private long BYTES_USED_TO_MEASURE = 336;
    private long tid;
 
    public AllocationMeasure(){
        tid = Thread.currentThread().getId();
        try {
            name = new ObjectName(
                    ManagementFactory.THREAD_MXBEAN_NAME);
            mBeanServer = ManagementFactory.getPlatformMBeanServer();
        } catch (MalformedObjectNameException e) {
            e.printStackTrace();
        }
 
        //calibrate
        for (int i = 0; i < 100; i++) {
            //run a few loops to allow for startup anomalies
            markAllocations();
        }
        long callibrate = threadAllocatedBytes();
        BYTES_USED_TO_MEASURE = threadAllocatedBytes()-callibrate;
        reset();
    }
 
    public void reset(){
        if(tid != Thread.currentThread().getId())
            throw new AssertionError("AllocationMeasure must not be used over more than 1 thread.");
        allocated.set(threadAllocatedBytes());
    }
 
    private long threadAllocatedBytes() {
        try {
            return (long)mBeanServer.invoke(
                            name,
                            GET_THREAD_ALLOCATED_BYTES,
                            PARAMS,
                            SIGNATURE
                    );
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }
 
    public long markAllocations() {
        if(tid != Thread.currentThread().getId())
            throw new AssertionError("AllocationMeasure must not be used over more than 1 thread.");
        long mark1 = ((threadAllocatedBytes()-BYTES_USED_TO_MEASURE) - allocated.get());
        allocated.set(threadAllocatedBytes());
        return mark1;
    }
 
    public void printAllocations(CharSequence marker) {
        if(tid != Thread.currentThread().getId())
            throw new AssertionError("AllocationMeasure must not be used over more than 1 thread.");
        long mark1 = ((threadAllocatedBytes()-BYTES_USED_TO_MEASURE) - allocated.get());
        System.out.println(threadName + " allocated " + marker + ":" + mark1);
        allocated.set(threadAllocatedBytes());
    }
 
 
    public static void main(String[] args) {
        String TEST = "Test";
        AllocationMeasure allocationMeasure = new AllocationMeasure();
 
        for (int i = 0; i < 1000; i++) {
            allocationMeasure.reset();
            //allocationMeasure = new AllocationMeasure();
 
            long mark1 = allocationMeasure.markAllocations();
 
 
            if(mark1 >0 )
            System.out.println("m1:" + mark1);
        }
        allocationMeasure.printAllocations(TEST);
    }
}
Reference: Measuring Allocations Programmatically from our JCG partner Daniel Shaya at the Rational Java blog.

Daniel Shaya

Daniel has been programming in Java since it was in beta. Working predominantly in the finance industry he has created real time trading and margin risk applications. He is currently a director at OpenHFT where we are building next generation Java low latency products.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button