匿名文件流重用描述符 [英] anonymous file streams reusing descriptors

查看:97
本文介绍了匿名文件流重用描述符的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

以下代码块(每个都是否相同)会导致意外错误?

  //第一个
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或者将强引用关闭到可关闭。



所以,我的问题是:


  • 我对JDK7的假设是否正确 - 它的行为与JDK8不同?

  • 我可以依赖JDK8的FileDescriptor的行为来持有对可关闭的强引用,在未来版本的JDK中恢复?



编辑:我发布了后续问题无效关闭流

解决方案


我可以依赖行为吗?

您只能依靠在包,类,公共/受​​保护字段或公共/受保护的方法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?

                     // 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(...))))

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 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.

Case #2:

Alternatively, a FileDescriptor keeps strong references to all closeables that have been assigned to it. When the second FileOutputStream is assigned to 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.

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:

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());
        }
    }
}

which has the following output:

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

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:

  • Are my assumptions concerning JDK7 correct - it behaves unlike JDK8?
  • Can I depend on the behaviour of JDK8's FileDescriptor to hold strong references to closeables, or will that be reverted in future versions of the JDK?

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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆