Javaで一時ファイルを作るときのdeleteOnExitとStandardOpenOption.DELETE_ON_CLOSEの違いを確認する

## 概要

Javaで一時ファイルを作るとき、ネットを少し探すと、File.createTempFileをした後にdeleteOnExitをつけたり、Files.createTempFileの後のファイル作成時のオプションで StandardOpenOption.DELETE_ON_CLOSE をつけるという方法の記事を見ますが、ファイルとしては実際どのように見えるのかについて確認してみました。JavaDocに記載があるような注意点について、実際確認してみることが目的です。

## 方法

以下のようなプログラムで確認します。

```Java
package tmp_file_visibility_test;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;

/**
 * 以下のことを確認するためのテストプログラム。
 * 
 * - FileのdeleteOnExitでは、他のプロセスからもファイルが見えること
 * - StandardOpenOption.DELETE_ON_CLOSEについては、プラットフォームにより、プロセス内外からのファイルの見え方が異なること
 * 
 */
public class Main {
	/**
	 * 動作プラットフォームに関する情報を標準出力に表示する。
	 */
	private static void printPlatform() {
		long pid = ProcessHandle.current().pid();
		System.out.println("[基本情報]");
		System.out.println("動作プラットフォーム: " + System.getProperty("os.name"));
		System.out.println("JavaVMのバージョン: " + System.getProperty("java.version"));
		System.out.println("実行中のプロセスID: " + pid);
	}

	private static void printFileVisibilityInSameProcess(File file) {
		System.out.println("check file path: " + file.getAbsolutePath());
		if (file.exists()) {
			System.out.println("ファイルが同一プロセスからファイルとして見える。");
		} else {
			System.out.println("ファイルが同一プロセスからファイルとして見えない。");
		}
	}

	private static void printFileVisibilityFromOtherProcess(File file) {
		System.out.println("check file path: " + file.getAbsolutePath());

		// 他のプロセスを起動し、そのプロセスから見えるかを確認する。
		String[] cmd = { "jshell", "filetest.jshell" }; // プラットフォームによらないための手段として洗濯
		try {
	        Process process = Runtime.getRuntime().exec(cmd);
			long otherProcessPid = process.pid();

			// 別ファイルの方がjshellをデバッグしやすかったため、パイプは使っていない
	        BufferedWriter writer = new BufferedWriter(new FileWriter("filetest.jshell"));
	        String escapedFilePath = file.getAbsolutePath().replace("\\", "\\\\");
	        writer.write("import java.io.File;File file = new File(\"" + escapedFilePath + "\");System.out.println(file.exists());/exit");
	        writer.flush();
	        writer.close();
						
			try {
	            int exitCode;
				exitCode = process.waitFor();
	            if (exitCode != 0) {
	                System.err.println("Command execution failed with exit code " + exitCode);
	            }
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
            
			// プロセスの標準出力を読み取る
			BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
			StringBuilder result = new StringBuilder();
			String line;
			while ((line = reader.readLine()) != null) {
				result.append(line).append("\n");
			}
			
			System.out.println(result.toString());

			if (result.toString().contains("true")) {
				System.out.println("ファイルが他のプロセス(" + otherProcessPid + ")から見える。");
			} else {
				System.out.println("ファイルが他のプロセス(" + otherProcessPid + ")から見えない。");
			}
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	/**
	 * deleteOnExitを使った時の可視性のテスト
	 */
	public static void test1() {
		System.out.println("-------------------------------------");
		System.out.println("[実験1]");
		System.out.println("deleteOnExitを使った時の可視性のテスト。");
		File file;
		try {
			file = File.createTempFile("prefix", ".tmp");
			file.deleteOnExit();

			printFileVisibilityInSameProcess(file);
			printFileVisibilityFromOtherProcess(file);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * StandardOpenOption.DELETE_ON_CLOSEを使った時の可視性のテスト
	 */
	public static void test2() {
		System.out.println("-------------------------------------");
		System.out.println("[実験2]");
		System.out.println("StandardOpenOption DELETE_ON_CLOSE、同一プロセスから見えるかのテストを実施。");
		try {
			Path path = Files.createTempFile("prefix", ".tmp");
			try (OutputStream out = Files.newOutputStream(path, StandardOpenOption.CREATE, StandardOpenOption.DELETE_ON_CLOSE)) {
				out.write("Hello.".getBytes());
				out.flush();
				System.out.println("★ファイルのクローズ前に同一プロセスからファイルとして見えるかの確認。");
				File f = new File(path.toFile().getAbsolutePath());
				printFileVisibilityInSameProcess(f);
				printFileVisibilityFromOtherProcess(f);
			}
			System.out.println("★ファイルのクローズ後に同一プロセスから見えるかの確認。");
			printFileVisibilityInSameProcess(path.toFile());
			printFileVisibilityFromOtherProcess(path.toFile());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	/**
	 * エントリポイント
	 */
	public static void main(String[] args) {
		printPlatform();
		test1();
		test2();
	}
}

```

## 結果

### Windows11 + Java21

```
[基本情報]
動作プラットフォーム: Windows 11
JavaVMのバージョン: 21.0.1
実行中のプロセスID: 35004
-------------------------------------
[実験1]
deleteOnExitを使った時の可視性のテスト。
check file path: C:\Users\hrkt\AppData\Local\Temp\prefix9955536575724157409.tmp
ファイルが同一プロセスからファイルとして見える。
check file path: C:\Users\hrkt\AppData\Local\Temp\prefix9955536575724157409.tmp
true

ファイルが他のプロセス(14948)から見える。
-------------------------------------
[実験2]
StandardOpenOption DELETE_ON_CLOSE、同一プロセスから見えるかのテストを実施。
★ファイルのクローズ前に同一プロセスからファイルとして見えるかの確認。
check file path: C:\Users\hrkt\AppData\Local\Temp\prefix10076351853171700637.tmp
ファイルが同一プロセスからファイルとして見える。
check file path: C:\Users\hrkt\AppData\Local\Temp\prefix10076351853171700637.tmp
true

ファイルが他のプロセス(36044)から見える。
★ファイルのクローズ後に同一プロセスから見えるかの確認。
check file path: C:\Users\hrkt\AppData\Local\Temp\prefix10076351853171700637.tmp
ファイルが同一プロセスからファイルとして見えない。
check file path: C:\Users\hrkt\AppData\Local\Temp\prefix10076351853171700637.tmp
false

ファイルが他のプロセス(32216)から見えない。

```

### Ubuntu2404 on WSL2

```
[基本情報]
動作プラットフォーム: Linux
JavaVMのバージョン: 21.0.5
実行中のプロセスID: 1181
-------------------------------------
[実験1]
deleteOnExitを使った時の可視性のテスト。
check file path: /tmp/prefix5898843062846091053.tmp
ファイルが同一プロセスからファイルとして見える。
check file path: /tmp/prefix5898843062846091053.tmp
true

ファイルが他のプロセス(1199)から見える。
-------------------------------------
[実験2]
StandardOpenOption DELETE_ON_CLOSE、同一プロセスから見えるかのテストを実施。
★ファイルのクローズ前に同一プロセスからファイルとして見えるかの確認。
check file path: /tmp/prefix11136340830952286021.tmp
ファイルが同一プロセスからファイルとして見えない。
check file path: /tmp/prefix11136340830952286021.tmp
false

ファイルが他のプロセス(1263)から見えない。
★ファイルのクローズ後に同一プロセスから見えるかの確認。
check file path: /tmp/prefix11136340830952286021.tmp
ファイルが同一プロセスからファイルとして見えない。
check file path: /tmp/prefix11136340830952286021.tmp
false

ファイルが他のプロセス(1326)から見えない。
```

### macOS

```
[基本情報]
動作プラットフォーム: Mac OS X
JavaVMのバージョン: 21.0.6
実行中のプロセスID: 4738
-------------------------------------
[実験1]
deleteOnExitを使った時の可視性のテスト。
check file path: /var/folders/48/xlg8vctn4vxf8_52j9s4p4280000gn/T/prefix17754993504371264141.tmp
ファイルが同一プロセスからファイルとして見える。
check file path: /var/folders/48/xlg8vctn4vxf8_52j9s4p4280000gn/T/prefix17754993504371264141.tmp
true

ファイルが他のプロセス(4739)から見える。
-------------------------------------
[実験2]
StandardOpenOption DELETE_ON_CLOSE、同一プロセスから見えるかのテストを実施。
★ファイルのクローズ前に同一プロセスからファイルとして見えるかの確認。
check file path: /var/folders/48/xlg8vctn4vxf8_52j9s4p4280000gn/T/prefix14734595030457445793.tmp
ファイルが同一プロセスからファイルとして見えない。
check file path: /var/folders/48/xlg8vctn4vxf8_52j9s4p4280000gn/T/prefix14734595030457445793.tmp
false

ファイルが他のプロセス(4742)から見えない。
★ファイルのクローズ後に同一プロセスから見えるかの確認。
check file path: /var/folders/48/xlg8vctn4vxf8_52j9s4p4280000gn/T/prefix14734595030457445793.tmp
ファイルが同一プロセスからファイルとして見えない。
check file path: /var/folders/48/xlg8vctn4vxf8_52j9s4p4280000gn/T/prefix14734595030457445793.tmp
false

ファイルが他のプロセス(4746)から見えない。

```

### 結果まとめ

以下のようになりました。

|プラットフォーム|Windows11|Linux(Ubuntu24.04)|macOS(Sequoia)|
|:--:|:--:|:--:|:--:|
|deleteOnExit指定後、削除前にファイルが自プロセスから見える|✅|✅|✅|
|StandardOpenOption DELETE_ON_CLOSEのファイルが削除前に自プロセスから見える|✅|-|-|
|StandardOpenOption DELETE_ON_CLOSEのファイルが削除前に自プロセスから見える|✅|-|-|
|StandardOpenOption DELETE_ON_CLOSEのファイルが削除前に他プロセスから見える|✅|-|-|
|StandardOpenOption DELETE_ON_CLOSEのファイルが削除後に自プロセスから見える|-|-|-|
|StandardOpenOption DELETE_ON_CLOSEのファイルが削除後に他プロセスから見える|-|-|-|

## まとめ

この記事では、Javaで一時ファイルを作るときのdeleteOnExitとStandardOpenOption.DELETE_ON_CLOSEの違いを確認しました。

[JavaDocの記載にもあるように](https://docs.oracle.com/javase/jp/8/docs/api/java/nio/file/StandardOpenOption.html)、StandardOpenOption.DELETE_ON_CLOSE については、ロックファイル等の目的には想定しない方がよさそうです。プラットフォームで挙動が異なる点にも注意が必要です。