Обходной путь для ошибки 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.
Код оператора и пример кода показывают, что ошибка возникает, когда:
- Существует ожидающее событие, которое не было использовано (оно может быть или не быть видимым для
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<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
или какой-то другой механизм.