2.5 Monitoring Gross Memory Usage
The JDK provides two
methods for monitoring the amount of memory used by the runtime
system: freeMemory(
)
and totalMemory( ) in
the java.lang.Runtime class.
totalMemory( ) returns a long,
which is the number of bytes currently allocated to the runtime
system for this particular VM process. Within this memory allocation,
the VM manages its objects and data. Some of this allocated memory is
held in reserve for creating new objects. When the currently
allocated memory gets filled and the garbage collector cannot
allocate sufficiently more memory, the VM requests more memory from
the underlying system. If the underlying system cannot allocate any
further memory, an
OutOfMemoryError error is thrown. Total memory can go up
and down; some Java runtimes return sections of unused memory to the
underlying system while still running.
freeMemory( ) returns a long,
which is the number of bytes available to the VM to create objects
from the section of memory it controls (i.e., memory already
allocated to the runtime by the underlying system). The free memory
increases when a garbage collection successfully reclaims space used
by dead objects, and also increases when the Java runtime requests
more memory from the underlying operating system. The free memory
reduces each time an object is created and when the runtime returns
memory to the underlying system.
SDK 1.4 added a new method, Runtime.maxMemory(
)
.
This method simply gives the -Xmx value, and is of
no use to monitor heap usage.
It can be useful to monitor memory usage while an application runs:
you can get a good feel for the hotspots of your
application. You may be surprised to see steady
decrements in the free memory available to your application when you
were not expecting any change. This can occur when you continuously
generate temporary objects from some routine; manipulating graphical
elements frequently shows this behavior.
Monitoring memory with freeMemory( ) and
totalMemory( ) is straightforward, and I include a
simple class that does this graphically. It creates three threads:
one to periodically sample the memory, one to maintain a display of
the memory usage graph, and one to run the program you are
monitoring. Figure 2-1 shows the memory monitor after monitoring a
run of the
ProfileTest class. The total memory allocation
is flat because the class did not hold on to much memory at any one
time. The free memory shows the typical sawtooth pattern of an
application cycling through temporary objects: each upstroke is where
the garbage collector kicked in and freed up the space being taken by
the discarded dead objects.
The monitor was run using the command:
% java tuning.profile.MemoryMonitor tuning.profile.ProfileTest
Here are the classes for the memory
monitor, together with comments:
package tuning.profile;
import java.awt.*;
import java.awt.event.*;
import java.lang.reflect.*;
/*
* Internal class to periodically sample memory usage
*/
class MemorySampler
implements Runnable
{
long[ ] freeMemory = new long[1000];
long[ ] totalMemory = new long[1000];
int sampleSize = 0;
long max = 0;
boolean keepGoing = true;
MemorySampler( )
{
//Start the object running in a separate maximum priority thread
Thread t = new Thread(this);
t.setDaemon(true);
t.setPriority(Thread.MAX_PRIORITY);
t.start( );
}
public void stop( )
{
//set to stop the thread when someone tells us
keepGoing = false;
}
public void run( )
{
//Just a loop that continues sampling memory values every
//30 milliseconds until the stop( ) method is called.
Runtime runtime = Runtime.getRuntime( );
while(keepGoing)
{
try{Thread.sleep(30);}catch(InterruptedException e){ };
addSample(runtime);
}
}
public void addSample(Runtime runtime)
{
//Takes the actual samples, recording them in the two arrays.
//We expand the arrays when they get full up.
if (sampleSize >= freeMemory.length)
{
//just expand the arrays if they are now too small
long[ ] tmp = new long[2 * freeMemory.length];
System.arraycopy(freeMemory, 0, tmp, 0, freeMemory.length);
freeMemory = tmp;
tmp = new long[2 * totalMemory.length];
System.arraycopy(totalMemory, 0, tmp, 0, totalMemory.length);
totalMemory = tmp;
}
freeMemory[sampleSize] = runtime.freeMemory( );
totalMemory[sampleSize] = runtime.totalMemory( );
//Keep the maximum value of the total memory for convenience.
if (max < totalMemory[sampleSize])
max = totalMemory[sampleSize];
sampleSize++;
}
}
public class MemoryMonitor
extends Frame
implements WindowListener,Runnable
{
//The sampler object
MemorySampler sampler;
//interval is the delay between calls to repaint the window
long interval;
static Color freeColor = Color.red;
static Color totalColor = Color.blue;
int[ ] xpoints = new int[2000];
int[ ] yfrees = new int[2000];
int[ ] ytotals = new int[2000];
/*
* Start a monitor and the graph, then start up the real class
* with any arguments. This is given by the rest of the commmand
* line arguments.
*/
public static void main(String args[ ])
{
try
{
//Start the grapher with update interval of half a second
MemoryMonitor m = new MemoryMonitor(500);
//Remaining arguments are the class with
//the main( ) method, and its arguments
String classname = args[0];
String[ ] argz = new String[args.length-1];
System.arraycopy(args, 1, argz, 0, argz.length);
Class clazz = Class.forName(classname);
//main has one parameter, a String array.
Class[ ] mainParamType = {args.getClass( )};
Method main = clazz.getMethod("main", mainParamType);
Object[ ] mainParams = {argz};
//start real class
main.invoke(null, mainParams);
//Tell the monitor the application finished
m.testStopped( );
}
catch(Exception e)
{
e.printStackTrace( );
}
}
public MemoryMonitor(long updateInterval)
{
//Create a graph window and start it in a separate thread
super("Memory Monitor");
interval = updateInterval;
this.addWindowListener(this);
this.setSize(600,200);
this.show( );
//Start the sampler (it runs itself in a separate thread)
sampler = new MemorySampler( );
//and put myself into a separate thread
(new Thread(this)).start( );
}
public void run( )
{
//Simple loop, just repaints the screen every 'interval' milliseconds
int sampleSize = sampler.sampleSize;
for (;;)
{
try{Thread.sleep(interval);}catch(InterruptedException e){ };
if (sampleSize != sampler.sampleSize)
{
//Should just call repaint here
//this.repaint( );
//but it doesn't always work, so I'll repaint in this thread.
//I'm not doing anything else anyway in this thread.
try{
this.update(this.getGraphics( ));
}
catch(Exception e){e.printStackTrace( );}
sampleSize = sampler.sampleSize;
}
}
}
public void testStopped( )
{
//just tell the sampler to stop sampling.
//We won't exit ourselves until the window is explicitly closed
//so that our user can examine the graph at leisure.
sampler.stop( );
}
public void paint(Graphics g)
{
//Straightforward - draw a graph for the latest N points of
//total and free memory where N is the width of the window.
try
{
java.awt.Dimension d = getSize( );
int width = d.width-20;
int height = d.height - 40;
long max = sampler.max;
int sampleSize = sampler.sampleSize;
if (sampleSize < 20)
return;
int free, total, free2, total2;
int highIdx = width < (sampleSize-1) ? width : sampleSize-1;
int idx = sampleSize - highIdx - 1;
for (int x = 0 ; x < highIdx ; x++, idx++)
{
xpoints[x] = x+10;
yfrees[x] = height -
(int) ((sampler.freeMemory[idx] * height) / max) + 40;
ytotals[x] = height -
(int) ((sampler.totalMemory[idx] * height) / max) + 40;
}
g.setColor(freeColor);
g.drawPolyline(xpoints, yfrees, highIdx);
g.setColor(totalColor);
g.drawPolyline(xpoints, ytotals, highIdx);
g.setColor(Color.black);
g.drawString("maximum: " + max +
" bytes (total memory - blue line | free memory - red line)",
10, 35);
}
catch (Exception e) {
System.out.println("MemoryMonitor: " + e.getMessage( ));}
}
public void windowActivated(WindowEvent e){ }
public void windowClosed(WindowEvent e){ }
public void windowClosing(WindowEvent e) {System.exit(0);}
public void windowDeactivated(WindowEvent e){ }
public void windowDeiconified(WindowEvent e){ }
public void windowIconified(WindowEvent e){ }
public void windowOpened(WindowEvent e) { }
}
|