Jmap

<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>

This entry was posted in Software. Bookmark the permalink.

Leave a comment