JDK-6558476 : com/sun/tools/javac/Main.compile don't release file handles on return
  • Type: Bug
  • Component: tools
  • Sub-Component: javac
  • Affected Version: 6,6u17-rev,6u25
  • Priority: P2
  • Status: Closed
  • Resolution: Fixed
  • OS: solaris_2.5.1,windows,windows_xp
  • CPU: x86,sparc
  • Submitted: 2007-05-17
  • Updated: 2012-10-01
  • Resolved: 2012-04-05
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.
JDK 6 JDK 7 Other
6u27Fixed 7 b71Fixed OpenJDK6Fixed
Related Reports
Duplicate :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
Relates :  
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
15-11-2007

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()).
05-11-2007

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.
25-10-2007

EVALUATION BEA is interested in any update on this issue. Can a finalizer help?
25-10-2007

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.
10-09-2007

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.
10-09-2007

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.
07-08-2007

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

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.
18-07-2007