|
December 22, 2000 WELCOME to the Java Developer ConnectionSM (JDC) Tech Tips, December 22, 2000. This issue covers techniques for tracking and controlling memory allocation in the Java HotSpotTM Virtual Machine*. The topics covered are:
These tips were developed using Java 2 SDK, Standard Edition, v 1.3. This issue of the JDC Tech Tips is written by Stuart Halloway, a Java specialist at DevelopMentor (http://www.develop.com/java)
|
public class MemWorkout {
private static final int K = 1024;
private int maxStep;
private LinkedList blobs = new LinkedList();
private long totalAllocs;
private long totalUnrefs;
private long unrefs;
public String toString() {
return "MemWorkout allocs=" + totalAllocs + "
unrefs=" + totalUnrefs;
}
private static class Blob {
public final int size;
private final byte[] data;
public Blob(int size) {
this.size = size;
data = new byte[size];
}
}
private void grow(long goal) {
long totalGrowth = 0;
long allocs = 0;
while (totalGrowth < goal) {
int grow = (int)(math.random() * maxstep);
blobs.add(new blob(grow));
allocs++;
totalgrowth += grow;
}
totalallocs += allocs;
system.out.println("" + allocs + " allocs, " +
totalgrowth + " bytes");
}
private void shrink(long goal) {
long totalshrink = 0;
unrefs = 0;
try {
while (totalshrink < goal) {
totalshrink += shrinknext();
}
} catch (nosuchelementexception nsee) {
system.out.println("all items removed");
}
totalunrefs+= unrefs;
system.out.println("" + unrefs + " unreferenced
objs, " + totalshrink + " bytes");
}
private long shrinknext() {
//choice of fifo/lifo very important!
blob b = (blob) blobs.removefirst();
//blob b = (blob) blobs.removelast();
unrefs++;
return b.size;
}
public memworkout(int maxstep) {
this.maxstep = maxstep;
}
public static void main(string [] args) {
if (args.length < 1) {
throw new error ("usage MemWorkout maxStepKB");
}
int maxstep = integer.parseint(args[0]) * k;
if (maxstep < (k)) throw new error("maxStep must
be at least 1KB");
memworkout mw = new memworkout(maxstep);
try {
while (true) {
bufferedreader br = new bufferedreader(new
inputstreamreader(system.in));
logmemstats();
system.out.println("{intMB} allocates, {-intMB}
deallocates, GC collects garbage, EXIT exits");
string s = br.readline();
if (s.equals("GC")) {
system.gc();
system.runfinalization();
continue;
}
long alloc = integer.parseint(s) * 1024* 1024;
if (alloc > 0) {
mw.grow(alloc);
} else {
mw.shrink(-alloc);
}
}
} catch (NumberFormatException ne) {
} catch (Throwable t) {
t.printStackTrace();
}
System.out.println(mw);
}
public static void logMemStats() {
Runtime rt = Runtime.getRuntime();
System.out.println("total mem: " +
(rt.totalMemory()/K) + "K free mem:
" + (rt.freeMemory()/K) + "K");
}
}
|
To run MemWorkout, specify it with a number argument, like this:
java MemWorkout 5
In response, you should see something like this:
total mem: 1984K free mem: 1790K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
The first line of output indicates the total available memory and the total amount of free memory. The second line is a prompt. You can respond to the prompt in one of four ways. If you enter a positive number, MemWorkout loads the system with approximately that many megabytes by adding "Blob" objects to a blobs list. The size of a Blob is a random number between 0 and the value of the initial argument you specified to MemWorkout, in kilobytes. So for the value 5, the size of each new Blob added to the list is 5 kilobytes.
If you enter a negative number in response to the prompt, MemWorkout attempts to unload the system of that amount of megabytes by removing Blobs from the list.
You can also enter GC to run System.gc() and
System.runFinalization(), or EXIT to exit the application.
For example, a MemWorkout session that adds 50MB of load, drops 25MB, and then collects garbage would look something like this:
java MemWorkout 5
total mem: 1984K free mem: 1790K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
50
20617 allocs, 52430544 bytes
total mem: 64320K free mem: 11854K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
-25
10312 unreferenced objs, 26216866 bytes
total mem: 64320K free mem: 11828K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
GC
total mem: 65280K free mem: 38976K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
EXIT
MemWorkout allocs=20617 unrefs=10312
|
This session exercises the Java HotSpot Client VM, which is part of Java 2 SDK, Standard Edition, v 1.3. The session demonstrates several interesting things about the HotSpot VM. First, notice that total memory increases immediately to meet the 50MB allocation. Second, notice that free memory is not immediately reclaimed when 25MB worth of objects are removed. Instead the free memory is reclaimed when the garbage collector is requested through System.gc(). The configuration options described in the next tip ("Controlling Your Memory Manager") give you several choices for controlling these behaviors.
Garbage collection performance can be very important to the overall performance of an application written in the Java programming language. The most primitive memory management schemes use a "stop-the-world" approach, where all other activity in the VM must halt while all objects in the system are scanned. This can cause a noticeable pause in program execution. Even when delays do not come in large, user-irritating chunks, the overall time spent collecting garbage can still impact performance. This tip uses the MemWorkout class to demonstrate the following memory management flags. You can use these flags to tune the garbage collection performance of the HotSpot VM in the Java 2 SDK v 1.3:
| Flags |
Purpose |
|||
| -Xms and -Xmx | Control system memory usage | |||
| -verbose:gc | Trace garbage collection activity | |||
| -XX:NewSize | Control the nursery starting size | |||
| -Xincgc and -Xnoincgc | Turn on, or off, incremental garbage collection |
***Warning*** The -X flags are non-standard options, and are
subject to change in future releases of the Java 2 SDK. Also,
the -XX flag is officially unsupported, and subject to change
without notice.
Perhaps the most crucial setting in memory management is the
maximum total memory allowed to the VM. If you set this lower than
the maximum memory needed by the VM, your application will fail
with an OutOfMemoryError exception. More subtly, if you set the
maximum memory too close to your application's memory usage,
performance might degrade significantly. (Although there are
many different garbage collection algorithms, most perform poorly
when memory is almost full.) The HotSpot VM default for initial
memory allocation is 2MB. By default, HotSpot gradually increases
memory allocation up to 64MB; any memory request above 64MB fails.
You can control the initial memory setting with the -Xms flag, and
control the maximum setting with the -Xmx flag. Try these flags
out in a MemWorkout session. (The MemWorkout class is described in
the previous tip, "A Memory Testbed Application.") Start
MemWorkout as follows:
java MemWorkout 5
Then respond to the MemWorkout prompt with the number 32, this means allocate 32MB. After the response to the entry, enter 32 again, for another 32MB allocation. (To keep the text short, the rest of the MemWorkout sessions in this tip will list only the command line entries. So, for example, a MemWorkout session with two entries of 32 will be abbreviated to "32,32".) Running MemWorkout this way should generate an OutOfMemoryError exception because the two 32MB allocations, plus the overhead of the application and VM, are easily greater than 64MB.
To fix this problem, try MemWorkout again, but this time specify
the -Xmx flag as follows:
java -Xmx80m MemWorkout 5
Then run the session as before, that is, "32,32". The 80m argument indicates that the VM can use a maximum of 80MB. This time MemWorkout should succeed.
If you know that the memory footprint of your application remains
almost constant for the life of the application, specify a
starting memory allocation that is higher than the starting
default. You specify the starting allocation with the -Xms flag.
This saves the startup overhead of working up from 2MB. The
following command specifies both a starting and maximum allocation
of 80MB. This guarantees that the virtual machine will grab 80MB
of system memory at startup and keep it for the lifetime of the
application:
java -Xms80m -Xmx80m MemWorkout 5
The -verbose:gc flag causes the VM to log garbage collection
activity. Instead of guessing when and how your program interacts
with the garbage collector, you can use this flag to track it. Try
running MemWorkout with the -verbose:gc flag, as follows:
java -verbose:gc MemWorkout 5
Then run the session as before, that is, "32,32". You should see trace output from the garbage collector similar to this:
total mem: 1984K free mem: 1790K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
32
[GC 508K->432K(1984K), 0.0128943 secs]
[GC 940K->939K(1984K), 0.0061460 secs]
[GC 1450K->1450K(1984K), 0.0057276 secs]
[GC 1959K->1959K(2496K), 0.0056435 secs]
[Full GC 2471K->2471K(3772K), 0.0276593 secs]
etc.
|
You'll probably see many indications of garbage collection,
indicated by [GC ...]. You might wonder why so many garbage
collections are done. The answer is that before the virtual
machine asks for more memory from the system, it tries to reclaim
some of the memory it already has. It does this by running the
garbage collector. If you run the same application with 80MB
preallocated, as in the following example, some of the calls to
the garbage collector should disappear:
java -verbose:gc -Xms80m -Xmx80m MemWorkout 5
total mem: 81664K free mem: 81470K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
32
[GC 2046K->1970K(81664K), 0.0240181 secs]
etc...
[GC 32669K->32669K(81664K), 0.0220730 secs]
13200 allocs, 33558101 bytes
|
This time you should see fewer garbage collections. Also, you
should not see any full garbage collections (indicated by
[FULL GC...]). Full garbage collections tend to be the most
expensive in terms of performance.
Intuitively, garbage collection should run when memory is low. Because the MemWorkout application above starts with 80MB and only allocates 32MB, the VM is never low on memory. So why are there still some calls to the garbage collector? The answer is that the HotSpot VM collector is generational. Generational collectors take advantage of the reasonable assumption that young objects are likely to die soon (think local variables). So instead of collecting all of memory, generational collectors divide memory into two or more generations. When the youngest generation, or "nursery," is nearly full, a partial garbage collection is done to reclaim some of the young objects that are no longer reachable. This partial garbage collection is usually much faster than a full garbage collection; it postpones the need for a full gc. Generational gc can dramatically reduce both the duration and frequency of full gc pauses.
The initial size of the object nursery is configurable; the documentation often refers to it as the "eden space." On a SPARCstation, the new generation size defaults to 2.125MB; on an Intel processor, it defaults to 640k. Try to configure MemWorkout so that it runs without any need for garbage collection. To do that, make the nursery large enough so that the entire application usage fits easily in the nursery. The session should look something like this:
java -verbose:gc -Xms80m -Xmx80m -XX:NewSize=60m
MemWorkout 5
total mem: 75776K free mem: 75582K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
32
13118 allocs, 33555324 bytes
total mem: 75776K free mem: 42073K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
|
The -XX:NewSize flag sets the initial nursery size to 60MB. This
accomplishes the objective; the lack of gc trace output indicates
that the nursery never needed collection. Of course, it is
unlikely that you would ever set the nursery so large. Like every
good thing in life, the size of the nursery involves a painful
tradeoff. If you make the nursery too small, objects get moved
into older generations too quickly, clogging the older generations
with dead objects. This situation forces a full gc earlier than
would otherwise be needed. But a large nursery causes longer
pauses, eventually approaching the length of a full gc. There is no
magic formula. Use -verbose:gc to observe the memory behavior of
your application, and then make small, incremental changes to the
nursery size and measure the results. Remember too that HotSpot
is adaptive and will dynamically adjust the nursery size in
long-running applications.
In addition to being generational, the HotSpot VM can also run in
incremental mode. Incremental gc divides the entire set of objects
into smaller sets, and then processes these sets incrementally.
Like generational gc, incremental gc aims to make pause times
smaller by avoiding long pauses to trace most or all objects.
However, incremental gc's advantages accrue regardless of the age
of the object. The disadvantage of incremental gc is that even
though collection is divided into smaller pauses, the overall cost
of garbage collection can be substantially higher, causing
throughput to decrease. This tradeoff is worthwhile for
applications that must make response time guarantees, such as
applications that have user interfaces. Incremental gc defaults to
"off." You can turn it on with the -Xincgc flag.
To see incremental gc in action, try a MemWorkout session that begins by
adding 32MB, and then adds and unreferences several fairly small chunks:
java -verbose:gc -Xms80m -Xmx80m -Xincgc MemWorkout 5
total mem: 640K free mem: 446K
{intMB} allocates, {-intMB} deallocates, GC collects
garbage, EXIT exits
32
[GC 511K->447K(960K), 0.0086260 secs]
[GC 959K->964K(1536K), 0.0075505 secs]
(many more GC pauses!)
|
Notice that the initial 32MB allocation of system memory causes a large number of incremental gc pauses. However, while there are more pauses, they are an order of magnitude faster than the other gc pauses you probably have seen. The pauses should be down in the millisecond range instead of the tens of milliseconds. Also notice that occasionally, unreferencing will appear to cause an incremental gc. This happens because unlike the other forms of garbage collection, incremental gc does not run primarily when memory is full (or when a segment of memory such as the nursery is almost full). Instead, incremental gc tries to run in the background when it sees an opportunity.
Tuning the memory management of the HotSpot VM is a complex task. HotSpot learns over time, and adjusts its behavior to get better performance for your specific application. This is an excellent feature, but it also makes it more difficult to evaluate the output from simple benchmarks such as the MemWorkout class presented in this tip. To gain a real understanding of HotSpot's interactions with your code, you need to run tests that approximate your application's behavior, and run them for long periods of time.
This tip has shown just a sampling of the memory settings available for the HotSpot VM. For further information about HotSpot VM settings, see the Java HotSpot VM Options page (http://java.sun.com/j2se/docs/VMOptions.html). Also see the HotSpot FAQ (http://java.sun.com/docs/hotspot/PerformanceFAQ.html).
The book "Java Platform Performance: Strategies and Tactics" by Steve Wilson and Jeff Kesselman (http://java.sun.com/jdc/Books/performance/) includes two appendixes that are valuable in learning more about memory management. One appendix gives an overview of garbage collection; the second introduces the HotSpot VM.
Richard Jones and Rafael Lins's "Garbage Collection" page (http://www.cs.ukc.ac.uk/people/staff/rej/gc.html) provides a good survey of gc algorithms, and a gc biliography.
Note
Sun respects your online time and privacy. The Java Developer Connection mailing lists are used for internal Sun MicrosystemsTM purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), uncheck the appropriate checkbox, and click the Update button.
Subscribe
To subscribe to a JDC newsletter mailing list, go to the Subscriptions page (https://softwarereg.sun.com/registration/developer/en_US/subscriptions), choose the newsletters you want to subscribe to, and click Update.
Feedback
Comments? Send your feedback on the JDC Tech Tips to: jdc-webmaster@sun.com
Archives
You'll find the JDC Tech Tips archives at:
http://java.sun.com/jdc/TechTips/index.html
Copyright
Copyright 2000 Sun Microsystems, Inc. All rights reserved.
901 San Antonio Road, Palo Alto, California 94303 USA.
This document is protected by copyright. For more information, see:
http://java.sun.com/jdc/copyright.html
* As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.
JDC Tech Tips December 22, 2000
|
| ||||||||||||