匿名文件流重用描述符 [英] anonymous file streams reusing descriptors
问题描述
//第一个
FileOutputStream f = new FileOutputStream (...);
//一些io,未显示
// 2nd
f = new FileOutputStream(f.getFD());
//一些io,未显示
// 3rd
f = new FileOutputStream(f.getFD());
//一些io,未显示
静态FileOutputStream ExampleFunction(FileOutputStream fos){
返回新的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来保存一个可关闭列表,所以Case# 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();
尝试{
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
)中抛出IOException异常{
长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(前缀+open+:\ t+no);
} else {
System.out.println(prefix +:\t\t+ fw.get(i).get()。toString());
System.out.println(前缀+打开+:\ 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(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(); (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(); (fw_3),fd_count_a,new ArrayList< WeakReference< FileOutputStream>>(Arrays.asList(fw_1,fw_2,fw_3)),os,f.getFD());
f.close();
gc(); (),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(dereferencing stream之后,fd_count_a,new ArrayList< WeakReference< FileOutputStream>>(Arrays.asList(fw_1,fw_2,fw_3)),os,fd);
fd = null;
gc();
test(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();
尝试{
t.join();
}
catch(InterruptedException e){
System.err.println(e.getMessage());
}
}
}
具有以下输出:
fw_1后的结果:
fw_0:java.io.FileOutputStream@7afd6488
fw_0 open:yes
fd:java.io.FileDescriptor@743a95a7
fds:1
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
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
结果,关闭流之后:
fw_0:java.io.FileOutputStream@7afd6488
fw_0 open:no
fw_1:ja va.io.FileOutputStream@70050ff8
FW_1开放:没有
FW_2:java.io.FileOutputStream@35079f9c
FW_2打开:没有
FD:java.io.FileDescriptor@743a95a7
fds:0
解除引用流后的结果:
fw_0:java.io.FileOutputStream@7afd6488
fw_0 open:no
fw_1:java.io .FileOutputStream @ 70050ff8
FW_1开放:没有
FW_2:java.io.FileOutputStream@35079f9c
FW_2打开:没有
FD:java.io.FileDescriptor@743a95a7
fds:0
解除引用描述符后的结果:
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
然而,根据该错误报告 - 这是后来恢复和推迟 - 以防止FileDescript或者将强引用关闭到可关闭。
所以,我的问题是: 编辑:我发布了后续问题无效关闭流。 我可以依赖行为吗? 您只能依靠在包,类,公共/受保护字段或公共/受保护的方法javadoc或JLS中指定了哪些内容。 Java实现毕竟不需要使用OpenJDK的类,它们可以从头开始重新实现接口。 在某些措词不清楚的情况下查看实现可能会有用,但参考实现不是规范的一部分。 您可以依赖特定于实现的行为,但在这种情况下,它应该由适当的检查,在可能的情况下提供回退代码路径并进行明显记录。 据我所知,由于这样的问题,不鼓励直接使用FileDescriptors。例如。 这个问题显示当第一个拥有它的流关闭时,android会关闭FD Will the following code blocks (each are equivalent) cause unexpected errors? Can I depend on the behavior, or is it subject to change? There are two possible results, which I outline before. I always assume the worst possible outcome, that the unreferenced objects will be collected as soon as no references hold them. What follows is in relation to the first code block. Case #1: When the second FileOutputStream is assigned to Case #2: Alternatively, a FileDescriptor keeps strong references to all closeables that have been assigned to it. When the second FileOutputStream is assigned to Test Case: I don't have JDK7 for testing, but apparently Case #1 applies (JDK7 FileDescriptor.java), unless an unkown third part holds the references or the garbage collector makes a specific exemption. However, JDK8 apparently changes FileDescriptor to hold a list of closeables, so that Case #2 applies (JDK8 FileDescriptor.java). I can confirm this behaviour (on openjdk8) using the following test program: which has the following output: However there seems to be a push, according to this bug report - which was later reverted and deferred - to prevent FileDescriptor from keeping strong references to closeables. So, my questions: Edit: I've posted the follow-up question Invalidate Stream without Closing. Can I depend on the behaviour You can only depend on what's specified either in the package, class, public/protected field or public/protected method javadocs or in the JLS. Java implementations are not required to use the OpenJDK's classes after all, they can reimplement the interfaces from scratch. Looking at the implementation may be useful when some wording is unclear, but the reference implementation is not part of the specification. You can rely on implementation-specific behavior, but in that case it should be guarded by appropriate checks, offer fallback codepaths where possible and be visibly documented. As I understand it direct use of FileDescriptors is discouraged due to such problems. E.g. this question shows android does close the FD when the first stream owning it is closed 这篇关于匿名文件流重用描述符的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!
// 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(...))))
f
, the first output stream will no longer have any references, and thus be collected. When it is finalized, the underlying file descriptor (shared between all three streams) will be closed. At this point, any IO operations (not shown) on the second FileOutputStream will throw IOException. However the second FileOutputStream keeps a reference to the FileDescriptor (now closed), so that the final f.getFD()
on the RHS of the third assignment does succeed. When the third FileOutputStream is assigned to f
, the second output stream will be collected and the underlying FileDescriptor will be closed again (generating an IOException, I believe). Once again, however, any IO on the third stream will fail.f
, the FileDescriptor maintains a reference to the first FileOutputStream so that it is never collected and finalized, and so that the FileDescriptor remains open. When the the third FileOutputStream is assigned to f
, all three streams are referenced by the descriptor and not eligible to be collected.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