src/share/classes/java/io/FileOutputStream.java

Print this page

        

@@ -67,11 +67,20 @@
      */
     private FileChannel channel;
 
     private final Object closeLock = new Object();
     private volatile boolean closed = false;
+    private static final ThreadLocal<Boolean> runningFinalize =
+        new ThreadLocal<>();
 
+    private static boolean isRunningFinalize() {
+        Boolean val;
+        if ((val = runningFinalize.get()) != null)
+            return val.booleanValue();
+        return false;
+    }
+
     /**
      * Creates a file output stream to write to the file with the
      * specified name. A new <code>FileDescriptor</code> object is
      * created to represent this file connection.
      * <p>

@@ -197,11 +206,11 @@
             throw new NullPointerException();
         }
         this.fd = new FileDescriptor();
         this.append = append;
 
-        fd.attach(this);
+        fd.incrementAndGetUseCount();
         open(name, append);
     }
 
     /**
      * Creates a file output stream to write to the specified file

@@ -234,11 +243,17 @@
         if (security != null) {
             security.checkWrite(fdObj);
         }
         this.fd = fdObj;
         this.append = false;
-        fd.attach(this);
+
+        /*
+         * FileDescriptor is being shared by streams.
+         * Ensure that it's GC'ed only when all the streams/channels are done
+         * using it.
+         */
+        fd.incrementAndGetUseCount();
     }
 
     /**
      * Opens a file, with the specified name, for overwriting or appending.
      * @param name name of file to be opened

@@ -323,17 +338,31 @@
             }
             closed = true;
         }
 
         if (channel != null) {
+            /*
+             * Decrement FD use count associated with the channel
+             * The use count is incremented whenever a new channel
+             * is obtained from this stream.
+             */
+            fd.decrementAndGetUseCount();
             channel.close();
         }
-        fd.closeAll(new Closeable() {
-            public void close() throws IOException {
+
+        /*
+         * Decrement FD use count associated with this stream
+         */
+        int useCount = fd.decrementAndGetUseCount();
+
+        /*
+         * If FileDescriptor is still in use by another stream, the finalizer
+         * will not close it.
+         */
+        if ((useCount <= 0) || !isRunningFinalize()) {
                close0();
            }
-        });
     }
 
     /**
      * Returns the file descriptor associated with this stream.
      *

@@ -343,13 +372,11 @@
      *
      * @exception  IOException  if an I/O error occurs.
      * @see        java.io.FileDescriptor
      */
      public final FileDescriptor getFD()  throws IOException {
-        if (fd != null) {
-            return fd;
-        }
+        if (fd != null) return fd;
         throw new IOException();
      }
 
     /**
      * Returns the unique {@link java.nio.channels.FileChannel FileChannel}

@@ -370,10 +397,17 @@
      */
     public FileChannel getChannel() {
         synchronized (this) {
             if (channel == null) {
                 channel = FileChannelImpl.open(fd, false, true, append, this);
+
+                /*
+                 * Increment fd's use count. Invoking the channel's close()
+                 * method will result in decrementing the use count set for
+                 * the channel.
+                 */
+                fd.incrementAndGetUseCount();
             }
             return channel;
         }
     }
 

@@ -388,18 +422,24 @@
     protected void finalize() throws IOException {
         if (fd != null) {
             if (fd == FileDescriptor.out || fd == FileDescriptor.err) {
                 flush();
             } else {
-                /* if fd is shared, the references in FileDescriptor
-                 * will ensure that finalizer is only called when
-                 * safe to do so. All references using the fd have
-                 * become unreachable. We can call close()
+
+                /*
+                 * Finalizer should not release the FileDescriptor if another
+                 * stream is still using it. If the user directly invokes
+                 * close() then the FileDescriptor is also released.
                  */
+                runningFinalize.set(Boolean.TRUE);
+                try {
                 close();
+                } finally {
+                    runningFinalize.set(Boolean.FALSE);
             }
         }
+        }
     }
 
     private native void close0() throws IOException;
 
     private static native void initIDs();