Return-Path: Received: from fort-point-station.mit.edu by po10.mit.edu (8.9.2/4.7) id QAA19138; Fri, 22 Dec 2000 16:02:11 -0500 (EST) Received: from hermes.java.sun.com (hermes.java.sun.com [204.160.241.85]) by fort-point-station.mit.edu (8.9.2/8.9.2) with ESMTP id QAA17933; Fri, 22 Dec 2000 16:02:31 -0500 (EST) Received: (from nobody@localhost) by hermes.java.sun.com (8.9.3+Sun/8.9.1) id UAA28642; Fri, 22 Dec 2000 20:59:30 GMT Date: Fri, 22 Dec 2000 20:59:30 GMT Message-Id: <200012222059.UAA28642@hermes.java.sun.com> X-Mailing: 327 From: JDCTechTips@sun.com Subject: JDC Tech Tips December 22, 2000 To: JDCMember@sun.com Reply-To: JDCTechTips@sun.com Errors-To: bounced_mail@hermes.java.sun.com Precedence: junk Mime-Version: 1.0 Content-Type: text/plain; charset=us-ascii Content-Transfer-Encoding: 7bit X-Mailer: Beyond Email 2.2 J D C T E C H T I P S TIPS, TECHNIQUES, AND SAMPLE CODE WELCOME to the Java Developer Connection(sm) (JDC) Tech Tips, December 22, 2000. This issue covers techniques for tracking and controlling memory allocation in the Java HotSpot(tm) Virtual Machine*. The topics covered are: * A Memory Testbed Application * Controlling Your Memory Manager This tip was developed using Java(tm) 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). You can view this issue of the Tech Tips on the Web at http://java.sun.com/jdc/TechTips/2000/tt1222.html - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - A MEMORY TESTBED APPLICATION Memory management can have a dramatic effect on performance, and most virtual machines expose a set of configuration options that you can tweak for the best possible performance of your application on a particular platform. To investigate this, let's examine a simple application for allocating and unreferencing blocks of memory. This application will be used in the second tip ("Controlling Your Memory Manager") to demonstrate some of the configuration options in the VM for memory management. import java.io.*; import java.util.*; 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(tm) 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. - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - CONTROLLING YOUR MEMORY MANAGER 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(tm) 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(tm) 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 Microsystems(tm) purposes only. You have received this email because you elected to subscribe. To unsubscribe, go to the Subscriptions page (http://developer.java.sun.com/subscription/), uncheck the appropriate checkbox, and click the Update button. - SUBSCRIBE To subscribe to a JDC newsletter mailing list, go to the Subscriptions page (http://developer.java.sun.com/subscription/), 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 JDC Tech Tips December 22, 2000 * As used in this document, the terms "Java virtual machine" or "JVM" mean a virtual machine for the Java platform.