We had a similar issue around 2015, after a user on Win7/x64 regularly found leftover files that hadn't been removed after the program terminated. After some research and trial-and-error, we found it only happened with files that had recently been memory mapped, and fixed it by avoiding memory mapping on files we wanted to delete soon/later.
FileChannelImpl.transferFromFileChannel
memory maps the source for transfer. (Depends on your JVM — I'm basing this on OpenJDK.) While the JVM cleans up after the copy by unmapping, thus invalidating the created view, the OS may delay the actual clean-up to another point in time. Until that happens, the file has a live (but inaccessible) memory map that may prevent unlinking.
This question appears related: How to properly close MappedByteBuffer?
For reference: jdk11/sun.nio.ch.FileChannelImpl#transferFromFileChannel
private long transferFromFileChannel(FileChannelImpl src,
long position, long count)
throws IOException
{
if (!src.readable)
throw new NonReadableChannelException();
synchronized (src.positionLock) {
long pos = src.position();
long max = Math.min(count, src.size() - pos);
long remaining = max;
long p = pos;
while (remaining > 0L) {
long size = Math.min(remaining, MAPPED_TRANSFER_SIZE);
// ## Bug: Closing this channel will not terminate the write
MappedByteBuffer bb = src.map(MapMode.READ_ONLY, p, size);
try {
long n = write(bb, position);
assert n > 0;
p += n;
position += n;
remaining -= n;
} catch (IOException ioe) {
// Only throw exception if no bytes have been written
if (remaining == max)
throw ioe;
break;
} finally {
unmap(bb);
}
}
long nwritten = max - remaining;
src.position(pos + nwritten);
return nwritten;
}
}