我开发的程序偶尔会因为这个bug而崩溃: http : //bugs.java.com/bugdatabase/view_bug.do?bug_id=8029516 。 不幸的是,这个错误还没有被Oracle解决,并且错误报告说没有已知的解决方法。
我试图通过在KeyWatcher线程中调用.register(sWatchService,eventKinds)来修改bug报告中的示例代码,而将所有挂起的注册请求添加到我在KeyWatcher线程中循环的列表中,但仍然崩溃。 我猜这跟sWatchService上的同步效果一样(就像bug报告的提交者一样)。
你能想办法解决这个问题吗?
尽管它有点难看,但我设法创造了一个解决方法。
该错误是在JDK方法WindowsWatchKey.invalidate()
释放本机缓冲区,而后续的调用仍然可以访问它。 这一行通过延迟缓冲区清理直到GC来解决问题。
这是一个JDK的编译补丁 。 为了应用它,添加下面的Java命令行标志:
-Xbootclasspath/p:jdk-8029516-patch.jar
如果在您的情况下修补JDK不是一个选项,那么在应用程序级别上仍然有一个解决方法。 它依赖于Windows WatchService内部实现的知识。
public class JDK_8029516 { private static final Field bufferField = getField("sun.nio.fs.WindowsWatchService$WindowsWatchKey", "buffer"); private static final Field cleanerField = getField("sun.nio.fs.NativeBuffer", "cleaner"); private static final Cleaner dummyCleaner = Cleaner.create(Thread.class, new Thread()); private static Field getField(String className, String fieldName) { try { Field f = Class.forName(className).getDeclaredField(fieldName); f.setAccessible(true); return f; } catch (Exception e) { throw new IllegalStateException(e); } } public static void patch(WatchKey key) { try { cleanerField.set(bufferField.get(key), dummyCleaner); } catch (IllegalAccessException e) { throw new IllegalStateException(e); } } }
在密钥被注册后JDK_8029516.patch(watchKey)
调用JDK_8029516.patch(watchKey)
,它会阻止watchKey.cancel()
提前释放本机缓冲区。
来自评论:
看起来我们有一个I / O取消的问题,当有一个未决的ReadDirectoryChangesW未完成。
该语句和示例代码指示在以下情况下触发该错误:
WatchService.poll()
或WatchService.take()
可能会或可能不可见) WatchKey.cancel()
在密钥上被调用 这是一个讨厌的错误,没有通用的解决方法。 该方法取决于您的应用程序的具体情况。 考虑将手表集中到一个地方,所以你不需要调用WatchKey.cancel()
。 如果在某一点上池变得太大,关闭整个WatchService
并重新开始。 类似的东西。
public class FileWatcerService { static Kind<?>[] allEvents = new Kind<?>[] { StandardWatchEventKinds.ENTRY_CREATE, StandardWatchEventKinds.ENTRY_DELETE, StandardWatchEventKinds.ENTRY_MODIFY }; WatchService ws; // Keep track of paths and registered listeners Map<String, List<FileChangelistner>> listeners = new ConcurrentHashMap<String, List<FileChangelistner>>(); Map<WatchKey, String> keys = new ConcurrentHashMap<WatchKey, String>(); boolean toStop = false; public interface FileChangelistner { void onChange(); } public void addFileChangelistner(String path, FileChangelistner l) { if(!listeners.containsKey(path)) { listeners.put(path, new ArrayList<FileChangelistner>()); keys.put(Paths.get(path).register(ws, allEvents), path); } listeners.get(path).add(l); } public void removeFileChangelistner(String path, FileChangelistner l) { if(listeners.containsKey(path)) listeners.get(path).remove(l); } public void start() { ws = FileSystems.getDefault().newWatchService(); new Thread(new Runnable() { public void run() { while(!toStop) { WatchKey key = ws.take(); for(FileChangelistner l: listeners.get(keys.get(key))) l.onChange(); } } }).start(); } public void stop() { toStop = true; ws.close(); } }
你可能无法解决问题本身,但你可以处理这个错误并处理它。 我不知道你的具体情况,但我可以想象最大的问题是整个JVM的崩溃。 把所有的try
块都行不通,因为你无法捕捉到JVM崩溃。
不知道更多关于你的项目使得难以建议一个好的/可接受的解决方案,但也许这可能是一个选择:做所有的文件在一个单独的JVM过程中看东西。 从你的主进程启动一个新的JVM(例如使用ProcessBuilder.start()
)。 当进程终止(即新启动的JVM崩溃)时,重新启动它。 显然,你需要能够恢复,即你需要跟踪什么文件来看,你需要保持这个数据在你的主要过程。
现在最大的剩余部分是实现主进程和文件监视进程之间的一些通信。 这可以使用文件监视过程的标准输入 / 输出或使用Socket
/ serverSocket
或其他机制来完成。