以下代码块(每个代码块均相等)会导致意外错误吗?我可以依靠行为,还是可以更改?
// 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
static FileOutputStream ExampleFunction(FileOutputStream fos) {
return new FileOutputStream(fos.getFD());
}
// |-- 3rd ------| |-- 2nd ------| |-- 1st ----------------|
ExampleFunction(ExampleFunction(ExampleFunction(new FileOutputStream(...))))
我之前概述了两种可能的结果。我总是假设最糟糕的结果是,没有引用的对象将立即收集未引用的对象。接下来是关于第一个代码块的。
情况1:
当第二个FileOutputStream分配给
f
时,第一个输出流将不再具有任何引用,因此将被收集。完成后,底层文件描述符(在所有三个流之间共享)将被关闭。此时,第二个FileOutputStream上的任何IO操作(未显示)都将引发IOException。但是,第二个FileOutputStream保留对FileDescriptor的引用(现已关闭),以便第三个分配的RHS上的最后f.getFD()
确实成功。当将第三个FileOutputStream分配给f
时,将收集第二个输出流,并且基础FileDescriptor将再次关闭(我相信会生成IOException)。但是,第三次流上的任何IO都会再次失败。情况2:
另外,FileDescriptor保留对已分配给它的所有可关闭对象的强引用。当将第二个FileOutputStream分配给
f
时,FileDescriptor维护对第一个FileOutputStream的引用,这样就不会对其进行收集和最终确定,从而使FileDescriptor保持打开状态。当第三个FileOutputStream分配给f
时,所有三个流都由描述符引用,并且不符合收集条件。测试用例:
我没有用于测试的JDK7,但显然案例1适用于(JDK7 FileDescriptor.java),除非未知的第三部分保存了引用或垃圾收集器做出了特定的豁免。
但是,JDK8显然更改了FileDescriptor来保存可关闭对象的列表,因此适用案例2(JDK8 FileDescriptor.java)。我可以使用以下测试程序来确认此行为(在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());
}
}
}
具有以下输出:
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
但是,根据this bug report看来有一个推动作用-后来被还原并推迟了-以防止FileDescriptor保持对可关闭对象的强引用。
所以,我的问题是:
我对JDK7的假设是否正确-它的行为与JDK8不同?
我是否可以依靠JDK8的FileDescriptor的行为来保持对可闭合对象的强引用,或者在以后的JDK版本中将其还原?
编辑:我已经发布了后续问题Invalidate Stream without Closing。
最佳答案
我可以依靠行为吗
您只能依赖在包,类,公共/受保护字段或公共/受保护方法javadocs或JLS中指定的内容。毕竟,Java实现不需要使用OpenJDK的类,它们可以从头开始重新实现接口。
当某些措辞不清楚时,查看实现可能会很有用,但是参考实现不是规范的一部分。
您可以依赖于特定于实现的行为,但在这种情况下,应通过适当的检查加以保护,并在可能的情况下提供后备代码路径,并进行明显记录。
据我了解,由于此类问题,不建议直接使用FileDescriptors。例如。 this question显示当拥有它的第一个流关闭时,android确实关闭了FD