JDK-4065018 : (gc) Require heap compaction (return memory to OS)
  • Type: Enhancement
  • Component: hotspot
  • Sub-Component: runtime
  • Affected Version: 1.1.3,1.1.4,1.1.5,1.2.0
  • Priority: P3
  • Status: Closed
  • Resolution: Fixed
  • OS:
    solaris_2.5.1,solaris_2.6,windows_95,windows_nt solaris_2.5.1,solaris_2.6,windows_95,windows_nt
  • CPU: generic,x86,sparc
  • Submitted: 1997-07-16
  • Updated: 2012-10-13
  • Resolved: 1998-08-26
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other Other Other
1.1.7 b04Fixed 1.1.8Fixed 1.2.0Fixed
Related Reports
Relates :  
Relates :  
Description
Name: rlT66838			Date: 07/16/97

Our application is designed to remain running for long periods of
time, and the current JVM implementation (where memory is 
garbage-collected back to the heap, but never released from the
heap back to the OS) is *killing* us.


======================================================================

"I have written a test app which creates some number of objects on a
button click.  When run with '-verbosegc', I see the output where the gc
is telling me it's doing its thing and compacted x bytes.  Yet, no matter
how many times I click my app, I never see any change in the mem usage in
Task Manager (NT 4.0).  In this trivial test app, it's hard to see
profound problems.  What's a few K of memory between friends?  But in our
real-life app, it's virtually impossible to run it for more than a few
hours without getting an out of mem exception.  [That is, before we
created an object recycler and reuse absolutely every object we create.]
And, since this is a mission critical app, that kinda sucks.

"I'm aware (and have used) the flag to set the max heap size.  That's not
the problem.  If I run the VM with the '-verbosegc' flag, I can watch the
garbage collector run, but neither the VM's mem usage or VM Size change in
Task Mgr.  Ever.  I've run numerous test cases and have only seen those
values increase, never decrease.  Well, that's not exactly true - they
both decrease if you minimize the window(!).  I don't understand that but
it might help diagnose the problem.

"I'm quite certain this is not a result of memory leaks because the
'-verbosegc' output clearly says memory was compacted on the heap.

This customer is using JDK 1.1.4 (they looked at 1.1.5 and the problem
persists).


import java.awt.*;
import java.awt.event.*;

/*

  This little app simply allocates some stuff when it's button is pressed.

  This demonstrates the VM bug where objects are garbage collected but that
  memory is never returned to the OS (this has only been tested on NT 4.0).

  You can clearly see this by running the VM with -verbosegc
  (java -verbosegc -classpath .;\java\lib\classes.zip MemTestApp).

*/

public class MemTestApp {

  public static void main(String[] args) {
    MemTestFrame memTestFrame = new MemTestFrame();
    Dimension dim = new Dimension(300,300);
    memTestFrame.setSize(300,300 /*dim*/);
    memTestFrame.init();
    memTestFrame.show();
  }

}

class MemTestFrame extends Frame implements ActionListener {

  private Font font = null;
  private Button button;
  private String message = "Mem Test";

  public void init() {
    setTitle(message);
    setVisible(true);
    button = new Button();
    add(button);
    button.setLabel("Click to alloc 500 objects...");
    button.addActionListener(this);
  }

  public void actionPerformed(ActionEvent evt) {
    System.out.println("------> allocing 500 objects...");
    MemObj obj;
    for (int i = 0; i < 50; i++)
      obj = new MemObj();
    System.out.println("------> done.");
  }

}

class MemObj {
  Font font;
  Rectangle rect;
  java.math.BigDecimal number;


  public MemObj() {
    font = new Font("Dialog", Font.PLAIN, 16);
    rect = new Rectangle(0,0,50,50);
  }
}


janet.koenig@Eng 1998-01-13

Response to request for test case:

Any java program shows this behaviour.  If memory is allocated by the
virtual machine from the operating system it is never returned to the
operating system until the process exits.  Compare this to Microsoft's VM
behaviour where its size is directly related to how many objects are live
in the system.

Another response:

IMHO, the problem is that the VM is allowed to expand the heap (and it
should), but that it does not contract.  Of course, it should be
reluctant to do either, and I have seen that it takes a good while to
decide to go get more memory...

Anyway, with the current memory model, the EFFECTIVE working set size
of a java app is the same as its virtual space (sigh, hard to be
worse).  This is not a problem for toys, but for SERVER applications,
this can be a problem.  My application grows to over 80MB.  As you
might imagine, SERIOUS thrashing occurs.

If, at an appropriate time, memory were compacted and "excess"
returned to the OS, the working set size would be reduced.

Of course, other garbage techniques (like generational) would help a
great deal too.


Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: 1.1.8 1.2beta4 FIXED IN: 1.1.7b_004 1.1.8 1.2beta4 INTEGRATED IN: 1.1.7b_004 1.1.8 1.2beta4 VERIFIED IN: 1.1.8
14-06-2004

SUGGESTED FIX Summary of suggested fix for backport to 1.1.8. For complete source diffs, see diffs118.Z in Attachments section. ------------------------------------------------------------------ build/win32/java/java/exports.lcf - exported functions from javai.dll src/share/java/include/jni.h - prototype for functions that initialize and set heap commands line option globals src/share/java/include/interpreter.h - heap command-line option #define and typedef for javai.dll src/share/java/include/sys_api.h - add sysCommitMem() prototype src/share/java/jre/jre.h - heap command-line option typedef for jre src/share/java/jre/jre_main.c - parse heap parameters for jre src/share/java/runtime/gc.c - backported heap expansion and shrinking changes src/share/java/runtime/jni.c - functions that initialize heap command-line options and set global src/share/java/util/globals.c - heap option global src/solaris/java/javai/javai.c src/win32/java/main/javai.c - parse heap parameters for java src/solaris/java/jre/jre_md.c src/win32/java/jre/jre_md.c - initialize and set heap options for jre src/solaris/java/runtime/memory_md.c src/win32/java/runtime/memory_md.c - fix size calculation bug in sysDecommitMem Several SCCS revisions to JDK1.2 are associated with bug fix for bug 4065018 and backported to 1.1.8(listed below). In addition, parsing of command-line options was changed from 1.1.8 to 1.2. The parameter parsing was put into shared code in one place for 1.2 for both JRE and JAVA. (share/javavm/runtime/javai.c) For 1.1.x, the options are parsed separately for jre and for java in files src/share/java/jre/jre_main.c, solaris/java/javai/javai.c, win32/java/main/javai.c. Also, for win32, the global variables that contain the values for heap expansion and shrinkage must be exported from the java VM within functions identified in build/win32/java/java/export.lcf. D 1.236 98/05/12 23:08:04 never 441 440 00325/00192/05927 4065018 (gc) Require heap compaction (return memory to OS) D 1.238 98/05/19 23:41:04 sl 443 442 00006/00004/06378 Fix assertion checks, and off by 4 error. D 1.239 98/05/21 22:48:04 hzhang 444 443 00020/00012/06372 Fix for bug 4140653. (gc) VM crashed in the gc during the build. D 1.238.1.1 98/06/11 09:21:38 sideout 445 443 00002/00002/06382 4140961: gc may not mark objects refered to by the last slot in the handle pool. D 1.241 98/06/19 10:08:16 never 448 446 00001/00001/06391 4150656 bad assertion in gc.c D 1.256 98/10/27 16:03:14 never 469 468 00016/00006/06294 fixed 4182896 GC may unmap wrong piece of memory causing NT Application Failure
11-06-2004

PUBLIC COMMENTS In the JVM, memory is garbage-collected back to the heap, but never released from the heap back to the OS.
10-06-2004

EVALUATION This bug description is too vague to determine what the actual problem might be. It could be that the user does not realize that the Java heap only grows in size up to a defined maximum. Most likely people are seeing memory leaks which cause the Java process to get very large and think that it's because we aren't returning memory to the system. Several attempts have been made by Engineering to contact the original submitter in order to get more information, but there has been no reply and on subsequent attempt, the email bounced. The JDC will be accepting any valid test cases which can be used to clarify this problem report. <end of original anonymous evaluation> The above evaluation is incorrect, and we know exactly what the problem is. The Java heap will expanded as needed by the computation, but will never contract once the increased heap size is no longer needed. We do not return heap memory to the system once allocated until the process exits. It is relatively easy to fix this. The user's view could continue to be driven by the -ms and -mx flags, but on heap contraction the heap would tend down to the minimum, which is the maximum of the amount needed by the computation plus any preallocation buffer, and the value of the -ms flag. That is, the -ms flag, in addition to setting the heap startup size, would also control the minimum it would shrink down to. The compactor is already set up to return the last free object in the heap, which is what memory would be given back from, and there is already an HPI function to free memory. Of course, systems using a contiguous heap based on malloc rather than memory mapping would be out of luck. timothy.lindholm@Eng 1998-01-14 Fxing this in the 1.1 codeline is considered too risky currently. We will fix it in 1.2beta4. If someday in the future the fix is considered well understood, we may be able to backport it to a later 1.1.x release, but for now it will not be fixed in 1.1.x. tom.rodriguez@Eng 1998-02-06 ---------------------------- Cannot verify this bug. Although the regression test exists in vm/gc/HeapShrink.java , there are no instructions nor anything resulting from running the test, that would indicating it fixed. al.smith@eng 1999-02-26 ----------------------------------------------------------------------- This bug was verified as fixed via manual testing using jdk118l. I made some slight modification to the HeapShrink.sh file to enable it to run manually. al.smith@eng 1999-03-29
29-03-1999

WORK AROUND Name: rlT66838 Date: 07/16/97 ========================= No acceptable workaround. "The only way they were able to workaround this bug was to militantly re-use objects." ========================= mark.chamness@Eng 1998-01-28 A trivial but crude workaround is to use the system as the garbage collecting mechanism. The java code can start another java process that is the memory hog, and when that process exits, that memory is returned to the system. Example follows: WorkAround.java -------------------------------------------------------------------- import java.io.*; public class WorkAround { public WorkAround () throws IOException { Runtime r = Runtime.getRuntime(); String output; while(true) { System.out.println("Press return to run."); System.in.read(); System.in.read(); String command = "java SimpleMain"; Process p = r.exec(command); BufferedReader br = new BufferedReader( new InputStreamReader(p.getInputStream())); while((output=br.readLine())!=null) System.out.println(output); try { p.waitFor(); } catch(InterruptedException ie) { System.out.println(ie); } } } static PrintStream err = System.out; public static void main(String args[]) { try { new WorkAround(); } catch(IOException ioe) { err.println(ioe); } } } -------------------------------------------------------------------- The following is the memory-hogging code. SimpleMain.java -------------------------------------------------------------------- public class SimpleMain { public SimpleMain (int count) throws Exception { int array[] = new int[1000]; for (int i = 0; i < count; i++) { System.out.print("Performing alloc number " + (i + 1) + " of " + count + "..."); array = new int[array.length * 2]; System.out.println("done."); } } public static void main(String args[]) throws Exception { if (args != null && args.length > 0) { new SimpleMain(Integer.parseInt(args[0])); } else { new SimpleMain(10); } } } -------------------------------------------------------------------- mark.chamness@Eng 1998-01-28
28-01-1998