<pre>
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.6.0</version>
<!-- This scope is similar to provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository. -->
<scope>system</scope>
<systemPath>${env.JAVA_HOME}/lib/tools.jar</systemPath>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.0</version>
</dependency>
</pre>
<pre>package com.wordpress.ghulal;
import com.sun.tools.attach.VirtualMachine;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sun.tools.attach.HotSpotVirtualMachine;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.management.ManagementFactory;
import java.text.NumberFormat;
import java.text.ParseException;
/**
* Class to profile heap usage of a java program. Works by reverse engineering the jmap utility.
* http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/sun/tools/jmap/JMap.java?av=f
* Created by siddjain on 4/8/16.
*/
public class Jmap
{
private static final Logger LOG = LoggerFactory.getLogger(Jmap.class);
/**
* A flag to ensure the start method of this class is only called once and subsequent invocations cause a noop
*/
private boolean isStarted;
private Jmap()
{
}
/**
* Class to implement proper thread safe lazy instantiation singleton pattern
*/
private static class Holder
{
public static final Jmap instance = new Jmap();
}
private class Task implements Runnable
{
private String pid;
private int lines;
private long milliseconds;
public Task(String processId, long sleepInterval, int numberOfLines)
{
if (processId == null || processId.isEmpty())
{
throw new IllegalArgumentException();
}
if (sleepInterval <= 0)
{
sleepInterval = 10000;
}
if (numberOfLines <= 0)
{
numberOfLines = 100;
}
this.pid = processId;
this.lines = numberOfLines;
this.milliseconds = sleepInterval;
}
/**
* This method should not be called by developer. It is called implicitly by the thread.
*/
public void run()
{
try
{
while (true)
{
try
{
HotSpotVirtualMachine vm = (HotSpotVirtualMachine) VirtualMachine.attach(pid);
try (InputStream is = vm.heapHisto("-live"))
{
drain(vm, is, lines);
}
vm.detach();
}
catch (Exception e)
{
LOG.error("", e);
}
Thread.sleep(milliseconds);
}
}
catch (InterruptedException ex)
{
// http://www.ibm.com/developerworks/library/j-jtp05236/
// If you catch InterruptedException but cannot rethrow it, you should preserve evidence that the
// interruption occurred so that code higher up on the call stack can learn of the interruption and
// respond to it if it wants to. This task is accomplished by calling interrupt() to "reinterrupt" the
// current thread. At the very least, whenever you catch InterruptedException and don't rethrow it,
// reinterrupt the current thread before returning.
Thread.currentThread().interrupt();
}
}
private void drain(VirtualMachine vm, InputStream in, int numLines)
{
try
{
int counter = 0;
try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(in)))
{
String line;
while ((line = bufferedReader.readLine()) != null && counter++ < numLines)
{
LOG.debug(line);
}
}
}
catch (Exception e)
{
LOG.error("", e);
}
}
}
public static Jmap getInstance()
{
return Holder.instance;
}
/**
* Call this method to start instrumenting heap usage using default sampling interval.
*/
public synchronized void start()
{
this.start(10000, 100);
}
/**
* Call this method to start instrumenting heap usage.
* @param intervalMilliseconds the sampling interval between profiling heap usage. units: milliseconds
*/
public synchronized void start(long intervalMilliseconds)
{
this.start(intervalMilliseconds, 100);
}
/**
* Call this method to start instrumenting heap usage.
* @param verbosity controls the number of lines in the output. use this to control verbosity of the output
*/
public synchronized void start(int verbosity)
{
this.start(10000, verbosity);
}
/**
* Call this method to start instrumenting heap usage. This method should only be called once in your program.
* Subsequent calls to the method are ignored. Once started, there is no way to stop the profiler.
* @param intervalMilliseconds the sampling interval between profiling heap usage. units: milliseconds
* @param numberOfLines controls the number of lines in the output. use this to control verbosity of the output.
* @throws Exception
*/
public synchronized void start(final long intervalMilliseconds, final int numberOfLines)
{
if (!this.isStarted)
{
if (intervalMilliseconds <= 0)
{
throw new IllegalArgumentException("intervalMilliseconds must be greater than 0");
}
if (numberOfLines <= 0)
{
throw new IllegalArgumentException("numberOfLines must be greater than 0");
}
String pid = null;
try
{
pid = getProcessId();
}
catch (Exception e)
{
// do nothing
}
if (pid != null && !pid.isEmpty())
{
new Thread(new Task(pid, intervalMilliseconds, numberOfLines)).start();
}
else
{
LOG.debug("failed to obtain PID. No heap statistics will be available.");
}
this.isStarted = true;
}
}
/**
* Get the PID of this process
* http://stackoverflow.com/a/35885/147530
* @return
*/
private static String getProcessId() throws ParseException
{
// this is not a foolproof way to get the process ID
return String.format("%d", (NumberFormat.getIntegerInstance().parse(ManagementFactory.getRuntimeMXBean().getName()).intValue()));
}
}
</pre>