导致崩溃转储的Java错误的解决方法

时间:2014-04-25 16:15:49

标签: java windows multithreading nio crash-dumps

由于此错误,我已经开发的程序偶尔会崩溃JVM:http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8029516。遗憾的是,Oracle尚未解决该漏洞,并且错误报告称没有已知的解决方法。

我试图通过在KeyWatcher线程中调用.register(sWatchService,eventKinds)来修改bug报告中的示例代码,方法是将所有挂起的注册请求添加到我在KeyWatcher线程中循环的列表中但它仍在崩溃。我猜测这与sWatchService上的同步效果相同(就像尝试过的bug报告的提交者一样)。

你能想出办法解决这个问题吗?

3 个答案:

答案 0 :(得分:4)

来自评论:

  

当有待处理的ReadDirectoryChangesW未完成时,我们似乎遇到了I / O取消问题。

语句和示例代码表明在以下情况下触发了错误:

  1. 尚未消费的待处理事件(WatchService.poll()WatchService.take()可能显示或未显示)
  2. 在密钥上调用
  3. 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<FileChangeListener>> listeners = new ConcurrentHashMap<String, List<FileChangeListener>>();
        Map<WatchKey, String> keys = new ConcurrentHashMap<WatchKey, String>();
    
        boolean toStop = false;
    
        public interface FileChangeListener {
            void onChange();
        }
    
        public void addFileChangeListener(String path, FileChangeListener l) {
            if(!listeners.containsKey(path)) {
                listeners.put(path, new ArrayList<FileChangeListener>());
                keys.put(Paths.get(path).register(ws, allEvents), path);
            }
            listeners.get(path).add(l);
        }
    
        public void removeFileChangeListener(String path, FileChangeListener 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(FileChangeListener l: listeners.get(keys.get(key)))
                            l.onChange();
                    }
                }
            }).start();
        }
    
        public void stop() {
            toStop = true;
            ws.close();
        }
    }
    

答案 1 :(得分:3)

您可能无法解决问题本身,但您可以处理错误并处理它。我不知道你的具体情况,但我可以想象最大的问题是整个JVM的崩溃。将所有内容放在try块中不起作用,因为您无法捕获JVM崩溃。

不了解您的项目的更多信息使得很难建议一个好的/可接受的解决方案,但也许这可能是一个选项:在一个单独的JVM进程中查看所有文件。从您的主进程启动一个新的JVM(例如使用ProcessBuilder.start())。当进程终止时(即新启动的JVM崩溃),重新启动它。显然你需要能够恢复,即你需要跟踪要监视的文件,你需要将这些数据保存在主进程中。

现在剩下的最大部分是在主进程和文件监视进程之间实现一些通信。这可以使用文件观看流程的标准input / output或使用Socket / ServerSocket或其他一些机制来完成。

答案 2 :(得分:3)

我设法创建了一个变通方法,虽然它有些难看。

该错误在JDK方法WindowsWatchKey.invalidate()中,它释放本机缓冲区,而后续调用仍然可以访问它。 This one-liner通过将缓冲区清理延迟到GC来解决问题。

这是JDK的编译patch。为了应用它,添加以下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),这将阻止watchKey.cancel()过早释放本机缓冲区。