提问者:小点点

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


我开发的一个程序偶尔会因为这个错误而导致JVM崩溃:http://bugs.java.com/bugdatabase/view_bug.do?臭虫id=8029516。不幸的是,Oracle还没有解决这个错误,错误报告说没有已知的解决方法。

我试图通过调用KeyWatcher线程中的. Register(sWatchService,eventKinds)来修改 bug 报告中的示例代码,而是将所有待处理的寄存器请求添加到我在KeyWatcher线程中循环的列表中,但它仍然崩溃。我猜这与在sWatchService上同步的效果相同(就像 bug 报告的提交者尝试的那样)。

你能想到任何解决这个问题的方法吗?


共3个答案

匿名用户

来自评论:

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

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

  1. 有一个未消费的挂起事件(可能对WatchService.poll()WatchService.take())可见也可能不可见
  2. WatchKey.cancel()在键上调用

这是一个令人讨厌的bug,没有通用的解决方法。方法取决于应用程序的具体情况。考虑将手表集中到一个地方,这样您就不需要调用WatchKey.cancel()。如果在某一点上池太大,请关闭整个Watch Service并重新开始。类似的东西。

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();
    }
}

匿名用户

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

该错误位于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),这将防止watchKey.cancel()过早释放本机缓冲区。

匿名用户

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

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

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