Статьи

Управление тяжелыми ресурсами на платформе NetBeans


Если у вас есть небольшой опыт работы с
платформой NetBeans , вы знаете, что одним из базовых классов в API является DataObject. Он моделирует данные, представленные одним (или несколькими) файлами, таким образом, он предлагает ссылки на файловую систему плюс другие средства для визуализации и выбора в различных видах представлений.

DataObject — это своего рода абстрактный объект, поэтому вы обычно создаете подкласс для своих нужд. Например, одним из самых первых классов, которые я написал в blueMarine при переносе его на платформу NetBeans, был PhotoDataObject, который расширяет DataObject и действует как адаптер для ввода-вывода изображения.

Даже если подклассы подходят для предоставления альтернативных реализаций, наследование вредно, если у вас есть прямые зависимости от подклассов! Вместо этого DataObject предоставляет очень простой и эффективный механизм делегирования с помощью Lookup, другого базового класса в API-интерфейсах платформы NetBeans. Lookup — это общий контейнер объектов, и каждый экземпляр DataObject имеет его; просто объявите, что ваши объекты могут делать как интерфейсы («возможности») и поместите их реализации в Lookup.

Например, следующий код используется компонентом, который управляет миниатюрами, чтобы извлечь изображение, которое будет уменьшено и превращено в миниатюру:

DataObject dataObject = ...
PreviewImageProvider previewImageProvider = dataObject.getLookup().lookup(PreviewImageProvider.class);
previewImageProvider.load(IMAGE);
EditableImage image = previewImageProvider.getImage();

Подобный код позволяет получить метаданные объекта (например, EXIF ​​для фотографии):

DataObject dataObject = ...
MetadataProvider metadataProvider = dataObject.getLookup().lookup(MetadataProvider.class);
metadataProvider.load();
Object metadata = metadataProvider.getMetadata();

Тот же код работает для фотографий, файлов PDF и фильмов (и может, например, работать с аудиофайлами, где миниатюрой может быть форма волны и т. Д.).

Это очень мощный подход. После последней большой рефакторинга, проведенной в августе, blueMarine больше не зависит от PhotoDataObject (который фактически превратился в закрытый API, то есть он не публикуется для всего приложения). Это означает , что Bluemarine способен работать с каждым видом DataObject вы предоставляете — менее чем за один час, к примеру, я добавил в инкубатор поддержку для PDF — файлов (благодаря PDF-визуализатора по Джошуа Marinacci ) , и я также иметь прототип для поддержки фильмов, который просто ждет, когда Sun выпустит новые портативные кодеки, анонсированные на JavaOne. 

Как это часто бывает, довольно хорошие проекты могут страдать от некоторых проблем в сценарии производительности. В моем случае вы должны учитывать, что мои объекты данных обычно большие (фотографии) или очень большие (фильмы); load-on-demand является хорошей идеей (именно поэтому в предыдущих интерфейсах есть явный метод load ()). Но это только уменьшает проблему, не решая ее полностью: если у вас много фотографий одновременно, память очень быстро уменьшается. Какую политику можно выбрать для освобождения ресурсов?

Конечно, работая с Java, на ум приходит сборщик мусора. Видимо, дело только в слабых ссылках, верно? Вы сохраняете только слабые ссылки на внутренние ресурсы, поэтому они будут автоматически очищаться, когда они вам больше не нужны. Вы можете придумать асинхронный способ использования API, например так:

PreviewImageProvider previewImageProvider ...
EditableImage image = previewImageProvider.getImage();

if (image == null)
{
image = previewImageProvider.load(IMAGE);
}

Когда вы пишете клиентский код, вы просто должны помнить, что ресурсы не всегда доступны, и вы должны в конечном итоге запрашивать их несколько раз.

К сожалению, простой загрузки по требованию со слабыми ссылками недостаточно. Сильное разделение, которое вы оценили, определив разные интерфейсы для разных возможностей (PreviewImageProvider и MetadataProvider), означает, что вы будете запрашивать разные возможности в разных частях приложения, которые не осведомлены друг о друге (также учтите, что по порядку чтобы использовать параллелизм, большинство задач blueMarine выполняются мастерами / рабочими, что еще более фрагментирует доступ к ресурсам). Весьма вероятно, что ресурсы распределяются между операциями PreviewImageProvider и MetadataProvider, что приводит к множественному доступу к диску — учтите, что дешевле загружать как метаданные, так и изображение за один проход.

Решение, которое я нашел эффективным до сих пор, — это простой служебный класс, который в первой части своей жизни выступает в качестве сильной ссылки, а затем — слабой:

class DisposableResource<T>
{
private static final int DELAY = 10 * 1000;

private WeakReference<T> resource;

private T keeper;

public void set (T object)
{
keeper = object;
resource = (object == null) ? null : new WeakReference<T>(object);

RequestProcessor.getDefault().post(new Runnable()
{
public void run()
{
keeper = null;
}
}, DELAY);
}

public T get()
{
return (resource == null) ? null : resource.get();
}
}

RequestProcessor происходит из API-интерфейсов NetBeans и позволяет мне выполнять задачу после задержки, эффективно планируя поток во внутреннем пуле; поэтому приведенный выше код должен быть очень эффективным даже с десятыми и тысячами экземпляров. Кстати, задержка в настоящее время очень консервативна, хотя должно хватить нескольких секунд, но я все еще должен выполнить для нее нагрузочный тест.