Monitor direct memory usage in your app…

Not sure if this is the best place for this… but here we are.

It has pained me for a long time that I cannot see how much direct memory my Java application is using. JME has BufferUtils which goes through some backflips to figure this out at least within the buffers that it allocated.

Scratching at the back of my brain has been the memory that the JDK jconsole app can see this information. It’s certainly available to the JVM. So I finally got motivated to look into it. Sure enough, jconsole not only has access to this but it’s accessing it using one of the MBeans built into the JVM.

The topic of MBeans is too long to go into here if you don’t already know what they are. Suffice it to say, that applications can instrument themselves and provide external control through JMX MBeans. And the JVM has been doing this for a long time. The best part, you can pretty easily access these if you know how.

I drudged up some of my old server management experience and did some deep-diving. The java.management package is really kind of cool if you ever want to take a look. These are the things the JVM provides to us “out of the box” with nice interfaces and stuff already built. Unfortunately, the direct BufferPool is not one of these things so requires direct MBean-style access. Still, I highly recommend looking at those other classes because you can get all kinds of really juicy info about the running JVM… GC performance in the different pools, CPU usages, thread stats, etc…

Anyway, the culmination of this quest for direct memory usage is a simple utility class:
[java]public class MemoryUtils {
private static MBeanServer mbeans = ManagementFactory.getPlatformMBeanServer();
private static ObjectName directPool;
static {
try {
// Create the name reference for the direct buffer pool’s MBean
directPool = new ObjectName(“java.nio:type=BufferPool,name=direct”);
} catch (MalformedObjectNameException ex) {
Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, “Error creating direct pool ObjectName”, ex);
}
}

public static long getDirectMemoryUsage() {
    try {
        Long value = (Long)mbeans.getAttribute(directPool, "MemoryUsed");
        return value == null ? -1 : value;
    } catch (JMException ex) {
        Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving 'MemoryUsed'", ex);
        return -1;
    } 
}

public static long getDirectMemoryCount() {
    try {
        Long value = (Long)mbeans.getAttribute(directPool, "Count");
        return value == null ? -1 : value;
    } catch (JMException ex) {
        Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving 'Count'", ex);
        return -1;
    } 
}

public static long getDirectMemoryTotalCapacity() {
    try {
        Long value = (Long)mbeans.getAttribute(directPool, "TotalCapacity");
        return value == null ? -1 : value;
    } catch (JMException ex) {
        Logger.getLogger(MemoryUtils.class.getName()).log(Level.SEVERE, "Error retrieving 'TotalCapacity'", ex);
        return -1;
    } 
}

}
[/java]

The usage and counts are the most interesting ones. I provide access to TotalCapacity but this is not a value that makes any sense to me. It seems it is always just slightly less than usage… which a) makes no sense, and b) doesn’t seem useful. I was really hoping for a way to figure out what the max direct mem setting was but I have been unsuccessful.

Anyway, I can now easily add direct memory stats to my debug HUDs along with the other regular heap stats I always do:

(now I just have to figure out why my new Mythruna engine seems to use twice the direct mem of the old one…) :slight_smile:

6 Likes

Will this be in the engine? In a plugin? Stay in this forum post? Or is it not decided yet?

Thanks for sharing anyway, this is valuable information to monitor/instrument.

@kwando said: Will this be in the engine? In a plugin? Stay in this forum post? Or is it not decided yet?

Thanks for sharing anyway, this is valuable information to monitor/instrument.

I don’t know. For now, it takes only 5 seconds to cut and paste it into a class and add the imports. :slight_smile:

Sure does! Already added it to my project and it works a charm!
Much nicer having the stats on screen for debugging.
Many thanks!

can’t hurt to put it in jme3 Utils alng with BufferUtils and all.

What’s the “count”? the number of objects in memory?

I committed it to com.jme3.util

@nehon said: can't hurt to put it in jme3 Utils alng with BufferUtils and all.

What’s the “count”? the number of objects in memory?

I think it’s the number of allocated direct buffers… I can’t be 100% sure but it seems to correlate.

I am attempting to use this and I keep getting the severe Error.

I must be doing something wrong.

[java]
import com.jme3.util.MemoryUtils;
private MemoryUtils directMemory;

private void setupDirectMemoryView() {
	totalMemory.setText("Total Memory: "+directMemory.getDirectMemoryTotalCapacity());
	usedMemory.setText("Used Memory: "+directMemory.getDirectMemoryUsage());
	memoryCount.setText("Memory Count: " +directMemory.getDirectMemoryCount());

	guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
	totalMemory = new BitmapText(guiFont, false);
	usedMemory = new BitmapText(guiFont, false);
	memoryCount = new BitmapText(guiFont, false);
	totalMemory.setLocalTranslation(0, 1, 0);
	usedMemory.setLocalTranslation(0, 1-totalMemory.getLineHeight(), 0);
	memoryCount.setLocalTranslation(0, 1-totalMemory.getLineHeight()-usedMemory.getLineHeight(), 0);
	guiLayer.attachChild(totalMemory);
	guiLayer.attachChild(usedMemory);
	guiLayer.attachChild(memoryCount);
	float y = 0;
	if (fpsText!=null)		// move it up so it appears above fps text
		y = fpsText.getLineHeight();
	statsView.setLocalTranslation(0, y, 0);
	guiLayer.attachChild(statsView);
}
private void updateMemoryDisplay(){
	totalMemory.setText("Total Memory: "+directMemory.getDirectMemoryTotalCapacity());
	usedMemory.setText("Used Memory: "+directMemory.getDirectMemoryUsage());
	memoryCount.setText("Memory Count: " +directMemory.getDirectMemoryCount());
}

[/java]

When I run either of those methods I get the following error.
SEVERE MemoryUtils 12:42:13 PM Error retrieving ‘MemoryUsed’
javax.management.InstanceNotFoundException: java.nio:type=BufferPool,name=direct
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getMBean(DefaultMBeanServerInterceptor.java:1094)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getAttribute(DefaultMBeanServerInterceptor.java:662)
at com.sun.jmx.mbeanserver.JmxMBeanServer.getAttribute(JmxMBeanServer.java:639)
at com.jme3.util.MemoryUtils.getDirectMemoryUsage(MemoryUtils.java:64)

Would anyone be able to provide some guidance?
I am running on Nightly Build 3-22-2013.

What version/vendor of Java are you running? Maybe they don’t implement the standard management extensions.

jvisualvm has a plugin for this if you want to monitor from that application (which is part of the JDK)

https://blogs.oracle.com/nbprofiler/entry/new_memory_buffer_plugin_for

I have tested with Oracles JDKs
1.7.0_17
1.6.0_45
1.6.0_32
Testing on Windows 7 64 Bit.

I really suspect that your application is running on JDK1.6 (BufferPoolMXBean is only available in 1.7). How do you launch it?

I am running through JBuilder 2008 R2.

I have JDK 1.7.0 _17 set for the build path.

@jmaasing said: I really suspect that your application is running on JDK1.6 (BufferPoolMXBean is only available in 1.7). How do you launch it?

Oooh… you are right. I swore that I developed it on 1.6 but I think my paths were messed up at the time.

So, yes it’s a 1.7+ only feature.

<cite>@dm1056 said:</cite> I am running through JBuilder 2008 R2.

I have JDK 1.7.0 _17 set for the build path.

Just a guess but the executing VM must be JDK 7. So I thought maybe it launches a java 6 VM to run the application. I don’t know how JBuilder works but other IDEs can compile with a VM you set in the IDE but then it launches applications with the same VM it is running from and that does not have to be the same as you build with. Oh well, then I don’t know what could cause that problem, try running jvisualvm and connect to your application, that will tell you for sure what VM it is executing.