Java instrumentation fundamentals(Part 1): Find size of Object using instrumentation

Java instrumentation was introduced in java 5 and it gives developer flexibility to get handle of bytecode of a class loaded by JVM(classloader). Once we get hold of bytecode, it can be manipulated using library like Apache BCEl, ASM, etc. and returned back to JVM. It can summarized as - get access of byte code , append custom code lines at run time and returned modified bytecode to JVM so that client will not be able to know whether byte code has been modified.
Instrumentaion finds its uses in various tools like monitoring agents, profilers, coverage analyzers, and event loggers.
In this post we will get overview of Java Instrumentation and does a simple application of it - How to find size of object and later discuss constraint associated in finding object size (What happens if object contains reference of another object).

Lets start with fundamental of Java Instrumentation - According to Java doc, there are two ways to obtain an instance of the Instrumentation interface:

1. Loading agent class via Command-Line Interface :- When JVM is launched in a way that indicates an agent class and in that case an Instrumentation instance is passed to the premain method of the agent class.
Using command-line interface with option to the command-line:
-javaagent:jarpath[=options] where jarpath is the path to the agent JAR file and options is the agent options. This switch may be used multiple times on the same command-line, thus creating multiple agents.
2. Lazy loading of Agent class (Starting Agents After VM Startup):- When a JVM provides a mechanism to start agents sometime after the JVM is launched. In that case an Instrumentation instance is passed to the agentmain method of the agent code.

Agent class and its methods(premain and agentmain):-
An agent 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. The manifest of the agent JAR file must contain the attribute Premain-Class or Agent-Class depending on implementation which we are flowing to obtain instrumentation instance.
Agent class is entry point of instrumentation, when an instance of JVM is created it first looks for Agent class and system class loader loads this agent class. Based on the option 1 or 2 discussed above premain or agentmain is executed and handle of Instrumentation object is passed to these method.
Signatures of the premain method :
public static void premain(String agentArgs, Instrumentation instObj){ }
public static void premain(String agentArgs){ }
Signatures of the agentmain method :
public static void agentmain(String agentArgs, Instrumentation instObj){ }
public static void agentmain(String agentArgs) { }
When the agent is started using a command-line option (with -javaagent), the agentmain method is not invoked and When the agent is started after JVM startup the premain method is not invoked, agentmain is invoked. Here premain/agentmain method is like main method of any class in java and agentArgs has similar characteristics of "String[] args" of  main method. 

Manifest – manifest.mf file :- Following discussion is using approach 1(Loading agent class via Command-Line Interface).
We know that JVM should be informed about Agent classs that it can be loaded by system class loader. We inform JVM about agent class - using “premain-class” property in manifest.mf file. While preparing jar file we include premain-class property with fully classified name of Agent class in manifest.mf. Sample manifest file with premain-class property :
Manifest-Version: 1.0
Build-Jdk: 1.7.0_25
premain-class:  com.devinline.instrumentation.AgentClassName

These are the building blocks which are required to get instance of Instrumentation instance from JVM. Now we will use the instrumentation instance received from JVM to find size of object.

Note
:- In next post we will see how we can use Instrumentation instance for byte code instrumentation and understand extensive flexibility provided by this instrumentation instance using javaassist.

Create a class "InstrumentationAgent.java" and copy following code lines in it.(Adjust package name accordingly)
package com.devinline.instrumentation;

import java.lang.instrument.Instrumentation;
import java.lang.instrument.UnmodifiableClassException;

public class InstrumentationAgent {
 /*
  * System classloader after loading the Agent Class, invokes the premain
  * (premain is roughly equal to main method for normal Java classes)
  */
 private static volatile Instrumentation instrumentation;

 public static void premain(String agentArgs, Instrumentation instObj) {
  // instObj is handle passed by JVM
  instrumentation = instObj;
 }

 public static void agentmain(String agentArgs, Instrumentation instObj)
   throws ClassNotFoundException, UnmodifiableClassException {

 }

 public static long findSizeOfObject(Object obj) {
  // use instrumentation to find size of object obj
  if (instrumentation == null) {
   throw new IllegalStateException("Agent not initialised");
  } else {
   return instrumentation.getObjectSize(obj);
  }
 }
}
Create another class "TestInstrumentation.java" for finding size of object of class "SampleClass" having int, String and Long data types.
package com.devinline.client;

import com.devinline.instrumentation.InstrumentationAgent;

public class TestInstrumentation {

 public static void main(String[] args) {
  long sizeOfObject = InstrumentationAgent
    .findSizeOfObject(new SampleClass(12, "Hello", (long) 2345));
  System.out.println("Size of object is " + sizeOfObject);
 }
}

class SampleClass {
 int number;//4 bytes 
 String name; //  12 bytes (char value[]; 4 bytes int offset; // 4 bytes int count; // 4 bytes)
 Long ssn; //8 bytes

 public SampleClass() {
 }

 public SampleClass(int number, String name, Long ssn) {
  super();
  this.number = number;
  this.name = name;
  this.ssn = ssn;
 }
}
Create "manifest.txt" in bin directory and make an entry in this file (Add property "premain-class")
premain-class: com.devinline.instrumentation.InstrumentationAgent

Jar(javaAgent) file creation:-
Create jar file with these class files and manifest.txt using following command.(Execute follwoing command from bin directory)
 C:\JavaInstrumentationPart1\bin> jar -cmf manifest.txt agent.jar com
Above command create agent.jar and MANIFEST.MF is updated with property "premain-class".

Execute agent.jar with following command
> java -javaagent:agent.jar -cp . com.devinline.client.TestInstrumentation
Size of object is 24

Limitation of finding size of Object using above approach:-
If Object contains reference of another Object then inner object size cannot be computed, it will be treated just as reference.

How to find size of object which contains reference of another object ?

Using classmexer (a utility developed using instrumentation)- a wrapper over InstrumentationAgent.findSizeOfObject().

In next post, we will explore instrumentation concept and see how byte code can be manipulated using it - how to use ClassFileTransformer interface and create Transformer class to register with instrumentation agent.

1 Comments

Previous Post Next Post