Return-Path: <nobody@hermes.java.sun.com>
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.





