JDK-4475540 : GZipOutputStream leaks native memory
  • Type: Bug
  • Component: core-libs
  • Sub-Component: java.util
  • Affected Version: 1.3.1
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: solaris_8
  • CPU: sparc
  • Submitted: 2001-06-27
  • Updated: 2002-05-26
  • Resolved: 2002-05-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
1.3.1_02 02Fixed
Related Reports
Relates :  
Relates :  
Description

Name: bsC130419			Date: 06/27/2001


java version "1.3.1"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.3.1-b24)
Java HotSpot(TM) Client VM (build 1.3.1-b24, mixed mode)


GZipOutputStream leaks memory to varying degrees depending on configuration
of the HotspotVM. The memory is leaked into a native area (not the
Java heap) which we detected using the pmap command:

$ pmap 12389 | grep heap
00026000 421256K read/write/exec     [ heap ]

The code at the end can be used to reproduce the problem. The appearance
of the problem seems to be affected by VM memory and GC configuration
options as follows:

Case1:
java -server -Xmx700m -Xms700m -verbose:gc -Xss128k test
- native heap grows fast, eventually exhausts all available memory
- minor GC's occur every 5 seconds
- full GCs practically never

Case2:
java -server -Xincgc -Xmx700m -Xms700m -verbose:gc -Xss128k test
- native heap grows really fast, exhausts all available memory quickly
- incGC's occur 10-15 every 5 seconds
- full GC's never

Case3:
java -server -verbose:gc -Xss128k test
- native heap grows slowly, if at all, so no problem
- minor GC's occur every second or so
- full GC's occur every 3 secs

It looks like memory that is allocated inside the ZLIB library is leaked
when lots of minor and incgc's occur and there are no full GCs happening.

The code below can use DataOuputStreams or ObjectOutputStreams, I wanted
to make sure it wasnt the serialization. Case 3 isnt a practical workaround
because we naturally dont want full GC's and we need the big heaps.

import java.util.*;
import java.util.zip.*;
import java.io.*;
import java.net.*;

public class test {

    public static void main(String args[]) {

        Runnable r = new Runnable() {
            public void run() {
                test t = new test();
            }
        };

        Thread t = new Thread(r);
        t.start();
        t = new Thread(r);
        t.start();
        t = new Thread(r);
        t.start();
        t = new Thread(r);
        t.start();


    }

    public test() {
        try {
            while (true) {
                List l = new ArrayList();
                for (int i=0;i<1000;i++) {
                    String str = String.valueOf(Math.random());
                    l.add(str);
                }
                byte[] payload = compressData(l);
                System.out.print("c[" + payload.length + "]\n");
            }

        }
        catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    private byte[] compressData(Object o) throws Exception {
        ByteArrayOutputStream baos =  new ByteArrayOutputStream();
        GZIPOutputStream gos = new GZIPOutputStream(baos);
        DataOutputStream dos = new DataOutputStream(gos);
        //ObjectOutputStream oos = new ObjectOutputStream(gos);
        //oos.writeObject(o);
        List l = (List) o;
        for (int i=0;i<l.size();i++) {
            dos.writeUTF((String) l.get(i));
        }
        //oos.flush();
        gos.finish();
        gos.close();
        baos.close();
        return baos.toByteArray();
    }
}
(Review ID: 126664) 
======================================================================



A different test case has been attached. To run:

java -server -Xcomp -ms384m -mx512m GZIPBug ./storage_dir IM66 10

(Be sure to run purge.sh or you'll run out of disk space fairly quickly!)

Watch the heap growth of the process using pmap:
pmap 1439 | grep heap
00026000   4976K read/write/exec     [ heap ]
           ^^^^^
          watch this number

With the suggested fix, the heap area remains constant.

jane.loizeaux@East 2001-07-13

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: 1.3.1_02 FIXED IN: 1.3.1_02 INTEGRATED IN: 1.3.1_02 VERIFIED IN: 1.3.1_02
14-06-2004

WORK AROUND Name: bsC130419 Date: 06/27/2001 none ======================================================================
11-06-2004

PUBLIC COMMENTS Fixed by adding deflator.end() to DeflatorOutputStream, GZIPOutputStream, and ZipOutputStream close() method.
10-06-2004

EVALUATION An escalation associated with bug 4411230 seemed to be related to this problem, so I had a look. The native memory leak seems to NOT appear when full gc's occur. That made me suspect that not having finalizers run was the key. Sure enough... GZIPOutputStream extends DeflatorOutputStream, which contains a Deflator object, def. It has a finalize() method, which calls end(). The comment before end() says: /** * Closes the compressor and discards any unprocessed input. * This method should be called when the compressor is no longer * being used, but will also be called automatically by the * finalize() method. Once this method is called, the behavior * of the Deflater object is undefined. */ However, def.end() is never called by the GZIPOutputStream; it relies on a finalizer to do the cleanup. I fixed it in my test case by adding one line to GZIPOutputStream's close method: public void close() throws IOException { finish(); def.end(); <===== added this line out.close(); } I see that GZIPInputStream does call the end() method for its inflater object in the close() method. I believe the correct fix is to modify DeflatorOutputStream.close() to make the end call: public void close() throws IOException { finish(); def.end(); out.close(); } and to *remove* the override of close() from GZIPOutputStream.java. However, if the GZIPOutputStream.close needs to remain, it must be fixed in both places. jane.loizeaux@East 2001-07-13
13-07-2001

SUGGESTED FIX See evaluation. One line change required to GZIPOutputStream and DeflaterOutputStream.close() methods (or maybe only to DeflatorOutputStream's close() and we can remove the override of close() in GZIPOutputStream?). jane.loizeaux@East 2001-07-13
13-07-2001