United StatesChange Country, Oracle Worldwide Web Sites Communities I am a... I want to...
Bug ID: 6558476 com/sun/tools/javac/Main.compile don't release file handles on return
6558476 : com/sun/tools/javac/Main.compile don't release file handles on return

Details
Type:
Bug
Submit Date:
2007-05-17
Status:
Closed
Updated Date:
2012-10-01
Project Name:
JDK
Resolved Date:
2012-04-05
Component:
tools
OS:
solaris_2.5.1,windows_xp,windows
Sub-Component:
javac
CPU:
x86,sparc
Priority:
P2
Resolution:
Fixed
Affected Versions:
6,6u17-rev,6u25
Fixed Versions:
7

Related Reports
Backport:
Backport:
Duplicate:
Relates:
Relates:
Relates:
Relates:
Relates:
Relates:
Relates:

Sub Tasks

Description
File handles are not released when calling the com.sun.tools.javac.Main.compile method in version 6. The handles are com/sun/tools/javac/Main.compile don???t release file handles on return

File handles are not released when calling the com.sun.tools.javac.Main.compile method in version 6. The handles are released when the object is finalized.

The object is instantiated on line 546 in com.sun.tools.javac.util.DefaultFileManager and should be closed when no longer in use.

This results in file handles being kept to files on the class path during compilation. Operations on these files then fail.

The following code reproduces the issue. When running this example make sure to specify a large enough java heap the JVM to lower the finalization rate.

The code below reproduces the issue. Compile and run with:
%JAVA_HOME%\bin\java.exe -Xmx512m -Xms512m -cp bin;%JAVA_HOME%\lib\tools.jar JavacWithJar

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Random;

import com.sun.tools.javac.Main;

public class JavacWithJar {

   private static File copyFileTo(File file, File directory) throws IOException {
      File newFile = new File(directory, file.getName());
      FileInputStream fis = null;
      FileOutputStream fos = null;
      try {
         fis = new FileInputStream(file);
         fos = new FileOutputStream(newFile);
         byte buff[] = new byte[1024];
         for (int val; (val = fis.read(buff)) > 0; fos.write(buff, 0, val)) {}
      } finally {
         if (fis != null) fis.close();
         if (fos != null) fos.close();
      }
      return newFile;
   }
   
   private static String generateJavaClass(String className) {
      StringBuffer sb = new StringBuffer();
      sb.append("import sun.net.spi.nameservice.dns.DNSNameService;\n");
      sb.append("public class ");
      sb.append(className);
      sb.append(" {\n");
      sb.append("  public void doStuff() {\n");
      sb.append("    DNSNameService dns = null;\n");
      sb.append("  }\n");
      sb.append("}\n");
      return sb.toString();
   }
   
   public static void main(String[] args) throws IOException {
      File tmpDir = new File(System.getProperty("java.io.tmpdir"));
      File javaHomeDir = new File(System.getProperty("java.home"));
      
      File outputDir = new File(tmpDir, "outputDir" + new Random().nextInt(65536));
      outputDir.mkdir();
      outputDir.deleteOnExit();

      File dnsjarfile = new File(javaHomeDir, "lib" + File.separator + "ext" + File.separator + "dnsns.jar");
      File tmpJar = copyFileTo(dnsjarfile, outputDir);
            
      String className = "TheJavaFile";
      File javaFile = new File(outputDir, className + ".java");
      javaFile.deleteOnExit();
      FileOutputStream fos = new FileOutputStream(javaFile);
      fos.write(generateJavaClass(className).getBytes());
      fos.close();

      int rc = Main.compile(new String[]{"-d", outputDir.getPath(),
               "-classpath",
               tmpJar.getPath(),
               javaFile.getAbsolutePath()});
      if (rc != 0) {
         System.out.println("Couldn't compile the file (exit code=" + rc + ")");
      }
      if (!tmpJar.delete()) {
         System.out.println("Error deleting file \"" + tmpJar.getPath() + "\"");
      }
   }
}

                                    

Comments
EVALUATION

Fixed by providing a list of class loaders to try:

1) allow user to specify classloader class via a hidden option
2) check if URLClassLoader implements Closeable, and if so, use it
3) use a private subtype of URLClassLoader that implements Closeable using reflection to access the (private) fields pointing at the jar files that need to be closed
4) fall back to standard URLClassLoader
                                     
2007-11-15
EVALUATION

The problem appears to be in sun.misc.URLClassPath, which is used by java.net.URLClassLoader.

One might reasonably presume that javac could clone URLClassPath (to have a close() method, to free resources) and URLClassLoader (to have a close method to call URLClassPath.close()).
                                     
2007-11-05
EVALUATION

BEA is interested in any update on this issue.

Can a finalizer help?
                                     
2007-10-25
WORK AROUND

Ignore my prior comments - 
One possible workaround/answer is to call close() or an appropriate "closer" right after flush() in JavaCompiler around if this is the issue, and make sure that it takes care of the URLClassLoader,

       try {
            fileManager.flush();
        } catch (IOException e) {

I have not tried it on XP and it does not reproduce on larger machines possibly.
                                     
2007-10-25
WORK AROUND

If you don't need the compiler to perform annotation processing, you can use -proc:none to disable the compiler's support for annotation processing.
                                     
2007-09-10
EVALUATION

The problem is nothing to do with the file manager, which is working as expected.

The problem appears to be in java.net.URLClassLoader.  The compiler creates a URLCLassLoader so that it can load any necessary annotation processors. It would appear that the URLClassLoader is holding on to the jar files, until it is garbage-collected. There is no API that javac can use to "close" a URLClassLoader.
                                     
2007-09-10
EVALUATION

The test program provided works fine for me, on Windows 2000.
                                     
2007-08-07
EVALUATION

I've instrumented the test program to verify that it is working as expected, and it is. 
This corresponds to manual examination of the code, which indicates that the file manager should be closed in main/Main.java at round about line 300.

Closing the bug as Not Reproducible.
                                     
2007-08-07
EVALUATION

JavaCompiler.close() calls DefaultFileManager.flush() not DefaultFileManager.close().  This is probably deliberate because of JSR 199 -- we don't want to unnecessarily close a JavaFileManager passed in by a client.

However, the JavaFileManager used by the compile method in question *is* supposed to be closed, by the code in main.Main.compile, round about line 301, and closing the file manager should close any archives opened by the file manager.

Needs more investigation.
                                     
2007-07-18



Hardware and Software, Engineered to Work Together