Kirk has posted 1 posts at DZone. View Full User Profile

Displaying Java’s Memory Pool Statistics with VisualVM

12.19.2011
| 29991 views |
  • submit to reddit

The "Just In Time" (JIT) compilation of Java Byte code provides a window of opportunity within the dynamics of a run time. The product of the JIT, native code, is stored in a memory pool known as the Code Cache. Filling the Code Cache will cause the JIT to stop working. Turning off the JIT will deny our applications key optimizations that could boost performance to match or, in some cases, exceed equivalent code written in C/C+. When this happens, our applications consume much more CPU and will execute slower than they should.

Given the important role that the Code Cache has in application performance, it’s surprising how little tooling there is out there to let you peek into it’s inner workings. But then, not many are asking for such tooling as not many are aware of the important role that code cache sizing plays in application performance. It’s also surprising that the JVM doesn’t provide much visibility into this mysterious part of it’s inner workings. It is, however, monitored by an instance of java.lang.management.MemoryMXBean. This MXBean does give us some insight into how much of this memory pool is being utilized by the JIT. In figure 1, you can see the data being exposed by the MBean brower, VisualVM plugin.



Figure 1. VisualVM MBean

The MBean browser view provides a simple textual view of the memory pool’s attributes. Instead of this textual view, I was looking for a graphical time line of Code Cache utilization. To this end, I wrote my own VisualVM plugin which can be seen in Figure 2. The rest of this piece describes the steps taken to create this new plugin.



Figure 2. Memory pools visualization

A VisualVM plugin is nothing more than a NetBeans plugin and that is nothing more than a specialized JAR. I’ve used the NetBeans IDE here simply because it makes it more convenient to build and test the VisualVM plugin. The first thing to do is to configure NetBeans by adding a downloaded version of VisualVM (I used 1.3.3) to the platform manager. The next step is to create a new NetBeans module project which we’ll call MemoryPoolView. You’ll need to configure the project to use the VisualVM platform. Once this is complete, we can get on with the task of building the new plugin.

NetBeans Module Lifecycle

NetBeans modules have a life cycle that is supported by the class ModuleInstaller. MemoryPoolView requires it’s own installer so that it can manage it’s own life-cycle. Installers can be generated using the NetBeans menu item New | Module Installer. The generated code should look something like that found in listing 1.

public class Installer extends ModuleInstall {

    @Override
    public void restored() {
        MemoryPoolViewProvider.initialize();
    }

    @Override
    public void uninstalled() {
        MemoryPoolViewProvider.unregister();
    }

}
Listing 1. Module Installer

The Installer overrides the two most important methods, restored() and uninstalled(). These methods are called when NetBeans starts up and shutdowns respectively. Our implementation of restored() makes a call to register our MemoryPoolViewProvider with the DataSourceViewsManager. The DataSourceViewsManager manages instances of DataSourceView. Thus MemoryPoolViewProvider must extend this class.

To have a better understanding of how all of this works we need to know about the concept of a data source in VisualVM. As the name suggests, a DataSource is anything that will feed a VisualVM with (more specifically, performance) data. VisualVM comes with a number of data sources including SnapShot, Host, and of interest to us, Application. When the user opens a DataSource, VisualVM asks the DataSourceViewsManager for any views that are registered that can display data from that data source. The DataSourceViewsManager in turn asks each of the views, can you display data from this DataSource. Those view providers that answer yes will be given the data source and asked for a view. VisualVM will create a new tabbed pane for that view. From that point forward, what happens in the view is up to the view provider.

In listing 2, we can see the code that registers and unregisters our view provider in response to lifecycle events. The supportsViewFor() and createView() methods respond to the requests from the DataSourceViewsManager. Because supportsViewFor can only accept an Application as a parameter and we want to inspect the code cache for all applications, this method simply returns true. In some cases you might want to put in more extensive testing to determine if you want to instantiate a view.
class MemoryPoolViewProvider extends DataSourceViewProvider<Application> {
    
    private static DataSourceViewProvider<Application> instance =  new MemoryPoolViewProvider();

    @Override
    public boolean supportsViewFor(final Application application) {
        return true;
    }

    @Override
    public synchronized DataSourceView createView(final Application application) {
        return new MemoryPoolView(application);

    }

    static void initialize() {
        DataSourceViewsManager.sharedInstance().addViewProvider(instance, Application.class);
    }

    static void unregister() {
        DataSourceViewsManager.sharedInstance().removeViewProvider(instance);
    }
}
Listing 2. DataSourceViewProvider

Now that we have MemoryPoolView, the next steps are to build the view and connect it to the Application. To do this, we need to consider two topics:

  • how to build a view that integrates into VisualVM
  • the logistics of acquiring the data from the Application for the view

Let's start by looking at how to build the DataSourceView that is returned by MemoryPoolViewProivder.createView().

The Anatomy of a VisualVM View

If you look back at figure 2, you can see that each individual view is contained in it’s own tabbed pane. Each tabbed pane has a title and an icon. After that, it may seem that the layout of each view is somewhat random. While it is true that each view is customized to best display what it needs to show, there is a wee bit of structure in there. The panel’s inner space is broken down into a master area on top and 4 display areas down below. The master area is generally used to provide course grained controls, while each of the 4 display areas present data. These display areas are arranged in a 4 quadrant grid. This layout is visible in MemoryPoolView as each quadrant contains a view of one memory pool. A time line of the Code Cache counters is displayed in the lower right hand quadrant. You can also clearly see this grid layout in the monitor view.

In our master view, there are 4 check boxes. You may have noticed these (optionally displayed) check boxes in other views. For example, the Threads plugin has two check boxes, one for each of the two views it maintains. These check boxes will hide or reveal a corresponding view.

Getting back to the code, we will provide the tabbed pane’s title and icon as arguments in our MemoryPoolView constructor. Note that MemoryPool view must extends DataSourceView as that is what is returned by the view providers createView method. The MasterView and DetailsView will be constructed in a call to createComponent() as shown in listing 3.

The last visual detail to consider here is positioning. In the constructor one argument is the magic number 60. VisualVM orders the tabs using preferences suggested by the user. In this case the suggestion is this tab should be in position 60. Anything smaller will be on the left and anything bigger will be on the right. Adding a DetailsView requires that you specify a quadrant and a slot. DataViewComponent defines constants for TOP_LEFT, BOTTOM_RIGHT and so on. The position is used when two DetailsView are placed in the same quadrant. Looking back at figure 2 you can see that the young generational spaces where both placed in TOP_LEFT with Eden in position 10 and Survivors in position 20.

We configure all of this in the class MemoryPoolView as shown in listing 3.

class MemoryPoolView extends DataSourceView {

   public MemoryPoolView(Application application) {
        super(application,"Memory Pools",
                                     new ImageIcon(ImageUtilities.loadImage(IMAGE_PATH, true)).getImage(),
                                     60,
                                     false);
    }

    @Override
    protected DataViewComponent createComponent() {
        //Data area for master view:
        JEditorPane generalDataArea = new JEditorPane();
        generalDataArea.setBorder(BorderFactory.createEmptyBorder(7, 8, 7, 8));

        //Master view:
        DataViewComponent.MasterView masterView =
                     new DataViewComponent.MasterView("Memory Pools", "View of Memory Pools", generalDataArea);

        //Configuration of master view:
        DataViewComponent.MasterViewConfiguration masterConfiguration =
                     new DataViewComponent.MasterViewConfiguration(false);

        //Add the master view and configuration view to the component:
        dvc = new DataViewComponent(masterView, masterConfiguration);

        // the magic that gets a handle on all instances of MemoryPoolMXBean
        findMemoryPools();

        MemoryPoolPanel panel;
        for ( MemoryPoolModel model : models) {
            panel = new MemoryPoolPanel(model.getName());
            model.registerView(panel);
            Point position = calculatePosition(model.getName());
            dvc.addDetailsView(new DataViewComponent.DetailsView(
                        model.getName(), "memory pool metrics", position.y, panel, null), position.x);
        }
        return dvc;
    }
    private Point calculatePosition(String name) {
        return positions.get(name);
    }

    static {
        positions = new HashMap<String,Point>();
        positions.put( "Par Eden Space", new Point(DataViewComponent.TOP_LEFT,10));
        positions.put( "PS Eden Space", new Point(DataViewComponent.TOP_LEFT,10));
        positions.put( "Eden Space", new Point(DataViewComponent.TOP_LEFT,10));
        positions.put( "G1 Eden", new Point(DataViewComponent.TOP_LEFT,10));
        positions.put( "Par Survivor Space", new Point(DataViewComponent.TOP_LEFT,20));
        positions.put( "PS Survivor Space", new Point(DataViewComponent.TOP_LEFT,20));
        positions.put( "Survivor Space", new Point(DataViewComponent.TOP_LEFT,20));
        positions.put( "G1 Survivor", new Point(DataViewComponent.TOP_LEFT,20));
        positions.put( "CMS Old Gen", new Point(DataViewComponent.TOP_RIGHT,10));
        positions.put( "PS Old Gen", new Point(DataViewComponent.TOP_RIGHT,10));
        positions.put( "Tenured Gen", new Point(DataViewComponent.TOP_RIGHT,10));
        positions.put( "G1 Old Gen", new Point(DataViewComponent.TOP_RIGHT,10));
        positions.put( "CMS Perm Gen", new Point(DataViewComponent.BOTTOM_LEFT,10));
        positions.put( "Perm Gen", new Point(DataViewComponent.BOTTOM_LEFT,10));
        positions.put( "PS Perm Gen", new Point(DataViewComponent.BOTTOM_LEFT,10));
        positions.put( "G1 Perm Gen", new Point(DataViewComponent.BOTTOM_LEFT,10));
        positions.put( "Code Cache", new Point(DataViewComponent.BOTTOM_RIGHT,10));
    }
Listing 3. MemoryPoolView

Getting the Data

Now that we’ve covered the basics of laying out a VisualVM view it’s time to move on to acquiring the data to feed the views. As was the case in view construction, VisualVM provides a lot built useful functionality for data acquisition and handling. In this case we want a handle on all the instances of MemoryPoolMXBean. To get to them we will make use of VisualVM facilities that eliminate our need to write complex JMX client code. Lets take the magic out of findMemoryPools() as we continue to build out MemoryPoolView in listing 4.

protected void findMemoryPools() {
    try {
        MBeanServerConnection conn = getMBeanServerConnection();
        ObjectName pattern = new ObjectName("java.lang:type=MemoryPool,name=*");  
        for (ObjectName name : conn.queryNames(pattern, null)) {
            initializeModel(name, conn);
        }
    } catch (Exception e) {
            LOGGER.throwing(MemoryPoolView.class.getName(), "Exception: ", e);
    }
}

private MBeanServerConnection getMBeanServerConnection() {
    JmxModel jmx = JmxModelFactory.getJmxModelFor((Application)super.getDataSource());
    return jmx == null ? null : jmx.getMBeanServerConnection();
}
Listing 4 Obtaining a handle on MemoryMXBean instances

We obtain handles on MXBeans through a MBeanServerConnection. We can get the server connection from a JmxModel which in turn is acquired from the JmxModelFactory. All of this infrastructure is provided to use by VisualVM. All we have to know is what we are looking for and then query the connection to find it. This is achieved by creating an ObjectName pattern. From figure 1 we can see that the ObjectName used to bind instances of MemoryPoolMXBean is something like java.lang:type=MemoryPool,name=”CMS Old Gen”. Changing the name attribute to with wildcard * allows us to find all of the memory pools. Now all we have to do is interrogate each of the beans for their values and display them.
Charting in VisualVM

Referring back to figure 2 we can see that the data for each MXBean displayed in an XY plot. If you run the plugin you’’ notice that the graph is updated every 2 seconds. As complex as the charts look, VisualVM’s supplied charts are remarkably easy to use.

Of the three different types of chart, Decimal, Precent and Byte. MemoryPoolView uses Byte. We build the chart using a prescribed variation of the builder pattern. This is demonstrated in the constructor in listing 5.
public class MemoryPoolPanel extends JPanel implements MemoryPoolModelListener {

    private SimpleXYChartSupport chart;

    public MemoryPoolPanel(String name) {
        setLayout(new BorderLayout());
        SimpleXYChartDescriptor description = SimpleXYChartDescriptor.bytes(0, 20, 100, false, 1000);
        description.setChartTitle(name);
        description.setDetailsItems(new String[3]);

        description.addLineItems("Configured");
        description.addLineItems("Used");

        description.setDetailsItems(new String[]{"Size current",
                                                 "configured",
                                                 "occupancy"});
        description.setXAxisDescription("<html>Time</html>");
        description.setYAxisDescription("<html>Memory Pool (K)</html>");

        chart = ChartFactory.createSimpleXYChart(description);

        add(chart.getChart(),BorderLayout.CENTER);

    }

    @Override
    public void memoryPoolUpdated(MemoryPoolModel model) {
        long[] dataPoints = new long[2];
        dataPoints[0] = model.getCommitted();
        dataPoints[1] = model.getUsed();
        chart.addValues(System.currentTimeMillis(), dataPoints);

        String[] details = new String[3];
        details[0] = Long.toString(model.getCommitted());
        details[1] = Long.toString(model.getMax());
        details[2] = Long.toString(model.getUsed());
        chart.updateDetails(details);
    }
}
Listing 5 MemoryPoolPanel

We start the process by building a description of the chart that we want. The initial parameters indicate minimal value, maximum value, initial Y margin, a boolean to indicate if the chart can be hidden (puts checkbox in MasterView), and the size of a buffer to hold data points. To this description we’ll add a title and a detail view for current, configured, and occupancy, and X and Y axis labels, . Finally we add a line for occupancy and currently configured size. Once this is complete, we are ready to ask the  ChartFactory to create the chart.

The final step is to connect the model to the view. We need a timer so that we can update the charts at a regular rate. Once again, VisualVM proves useful in that it provides a CachedMBeanServerConnection. A CachedMBeanServerConnection caches the values from a MBeanServerConnection. It also contains a timer that when fires causes the cache to be flushed. Flushing the cache causes the cache to be refreshed and then notifying all MBeanCacheListeners that the cache has been updated. All we have to do is implement the flushed() as specified by the interface. When flushed(0 is called we will dig through the CompositeData that bundles the data values we are looking for. This implementation can be found in Listing 7.
class MemoryPoolModel implements MBeanCacheListener {

    public MemoryPoolModel(final ObjectName mbeanName, final JmxModel model,
                                              final MBeanServerConnection mbeanServerConnection) throws Exception {
        this.mbeanName = mbeanName;
        this.mbeanServerConnection = mbeanServerConnection;
        CachedMBeanServerConnectionFactory.getCachedMBeanServerConnection(model,
                                                                                                            2000).addMBeanCacheListener(this);
        name = mbeanServerConnection.getAttribute(mbeanName, "Name").toString();
        type = mbeanServerConnection.getAttribute(mbeanName, "Type").toString();
    }


    @Override
    public void flushed() {
        try {
            CompositeData poolStatistics = (CompositeData)mbeanServerConnection.getAttribute(mbeanName, "Usage");
            if ( poolStatistics != null) {
                CompositeType compositeType = poolStatistics.getCompositeType();
                if ( compositeType != null) {
                    Collection keys = compositeType.keySet();
                    for ( String key : compositeType.keySet()) {
                        if ( key.equals("committed"))
                            this.committed = (Long)poolStatistics.get("committed");
                        else if ( key.equals("init"))
                            this.initial = (Long)poolStatistics.get("init");
                        else if ( key.equals("max"))
                            this.max = (Long)poolStatistics.get("max");
                        else if ( key.equals("used"))
                            this.used = (Long)poolStatistics.get("used");
                        else
                            LOGGER.warning("Unknown key: " + key);
                    }
                    tickleListeners();
                }
            }
        } catch (Throwable t) {
            LOGGER.throwing(MemoryPoolModel.class.getName(), "Exception recovering data from MemoryPoolMXBean ", t);
        }
    }
Listing 7 MemoryViewModel

The final step is to update the chart. Once the cache has been refreshed, the model will let the view know that it has new data values. The view can then query the model to obtain those values. To add values we need to put them into a long[] and pass them to the chart. Updating the details follows the same pattern. This is demonstrated in listing 8.
public void memoryPoolUpdated(MemoryPoolModel model) {
    long[] dataPoints = new long[2];
    dataPoints[0] = model.getCommitted();
    dataPoints[1] = model.getUsed();
    chart.addValues(System.currentTimeMillis(), dataPoints);

    String[] details = new String[3];
    details[0] = Long.toString(model.getCommitted());
    details[1] = Long.toString(model.getMax());
    details[2] = Long.toString(model.getUsed());
    chart.updateDetails(details);
}
Listing 8 Updating a chart

Conclusion

As was demonstrated here, there is quite a bit of support to aid in the visualization of performance data in VisualVM. We can use this support to do the heavy lifting that would normally take considerable amounts of boiler plate code. This demonstration only focuses only a fraction of the support that is available. For example, we could easily build in snapshot capabilities that would take advantage of the support for that feature.

Finally, MemoryPoolView has now been released as an open source project @ http://java.net/projects/memorypoolview.

Published at DZone with permission of its author, Kirk Pepperdine.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

John David replied on Tue, 2011/12/20 - 9:59am

Its a nice post on memory monitoring and related to watching. My question is how we can optimize our code or manage the memory in our code. There are few good code writing practices which must be followed in order to improve memory management in our code. Also I am facing perm space issue with a web application and JBoss. After running for few hours, I got started getting this error. Any one has some good solution to this?

Kirk Pepperdine replied on Tue, 2011/12/20 - 1:19pm

Hi John,

There isn't much you can do from a technical tuning POV except make it bigger. There are some other things that could be talked about but they are outside the scope of this document. Mostly , small simple methods tend to be well optimized where as large complex ones do not.

Application servers are infamous for classloader leaks in perm space. I would suggest using MAT for this type of leak.

Kirk

Morkel Abie replied on Thu, 2013/05/02 - 4:58am

Hi! I'm first time visit this website and I think there are lots of informational things here for me and I'll try to read some more posts here like this one.. san antonio wedding dj

Danish12 Ali replied on Fri, 2013/07/26 - 3:34pm

 This is my first time i visit here. I found so many entertaining stuff in your blog, especially its discussion. From the tons of comments on your articles, I guess I am not the only one having all the leisure here! Keep up the good work. I have been meaning to write something like this on my website and you have given me an idea.  no flame e cigarette

Cata Nic replied on Tue, 2013/09/03 - 3:36am

The statistics are very important if you want to have a good solution for your clients. They need data to be able to increase their business . This tutorial is really useful.

Raju Mummidi replied on Fri, 2013/10/18 - 11:37pm

Hi i am new this site.. Now i am searching for the concept of VisualVM. And how to integrate our application with VisualVM and how to analyze it. Please let me suggest and help me......


Thanks in Advance..........!

Muhammad Danial replied on Sun, 2014/04/06 - 8:59am

 

 When this happens, our applications consume much more CPU and will execute slower than they should. 

eczema free forever pdf download

Black Men replied on Sun, 2014/04/13 - 1:31pm

  Turning off the JIT will deny our applications key optimizations that could boost performance to match or, in some cases, exceed equivalent code written in C/C+. When this happens, our applications consume much more CPU and will execute slower than they should.

soundcloud downloader

Black Men replied on Sat, 2014/04/19 - 10:17am

 I simply want to tell you that I am new to weblog and definitely liked this blog site. Very likely I’m going to bookmark your blog . You absolutely have wonderful stories. Cheers for sharing with us your blog.   

http://aokmedia.co

Black Men replied on Mon, 2014/04/21 - 9:42am

 The next step is to create a new NetBeans module project which we’ll call MemoryPoolView. You’ll need to configure the project to use the VisualVM platform. Once this is complete, we can get on with the task of building the new plugin. 

furnished rentals

Black Men replied on Wed, 2014/04/23 - 10:14am

 But then, not many are asking for such tooling as not many are aware of the important role that code cache sizing plays in application performance. It’s also surprising that the JVM doesn’t provide much visibility into this mysterious part of it’s inner workings. 

élixir du suedois

Josep Media replied on Thu, 2014/04/24 - 2:15am

 Association International has named thirty future leaders of the destination marketing industry http://www.jmtrade.net.pl/

Black Men replied on Thu, 2014/04/24 - 2:37am

 My friend suggested me to check it out for online thesis and dissertation help. As I am a biology student I felt i can make use of the online courses that is offered by the university . It was a good to know about the institution.    

http://www.prymsalony.pl/

Black Men replied on Thu, 2014/04/24 - 7:24am

 MemoryMXBean. This MXBean does give us some insight into how much of this memory pool is being utilized by the JIT. In figure 1, you can see the data being exposed by the MBean brower, VisualVM plugin. 

read more please

Black Men replied on Sat, 2014/04/26 - 3:12am

 Thomas, you live in Ohio and you have good credit, you don't want to be mixed up with Judith Kendall who lives in Utah and doesn't have good credit. Worse, if you are mixed up with h er,   

amway

Black Men replied on Sun, 2014/04/27 - 2:40am

 Turning off the JIT will deny our applications key optimizations that could boost performance to match or, in some cases, exceed equivalent code written in C/C+. When this happens, our applications consume much more CPU and will execute slower than they should. 

Liquor Store POS SystemGrocery POS System

Black Men replied on Sun, 2014/04/27 - 9:49am

 I have been to this site as My friend suggested me to check it out for online thesis and dissertation help. As I am a biology student I felt i can make use of the online courses that is offered by the university . It was a good to know about the institution.   

palette color shampoo

Samad Khatri replied on Mon, 2014/04/28 - 11:20am in response to: Kirk Pepperdine

 mall simple methods tend to be well optimized where as large complex ones do not. www.slubnydom.pl

Black Men replied on Wed, 2014/04/30 - 1:55am

 It is, however, monitored by an instance of java.lang.management.MemoryMXBean. This MXBean does give us some insight into how much of this memory pool is being utilized by the JIT. In figure 1, you can see the data being exposed by the MBean brower, VisualVM plugin. 

buy guaranteed facebook followers

Samad Khatri replied on Wed, 2014/04/30 - 12:17pm in response to: Danish12 Ali

 the leisure here! Keep up the good work. I have been meaning to write something like this on my website and you have given me an idea kpkawka.pl

Samad Khatri replied on Sat, 2014/05/10 - 3:55pm in response to: John David

 related to watching. My question is how we can optimize our code or manage the memory in our code. kandydacimazowieckie.pl

Samad Khatri replied on Sat, 2014/05/17 - 5:40am in response to: Black Men

 provide much visibility into this mysterious part of it’s inner workings. It is, however, monitored by an instance of java.lang.

opinie citibank

Black Men replied on Mon, 2014/05/19 - 10:23am

 The processors are brand new silicon, so they often don’t pass BIOS tests much less boot up fully. But my work still had to get done. I did that work for a month and a half to get results that only took a day later on when everything was working correctly. I had to come in late at night and on the weekends to try to get the most out of the time available just to meet deadlines. That definitely put stress on my home life.     

bforex احتيال

Alec Grayson replied on Thu, 2014/05/22 - 7:28pm

 This is a great article, Thanks for giving me this information. Keep posting.

cushion inners wholesale

Samad Khatri replied on Tue, 2014/05/27 - 12:49pm

 would add to the must have list "your favorite writing tool." I love my electronics, but having my favorite pens to write the notes and big ideas just makes my heart happy. Can't wait to see and me fud crypter

coffee heaven opinie

Samad Khatri replied on Sat, 2014/06/14 - 7:02am

 MemoryPoolViewProvider with the DataSourceViewsManager. The DataSourceViewsManager manages instances of DataSourceView. Thus MemoryPoolViewProvider must extend this class.

pearlypenilepapulesremoval.co.uk/

Samad Khatri replied on Sun, 2014/06/22 - 12:49pm

  Code Cache has in application performance, it’s surprising how little tooling there is out there to let you peek into it’s inner workings.

http://www.ejaculationbycommand.co.uk/

Samad Khatri replied on Mon, 2014/07/21 - 4:32am in response to: Danish12 Ali

performance to match or, in some cases, exceed equivalent code written in C/C+. When this happens, our applications consume much more CPU and will execute slower than they should.
personalized framed birth announcements

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.