I'm opening an asset using a FileDescriptor on Android. It appears that garbage collection changes the FileDescriptor's internal descriptor to -1. Attempting to use the FileDescriptor after that throws an exception.
As a sanity check, I added this code to a blank project:
try{
fd = getAssets().openFd("greensleeves.wav").getFileDescriptor();
}catch(IOException e) {
}
System.out.println("file descriptor before gc" + fd);
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("file descriptor before gc" + fd);
try { Thread.sleep(100); } catch (InterruptedException e) {}
System.out.println("file descriptor before gc" + fd);
System.gc();
System.out.println("file descriptor after gc" + fd);
System.out.println("file descriptor after gc" + fd);
System.out.println("file descriptor after gc" + fd);
This is the output
System.out I file descriptor before gcFileDescriptor[45]
System.out I file descriptor before gcFileDescriptor[45]
System.out I file descriptor before gcFileDescriptor[45]
dalvikvm D GC_EXPLICIT freed 176K, 3% free 9108K/9316K, paused 2ms+2ms, total 24ms
System.out I file descriptor after gcFileDescriptor[-1]
System.out I file descriptor after gcFileDescriptor[-1]
System.out I file descriptor after gcFileDescriptor[-1]
Why does this happen? How can I safely use a FileDescriptor without worrying about racing against the garbage collector?
You aren't retaining a reference to the
AssetFileDescriptor
created byopenFd()
. When it gets GC'ed, it has an internalParcelFileDescriptor
that is also eventually GC'ed. That, in turn, has the reference to theFileDescriptor
that you are retrieving in the call togetFileDescriptor()
. It also happens to have afinalize()
method that, when called, will close the file descriptor (by callingIoUtils.closeQuietly(mFd);
). Thisfinalize()
method will be called by the GC when theParcelFileDescriptor
is collected. This is how the behavior you are observing happens.My guess as to why sleeping doesn't trigger these events is that there's no pressure on the GC to clean things up.
To test this (and, if I'm right, to prevent the invalidation), maintain a reference to the object returned by
openFd()
for the duration of your experiment.