Java 匿名文件流重用描述符

Java 匿名文件流重用描述符,java,garbage-collection,Java,Garbage Collection,以下代码块(每个都是等效的)是否会导致意外错误?我可以依赖于这种行为,还是它可能会改变 // 1st FileOutputStream f = new FileOutputStream(...); // some io, not shown // 2nd f = new FileOutputStream(f.getFD());

以下代码块(每个都是等效的)是否会导致意外错误?我可以依赖于这种行为,还是它可能会改变

                     // 1st
FileOutputStream f = new FileOutputStream(...);
                     // some io, not shown

                     // 2nd
                 f = new FileOutputStream(f.getFD());
                     // some io, not shown

                     // 3rd
                 f = new FileOutputStream(f.getFD());
                     // some io, not shown

有两种可能的结果,我在前面概述过。我总是假设最坏的结果是,一旦没有引用就收集未引用的对象。以下内容与第一个代码块有关

案例1:

当第二个FileOutputStream分配给
f
时,第一个输出流将不再具有任何引用,因此将被收集。完成后,底层文件描述符(在所有三个流之间共享)将关闭。此时,第二个FileOutputStream上的任何IO操作(未显示)都将抛出IOException。但是,第二个FileOutputStream保留对FileDescriptor的引用(现在已关闭),因此第三个赋值的RHS上的最终
f.getFD()
会成功。当第三个FileOutputStream分配给
f
时,将收集第二个输出流,并再次关闭底层FileDescriptor(我相信会生成IOException)。然而,第三个流上的任何IO都将再次失败

案例2:

或者,FileDescriptor保留对已分配给它的所有CloseTables的强引用。当第二个FileOutputStream被分配给
f
时,FileDescriptor将保持对第一个FileOutputStream的引用,这样它就永远不会被收集和最终确定,并且FileDescriptor保持打开状态。当第三个FileOutputStream被分配给
f
时,描述符引用了所有三个流,因此不符合收集条件

测试用例

我没有用于测试的JDK7,但显然情况#1适用(),除非未知的第三方持有引用或垃圾收集器做出特定的豁免

然而,JDK8显然更改了FileDescriptor以保存可关闭的列表,因此案例2适用()。我可以使用以下测试程序(在openjdk8上)确认此行为:

import java.io.*;
import java.lang.ref.WeakReference;
import java.lang.Thread;
import java.lang.Runtime;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import com.sun.management.UnixOperatingSystemMXBean;
import java.util.ArrayList;
import java.util.Arrays;


class TestThread extends Thread {
    static void gc() {
        System.gc();

        try {
            sleep(1000);
        }
        catch (InterruptedException e) {
            System.err.println(e.getMessage());
        }
    }

    static void test(String message,
                     long fd_count_a,
                     ArrayList<WeakReference<FileOutputStream>> fw,
                     OperatingSystemMXBean os,
                     FileDescriptor fd
                     ) throws IOException {
        long fd_count_b = fd_count_b = ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount() - fd_count_a;

        System.out.println("Results, " + message + ":");
        for (int  i=0; i<fw.size(); i++) {
            String prefix = "fw_" + String.valueOf(i);
            if (fw.get(i).get() == null) {
                System.out.println(prefix + ":\t\t" + "null");
                System.out.println(prefix + " open" + ":\t" + "no");
            } else {
                System.out.println(prefix + ":\t\t" + fw.get(i).get().toString());
                System.out.println(prefix + " open" + ":\t" + (fw.get(i).get().getFD().valid() ? "yes" : "no"));
            }
        }
        System.out.println("fd  :\t\t" + ((fd == null) ? "null" : fd.toString()));
        System.out.println("fds :\t\t" + String.valueOf(fd_count_b));
        System.out.println();
    }

    public void run() {
        try {
            run_contents();
        }
        catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }

    public void run_contents() throws IOException {
        FileOutputStream                        f       = null;
        WeakReference<FileOutputStream>         fw_1    = null;
        WeakReference<FileOutputStream>         fw_2    = null;
        WeakReference<FileOutputStream>         fw_3    = null;
        FileDescriptor                          fd      = null;

        OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();

        long fd_count_a = fd_count_a = ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount();

        f       = new FileOutputStream("/dev/null");
        fw_1    = new WeakReference<FileOutputStream>(f);
        f.write(1);
        gc();
        test("after fw_1", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1)), os, f.getFD());

        f       = new FileOutputStream(f.getFD());
        fw_2    = new WeakReference<FileOutputStream>(f);
        f.write(2);
        gc();
        test("after fw_2", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2)), os, f.getFD());

        f       = new FileOutputStream(f.getFD());
        fw_3    = new WeakReference<FileOutputStream>(f);
        f.write(3);
        gc();
        test("after fw_3", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2, fw_3)), os, f.getFD());

        f.close();

        gc();
        test("after closing stream", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2, fw_3)), os, f.getFD());

        fd = f.getFD();

        f = null;

        gc();
        test("after dereferencing stream", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2, fw_3)), os, fd);

        fd = null;

        gc();
        test("after dereferencing descriptor", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2, fw_3)), os, fd);
    }
}

class Test {
    public static void main(String[] args) {
        TestThread t = new TestThread();
        t.start();

        try {
            t.join();
        }
        catch (InterruptedException e) {
            System.err.println(e.getMessage());
        }
    }
}
然而,据报道,似乎有一个推送,它后来被恢复和推迟,以防止FileDescriptor保留对closeables的强引用

那么,我的问题是:

  • 我关于JDK7的假设正确吗?它的行为不同于JDK8
  • 我是否可以依靠JDK8的FileDescriptor的行为来保存对closeables的强引用,或者在JDK的未来版本中会恢复这种引用
编辑:我已经发布了后续问题

我能相信他的行为吗

您只能依赖于在包、类、public/protected字段或public/protected方法javadocs或JLS中指定的内容。Java实现不需要使用OpenJDK的类,毕竟,它们可以从头开始重新实现接口

当某些措辞不清楚时,查看实现可能会有用,但参考实现不是规范的一部分

您可以依赖于特定于实现的行为,但在这种情况下,应该通过适当的检查来保护它,在可能的情况下提供回退代码路径,并以明显的文档记录


据我所知,由于这些问题,不鼓励直接使用文件描述符。例如,显示android在第一个拥有FD的流关闭时确实关闭了FD

你能确认我问题中的行为没有记录在javadocs(我很确定)或JLS(我几乎不确定)中吗?另外,您是否建议我将每个新的FileInputStream分配给一个新变量?要证明没有这样的东西,需要进行彻底的搜索,而我不愿意这样做。这就是说,我还没有看到任何关于终结和FDs之间交互的说明,只是在内部代码注释中。对我来说已经足够好了。我发布了后续问题。
import java.io.*;
import java.lang.ref.WeakReference;
import java.lang.Thread;
import java.lang.Runtime;
import java.lang.management.ManagementFactory;
import java.lang.management.OperatingSystemMXBean;
import com.sun.management.UnixOperatingSystemMXBean;
import java.util.ArrayList;
import java.util.Arrays;


class TestThread extends Thread {
    static void gc() {
        System.gc();

        try {
            sleep(1000);
        }
        catch (InterruptedException e) {
            System.err.println(e.getMessage());
        }
    }

    static void test(String message,
                     long fd_count_a,
                     ArrayList<WeakReference<FileOutputStream>> fw,
                     OperatingSystemMXBean os,
                     FileDescriptor fd
                     ) throws IOException {
        long fd_count_b = fd_count_b = ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount() - fd_count_a;

        System.out.println("Results, " + message + ":");
        for (int  i=0; i<fw.size(); i++) {
            String prefix = "fw_" + String.valueOf(i);
            if (fw.get(i).get() == null) {
                System.out.println(prefix + ":\t\t" + "null");
                System.out.println(prefix + " open" + ":\t" + "no");
            } else {
                System.out.println(prefix + ":\t\t" + fw.get(i).get().toString());
                System.out.println(prefix + " open" + ":\t" + (fw.get(i).get().getFD().valid() ? "yes" : "no"));
            }
        }
        System.out.println("fd  :\t\t" + ((fd == null) ? "null" : fd.toString()));
        System.out.println("fds :\t\t" + String.valueOf(fd_count_b));
        System.out.println();
    }

    public void run() {
        try {
            run_contents();
        }
        catch (IOException e) {
            System.err.println(e.getMessage());
        }
    }

    public void run_contents() throws IOException {
        FileOutputStream                        f       = null;
        WeakReference<FileOutputStream>         fw_1    = null;
        WeakReference<FileOutputStream>         fw_2    = null;
        WeakReference<FileOutputStream>         fw_3    = null;
        FileDescriptor                          fd      = null;

        OperatingSystemMXBean os = ManagementFactory.getOperatingSystemMXBean();

        long fd_count_a = fd_count_a = ((UnixOperatingSystemMXBean) os).getOpenFileDescriptorCount();

        f       = new FileOutputStream("/dev/null");
        fw_1    = new WeakReference<FileOutputStream>(f);
        f.write(1);
        gc();
        test("after fw_1", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1)), os, f.getFD());

        f       = new FileOutputStream(f.getFD());
        fw_2    = new WeakReference<FileOutputStream>(f);
        f.write(2);
        gc();
        test("after fw_2", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2)), os, f.getFD());

        f       = new FileOutputStream(f.getFD());
        fw_3    = new WeakReference<FileOutputStream>(f);
        f.write(3);
        gc();
        test("after fw_3", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2, fw_3)), os, f.getFD());

        f.close();

        gc();
        test("after closing stream", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2, fw_3)), os, f.getFD());

        fd = f.getFD();

        f = null;

        gc();
        test("after dereferencing stream", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2, fw_3)), os, fd);

        fd = null;

        gc();
        test("after dereferencing descriptor", fd_count_a, new ArrayList<WeakReference<FileOutputStream>>(Arrays.asList(fw_1, fw_2, fw_3)), os, fd);
    }
}

class Test {
    public static void main(String[] args) {
        TestThread t = new TestThread();
        t.start();

        try {
            t.join();
        }
        catch (InterruptedException e) {
            System.err.println(e.getMessage());
        }
    }
}
Results, after fw_1:
fw_0:        java.io.FileOutputStream@7afd6488
fw_0 open:   yes
fd  :        java.io.FileDescriptor@743a95a7
fds :        1

Results, after fw_2:
fw_0:        java.io.FileOutputStream@7afd6488
fw_0 open:   yes
fw_1:        java.io.FileOutputStream@70050ff8
fw_1 open:   yes
fd  :        java.io.FileDescriptor@743a95a7
fds :        1

Results, after fw_3:
fw_0:        java.io.FileOutputStream@7afd6488
fw_0 open:   yes
fw_1:        java.io.FileOutputStream@70050ff8
fw_1 open:   yes
fw_2:        java.io.FileOutputStream@35079f9c
fw_2 open:   yes
fd  :        java.io.FileDescriptor@743a95a7
fds :        1

Results, after closing stream:
fw_0:        java.io.FileOutputStream@7afd6488
fw_0 open:   no
fw_1:        java.io.FileOutputStream@70050ff8
fw_1 open:   no
fw_2:        java.io.FileOutputStream@35079f9c
fw_2 open:   no
fd  :        java.io.FileDescriptor@743a95a7
fds :        0

Results, after dereferencing stream:
fw_0:        java.io.FileOutputStream@7afd6488
fw_0 open:   no
fw_1:        java.io.FileOutputStream@70050ff8
fw_1 open:   no
fw_2:        java.io.FileOutputStream@35079f9c
fw_2 open:   no
fd  :        java.io.FileDescriptor@743a95a7
fds :        0

Results, after dereferencing descriptor:
fw_0:        null
fw_0 open:   no
fw_1:        null
fw_1 open:   no
fw_2:        null
fw_2 open:   no
fd  :        null
fds :        0