Sunday, May 19, 2013

Monitoring Memory Usage inside a Java Web Application with JSF, PrimeFaces and MemoryMXBean

This article explains how to monitor memory usage in your web application by requesting the MemoryMXBean and exposing collected metrics within a Primefaces LineChart component in a JSF page.

Principle

The monitoring of the memory usage involves:

The overall design is illustrated below:



  1. The LineChart component calls every minute the MonitorController instance.
  2. The MonitorController instance requests the MemoryMXBean instance to get the current memory usage.
  3. The MonitorController instance updates a CartesianChartModel instance by adding a memory usage snapshot.
  4. The CartesianChartModel instance is returned to the LineChart component that renders it inside a JSF page.

PrimeFaces LineChart

To enable PrimeFaces components into your web application, update your maven pom.xml with the following repository and dependency:
    
        
            primefaces-maven-repo
            PrimeFaces Maven Repository
            http://repository.primefaces.org
            default
        
    
        
            org.primefaces
            primefaces
            3.4.2
        
Next, inside a JSF page, add the following code to display the LineChart and make it polling the MonitorController every minute:
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core"
      xmlns:p="http://primefaces.org/ui"
      xmlns:c="http://java.sun.com/jsp/jstl/core"
      xmlns:ui="http://java.sun.com/jsf/facelets">

    <h:head>
       ...
    </h:head>

    <h:body>
       ...
                <h:form id="form">  

                    <!-- polls every minute -->
                    <p:poll interval="60" update="memoryChart" />  

                    <!-- memory line chart -->
                    <p:lineChart id="memoryChart" value="#{monitorController.memoryModel}"
                                 legendPosition="ne" title="Memory Usage" style="height:300px;margin-top:20px"
                                 xaxisLabel="Minutes" yaxisLabel="Bytes" zoom="true"/>  

                </h:form>  
       ...

    </h:body>
</html>


MonitorController

The code corresponding to the monitor controller class is:
package org.javabenchmark;

import java.io.Serializable;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.SessionScoped;
import org.primefaces.model.chart.CartesianChartModel;
import org.primefaces.model.chart.ChartSeries;
import org.slf4j.LoggerFactory;

/**
 * JSF Managed Bean to get JVM memory usage.
 *
 */
@ManagedBean
@SessionScoped
public class MonitorController implements Serializable {

    /**
     * memory model.
     */
    private CartesianChartModel memoryModel;
    /**
     * memory usage series.
     */
    private ChartSeries memoryUsageSerie, maxMemorySerie;
    /**
     * elapsed time in minutes since the monitoring starts
     */
    private long elapsedTime = -1;

    /**
     * instantiates a new monitoring controller.
     */
    public MonitorController() {
        
        createMemoryModel();
    }
    
    /**
     * initializes memory usage model.
     */
    private void createMemoryModel() {  
        // model
        memoryModel = new CartesianChartModel(); 
        // heap serie
        memoryUsageSerie = new ChartSeries();  
        memoryUsageSerie.setLabel("Heap");
        memoryModel.addSeries(memoryUsageSerie);
        // max serie
        maxMemorySerie = new ChartSeries();  
        maxMemorySerie.setLabel("Max");
        memoryModel.addSeries(maxMemorySerie); 
    }
    
    /**
     * gets the memory model that will be rendered within a LineChart
     * component. The LineChart component should call this method every minute.
     * @return the updated memory model
     */
    public CartesianChartModel getMemoryModel() {  
        
        // gets data
        MemoryMXBean memoryMxBean = ManagementFactory.getMemoryMXBean();
        MemoryUsage memoryUsage = memoryMxBean.getHeapMemoryUsage();
        
        // one more minute
        elapsedTime++;
        
        // updates series
        memoryUsageSerie.set(elapsedTime, memoryUsage.getUsed());
        maxMemorySerie.set(elapsedTime, memoryUsage.getMax()); 
        
        return memoryModel;  
    }
}


Screenshot

Finally, a screenshot of the PrimeFaces line chart representing the memory usage :


Notice that it is possible to zoom with drag and drop, if you have a lot of points displayed.

Conclusion

As you can see, it is quite easy to monitor the JVM and to display collected metrics within a JSF page. You can go further by monitoring CPU usage for instance, or by adding some features like starting/stopping/resetting the monitoring.

Sunday, May 5, 2013

Java instrumentation tutorial

This article explains how Java instrumentation works, and how you can write your own agent to do some basic profiling/tracing.

Overview

JVM instrumentation feature was introduced in JDK 1.5 and is based on byte code instrumentation (BCI). Actually, when a class is loaded, you can alter the corresponding byte code to introduce features such as methods execution profiling or event tracing. Most of Java Application Performance Management (APM) solutions use this mechanism to monitor JVM.

Instrumentation Agent

To enable JVM instrumentation,  you have to provide an agent (or more) that is deployed as a JAR file. An attribute in the JAR file manifest specifies the agent class which will be loaded to start the agent.

There is 2 ways to load the agent:
  • with a command-line interfaceby adding this option to the command-line: -javaagent:jarpath[=options] where jarpath is the path to the agent JAR file. options is the agent options. This switch may be used multiple times on the same command-line, thus creating multiple agents. More than one agent may use the same jarpath.
  • by dynamic loading: the JVM must implement a mechanism to start agents sometime after the the VM has started. That way, a tool can "attach" an agent to a running JVM (for instance profilers or ByteMan)


After the JVM has initializedthe agent class will be loaded by the system class loader. If the class loader fails to load the agent, the JVM will abort.



Next, the JVM instantiates an Instrumentation interface implementation and given the context, tries to invoke one of the two methods that an agent must implement: premain or agentmain.

The premain method is invoked after JVM initialization and the agentmain method is invoked sometime after the JVM has started (if the JVM provides such a mechanism). When the agent is started using a command-line option (with -javaagent), the agentmain method is not invokedThe agent class may also have a premain method for use when the agent is started using a command-line option. When the agent is started after JVM startup the premain method is not invoked.




The signatures of the premain method are:
public static void premain(String agentArgs, Instrumentation inst);
public static void premain(String agentArgs);

And the signatures of the agentmain method are:
public static void agentmain(String agentArgs, Instrumentation inst);
public static void agentmain(String agentArgs);
The agent needs to implement only one signature per method.  The JVM first attempts to invoke the first signature, and if the agent class does not implement it then the JVM will attempt to invoke the second signature.

Byte Code Instrumentation

With the premain and agentmain methods, the agent can register a ClassFileTransformer instance by providing an implementation of this interface in order to transform class files. To register the transformer, the agent can use the addTransformer method of the given Instrumentation instance.


Now, all future class definitions will be seen by the transformer, except definitions of classes upon which any registered transformer is dependent. The transformer is called when classes are loaded, when they are redefined. and optionally, when they are retransformed (if the transformer was added to the instrumentation instance with the boolean canRetransform set to true).

The following method of the ClassFileTransformer interface is responsible of any class file transformation:
byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
                 ProtectionDomain protectionDomain, byte[] classfileBuffer);

This is where things get complicated because you need to manipulate raw byte code. To achieve this, i strongly suggest to rely on dedicated tools such as ASM or Javassist.

Basic Profiling

To illustrate how the class file transformer can be used, we are going to set up some basic method profiling with Javassist:
  1. Write a trivial class that will be instrumented
  2. Write a ClassFileTransformer to inject some code to print method execution time
  3. Write an agent that registers the previous transformer
  4. Write the corresponding JUnit test
The class to be instrumented is:
package org.javabenchmark.instrumentation;

public class Sleeping {
    
    public void randomSleep() throws InterruptedException {
        
        // randomly sleeps between 500ms and 1200s
        long randomSleepDuration = (long) (500 + Math.random() * 700);
        System.out.printf("Sleeping for %d ms ..\n", randomSleepDuration);
        Thread.sleep(randomSleepDuration);
    }
}

The transformer class is:
package org.javabenchmark.instrumentation;

import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.IllegalClassFormatException;
import java.security.ProtectionDomain;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

public class SleepingClassFileTransformer implements ClassFileTransformer {

    public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined,
        ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {

        byte[] byteCode = classfileBuffer;

        if (className.equals("org/javabenchmark/instrumentation/Sleeping")) {

            try {
                ClassPool cp = ClassPool.getDefault();
                CtClass cc = cp.get("org.javabenchmark.instrumentation.Sleeping");
                CtMethod m = cc.getDeclaredMethod("randomSleep");
                m.addLocalVariable("elapsedTime", CtClass.longType);
                m.insertBefore("elapsedTime = System.currentTimeMillis();");
                m.insertAfter("{elapsedTime = System.currentTimeMillis() - elapsedTime;"
                        + "System.out.println(\"Method Executed in ms: \" + elapsedTime);}");
                byteCode = cc.toBytecode();
                cc.detach();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        return byteCode;
    }
}

The transformer checks if the class to transform is the Sleeping one, then it injects the code that prints the time elapsed by the execution of the randomSleep() method.

The agent class is:
package org.javabenchmark.instrumentation;

import java.lang.instrument.Instrumentation;

public class Agent {

    public static void premain(String agentArgs, Instrumentation inst) {
        
        // registers the transformer
        inst.addTransformer(new SleepingClassFileTransformer());
    }
}
And finally, the corresponding JUnit code:
package org.javabenchmark.instrumentation;

import org.junit.Test;

public class AgentTest {

    @Test
    public void shouldInstantiateSleepingInstance() throws InterruptedException {

        Sleeping sleeping = new Sleeping();
        sleeping.randomSleep();
    }
}
But, before executing the test you need to setup Maven to enable the JVM agent:
  • build the JAR that contains the agent's code before running test
  • add the JVM option -javagent to the JVM that runs the test
  • add the Javassist dependency
To achieve this, add the following XML code to your pom.xml file, inside the <build><plugins> ... </plugins></build> section:
            
            
                org.apache.maven.plugins
                maven-jar-plugin
                2.4
                
                    
                        process-classes
                        
                            jar
                        
                        
                            
                                
                                    org.javabenchmark.instrumentation.Agent
                                
                            
                        
                    
                
            

            
            
                org.apache.maven.plugins
                maven-surefire-plugin
                2.14
                
                    -javaagent:target/${project.build.finalName}.jar
                
            
Next, add the Javassist dependency:
        
            org.javassist
            javassist
            3.14.0-GA
            jar
        

Then, running the test should produce something like this:
-------------------------------------------------------
 T E S T S
-------------------------------------------------------
Running org.javabenchmark.instrumentation.AgentTest
Sleeping for 818 ms ..
Method Executed in ms: 820
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.001 sec
You can see that there is a new trace: Method Executed in ms: 820 proving that instrumentation works. Without instrumentation, you would only have the Sleeping for 818 ms .. trace.

Conclusion

It is easy to profile your code with the instrumentation API and the Javassist API: write transformers with Javassist, write an agent to register them, then use the -javaagent option and you're done !