导致故障转储的Java错误的解决方法

我开发的程序偶尔会因为这个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未完成。

该语句和示例代码指示在以下情况下触发该错误:

  1. 有一个未被使用的未决事件( WatchService.poll()WatchService.take()可能会或可能不可见)
  2. 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或其他机制来完成。