我正在开发一个在Linux Mint下使用Java watchservice(Java 8)的应用程序。我遇到的一个有趣问题是用完了inotify手表。
我正在Eclipse下开发,行为如下:
当应用程序启动时,它会递归目录结构,对每个找到的目录进行监视。当前的测试用例使用13,660个路径。我的最大值设置为16384。
如果我停止并重启应用程序几次(20次以上),它似乎正常运行。然而,最终,我将得到一系列系统错误,表明已达到最大手表数量。但是,如果我重新启动Eclipse,问题就会消失。
显然,Watch Service并没有释放它所有的资源,但是它所获得的13,660块手表中,只保留了少量(我猜不到一百只)。除非我关闭Eclipse的Java实例并重新启动它,否则它们似乎不会被释放。
为了解决这个问题,我确保在应用程序关闭并取消监视服务任务时调用监视服务的close方法。
我做的另一件事就是我为两个不同的目的运行两个独立的监视服务。我被告知你不应该运行多个,也许这就是问题所在,但如果我能帮忙,我宁愿不运行一个监视服务。
那就是说,对于我如何能够确定这个错误的原因有什么想法或建议吗?
为大量代码发布道歉。这是我对WatchService类的实现。
一些注意事项:
pathFinder在一个单独的线程中运行,它只是一个文件访问者 - 遍历目录树并返回找到的所有目录/文件的路径。
只有在将更改发布到pathsChanged属性(来自pathFinder的onSucceeded回调)时才会调用注册。
pathsChanged属性始终由setAll()调用更新。它只发布最新的更改,并不是累积的。除了watchservice,其他类会听取这些属性并做出相应的响应。
public final class LocalWatchService extends BaseTask {
private final static String TAG = "LocalWatchService";
//watch service task
private WatchService watcher;
//path finding task and associated executor
private LocalPathFinder finder;
//root path where the watch service begins
private final Path mRootPath;
private final ExecutorService pathFinderExecutor =
createExecutor ("pathFinder", false);
//class hash map which keys watched paths to generated watch keys
private final Map<WatchKey, Path> keys = new HashMap<WatchKey, Path>();
//reference to model property of watched paths.
private final SimpleListProperty <SyncPath> mChangedPaths =
new SimpleListProperty <SyncPath>
(FXCollections.<SyncPath> observableArrayList());
public LocalWatchService (String rootPath) {
super ();
mRootPath = Paths.get(rootPath);
//create the watch service
try {
this.watcher = FileSystems.getDefault().newWatchService();
} catch (IOException e) {
e.printStackTrace();
}
setOnCancelled(new EventHandler() {
@Override
public void handle(Event arg0) {
pathFinderExecutor.shutdown();
}
});
mChangedPaths.addListener(new ListChangeListener <SyncPath> (){
@Override
public void onChanged(
javafx.collections.ListChangeListener.Change<? extends SyncPath>
arg0) {
for (SyncPath path: arg0.getList()) {
//call register only when a directory is found
if (path.getFile() == null) {
try {
register (path.getPath());
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
});
};
public SimpleListProperty<SyncPath> changedPaths() { return mChangedPaths; }
public void initializeWatchPaths() {
ArrayList <Path> paths = new ArrayList <Path> ();
//create a DirectoryStream filter that finds only directories
//and symlinks
DirectoryStream.Filter<Path> filter =
new DirectoryStream.Filter<Path>() {
public boolean accept(Path file) throws IOException {
return (Files.isDirectory(file) ||
Files.isSymbolicLink(file));
}
};
//apply the filter to a directory stream opened on the root path
//and save everything returned.
paths.addAll(utils.getFiles(mRootPath, filter));
runPathFinder (paths);
}
private void runPathFinder (ArrayList <Path> paths) {
//need to add blocking code / mechanism in case a path finder is
//currently running (rare case)
finder = new LocalPathFinder();
finder.setPaths (paths);
//callbacks on successful completion of pathfinder
EventHandler <WorkerStateEvent> eh =
new EventHandler <WorkerStateEvent> () {
ArrayList <SyncPath> paths = new ArrayList <SyncPath>();
@Override
public void handle(WorkerStateEvent arg0) {
for (Path p: finder.getPaths()) {
paths.add(
new SyncPath(mRootPath, p, SyncType.SYNC_NONE));
}
addPaths(paths);
}
};
finder.setOnSucceeded(eh);
pathFinderExecutor.execute (finder);
}
private void addPath(Path path, SyncType syncType) {
mChangedPaths.setAll(new SyncPath(mRootPath, path, syncType));
}
private void addPaths(ArrayList<SyncPath> paths) {
mChangedPaths.setAll(paths);
}
/**
* Register the given directory with the WatchService
* @throws InterruptedException
*/
public final void register(Path dir)
throws IOException, InterruptedException {
//register the key with the watch service
WatchKey key =
dir.register (watcher, ENTRY_CREATE, ENTRY_DELETE, ENTRY_MODIFY);
if (!keys.isEmpty()) {
Path prev = keys.get(key);
if (prev == null) {
//This is a new key
}
else if (!dir.equals(prev)) {
//This is an update
}
}
keys.put(key, dir);
}
private void processWatchEvent (WatchKey key, Path dir) throws IOException, InterruptedException {
for (WatchEvent<?> event: key.pollEvents()) {
WatchEvent.Kind kind = event.kind();
// TBD - provide example of how OVERFLOW event is handled
if (kind == OVERFLOW) {
System.out.println ("Overflow encountered");
}
WatchEvent<Path> ev = (WatchEvent<Path>)event;
Path target = dir.resolve(ev.context());
if (kind == ENTRY_DELETE) {
ArrayList <Path> finderList = new ArrayList <Path> ();
if (Files.isDirectory(target)) {
//directory deletion is not implemented apart from
//file deletion
}
else
addPath (target, SyncType.SYNC_DELETE);
} else if (kind == ENTRY_CREATE) {
/*
* Added paths are passed to the pathfinder service for
* subdirectory discovery. Path and subpaths are then added
* to the AddedPaths property via an event listener on
* service's onSucceeded() event.
*
* Added files are added directly to the AddedPaths property
*/
ArrayList <Path> finderList = new ArrayList <Path> ();
if (Files.isDirectory(target)) {
finderList.add (target);
runPathFinder (finderList);
}
//add files directly to the addedPaths property
else {
//a newly created file may not be immediately readable
if (Files.isReadable(target)) {
addPath (target, SyncType.SYNC_CREATE);
}
else
System.err.println ("File " + target + " cannot be read");
}
} else if (kind == ENTRY_MODIFY) {
System.out.println ("File modified: " + target.toString());
}
boolean valid = key.reset();
if (!valid)
break;
}
}
@SuppressWarnings("unchecked")
<T> WatchEvent<T> cast(WatchEvent<?> event) {
return (WatchEvent<T>)event;
}
@Override
protected Void call () throws IOException, InterruptedException {
boolean interrupted = false;
register (mRootPath);
initializeWatchPaths();
try {
// enter watch cycle
while (!interrupted) {
//watch for a key change. Thread blocks until a change occurs
WatchKey key = null;
interrupted = isCancelled();
//thread blocks until a key change occurs
// (whether a new path is processed by finder or a watched item changes otherwise)
try {
key = watcher.take();
} catch (InterruptedException e) {
interrupted = true;
try {
watcher.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
// fall through and retry
}
Path dir = keys.get (key);
if (dir == null) {
System.out.println ("Null directory key encountered.");
continue;
}
//process key change once it occurs
processWatchEvent(key, dir);
// reset key and remove from set if directory no longer accessible
if (!key.reset()) {
keys.remove(key);
// all directories are inaccessible
if (keys.isEmpty())
break;
}
}
} finally {
if (interrupted)
Thread.currentThread().interrupt();
}
keys.clear();
watcher.close();
return null;
};
}