Обходной путь для ошибки Java, которая вызывает аварийный дамп

Программа, которую я разработал, иногда вызывает сбой JVM из-за этой ошибки: http://bugs.java.com/bugdatabase/view_bug.do?bug_id=8029516. К сожалению, ошибка не была устранена Oracle, и в отчете об ошибках говорится, что известных обходных путей нет.

Я попытался изменить пример кода из отчета об ошибке, вызвав вместо этого.register (sWatchService, eventKinds) в потоке KeyWatcher, добавив все ожидающие запросы на регистрацию в список, который я перебираю в потоке KeyWatcher, но он все еще падает. Я предполагаю, что это имело тот же эффект, что и синхронизация в sWatchService (как пытался подать отчет об ошибке).

Можете ли вы придумать способ обойти это?

3 ответа

Решение

Мне удалось создать обходной путь, хотя это несколько уродливо.

Ошибка в методе 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() от преждевременного освобождения нативного буфера.

Из комментариев:

Похоже, что у нас есть проблема с отменой ввода-вывода, когда ожидается незавершенное чтение 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<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();
    }
}

Возможно, вы не сможете обойти саму проблему, но вы можете справиться с ошибкой и справиться с ней. Я не знаю вашей конкретной ситуации, но могу представить, что самая большая проблема - это крах всей JVM. Положить все в try Блок не работает, потому что вы не можете поймать сбой JVM.

Если вы не знаете больше о вашем проекте, вам будет трудно предложить хорошее / приемлемое решение, но, возможно, это может быть вариантом: сделать все, что нужно для просмотра файлов, в отдельном процессе JVM. С вашего основного процесса запустите новую JVM (например, используя ProcessBuilder.start()). Когда процесс завершается (т. Е. Недавно запущенная JVM падает), перезапустите его. Очевидно, что вы должны быть в состоянии восстановить, то есть вам нужно отслеживать, какие файлы просматривать, и вам нужно также хранить эти данные в вашем основном процессе.

Теперь самая большая оставшаяся часть заключается в реализации некоторой связи между основным процессом и процессом просмотра файлов. Это можно сделать, используя стандартный ввод / вывод процесса просмотра файлов или используя Socket / ServerSocket или какой-то другой механизм.

Другие вопросы по тегам