Эталонная очередь всегда пуста

Я пытаюсь очистить собственный ресурс, когда он больше не доступен. Этот ресурс предоставляет метод для очистки выделенных ресурсов (память, потоки и т. д.). Для этого я использовал Phantom Reference.

Этот ресурс должен создаваться асинхронно, когда новая конфигурация предоставляется пользователем библиотеки.

Проблема в том, что ReferenceQueue всегда пуст. Я не ссылаюсь на собственный ресурс вне файлов. Даже в этой ситуации метод poll() возвращает null. Поэтому я не могу очистить ресурс, и это приводит к утечке памяти. Как я могу избежать этой ситуации?

Вы можете найти пример кода ниже. Я использовал JDK8.

// Engine.java

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Native source
 */
public class Engine {
    private static final Map<Long, byte[]> nativeResource = new HashMap<>();
    private static final AtomicLong counter = new AtomicLong(0);

    private final long id;

    public Engine() {
        // Simple memory leak implementation
        id = counter.incrementAndGet();
        nativeResource.put(id, new byte[1024 * 1024 * 10]);
    }

    public void close() {
        nativeResource.remove(id);
        System.out.println("Engine destroyed.");
    }
}
// EngineHolder.java

/**
 * Native source wrapper.
 */
public class EngineHolder {
    private final Engine engine;
    private final String version;

    EngineHolder(Engine engine, String version) {
        this.engine = engine;
        this.version = version;
    }

    public Engine getEngine() {
        return engine;
    }

    public String getVersion() {
        return version;
    }
}
import java.util.UUID;

// EngineConfiguration.java

/**
 * Native source configuration.
 */
public class EngineConfiguration {
    private final String version;

    public EngineConfiguration() {
        // Assign a new version number for configuration.
        this.version = UUID.randomUUID().toString();
    }

    public String getVersion() {
        return version;
    }
}
// SecuredRunnable.java

public class SecuredRunnable implements Runnable {
    private final Runnable runnable;

    public SecuredRunnable(Runnable runnable) {
        this.runnable = runnable;
    }

    @Override
    public void run() {
        try {
            this.runnable.run();
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }
}
import java.lang.ref.PhantomReference;
import java.lang.ref.Reference;
import java.lang.ref.ReferenceQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

// EngineService.java

public class EngineService {
    private static EngineService INSTANCE = null;
    private static final Object INSTANCE_LOCK = new Object();

    private final ReferenceQueue<Engine> engineRefQueue = new ReferenceQueue<>();
    private final ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

    private volatile EngineConfiguration engineConfiguration;

    private volatile EngineHolder engineHolder;

    private EngineService() {
        engineConfiguration = new EngineConfiguration();

        EngineRunnable backgroundDaemon = new EngineRunnable();
        executor.scheduleWithFixedDelay(new SecuredRunnable(backgroundDaemon), 0, 5, TimeUnit.SECONDS);
    }

    public Engine getEngine() {
        return engineHolder != null ? engineHolder.getEngine() : null;
    }

    public void setEngineConfiguration(EngineConfiguration configuration) {
        this.engineConfiguration = configuration;
        // Dispatch job.
        EngineRunnable backgroundDaemon = new EngineRunnable();
        executor.submit(new SecuredRunnable(backgroundDaemon));
    }

    public static EngineService getInstance() {
        synchronized (INSTANCE_LOCK) {
            if (INSTANCE == null) {
                INSTANCE = new EngineService();
            }

            return INSTANCE;
        }
    }

    private static class EngineRunnable implements Runnable {
        @Override
        public void run() {
            EngineHolder engineHolder = INSTANCE.engineHolder;
            EngineConfiguration engineConfiguration = INSTANCE.engineConfiguration;

            // If there is no created engine or the previous engine is outdated, create a new engine.
            if (engineHolder == null || !engineHolder.getVersion().equals(engineConfiguration.getVersion())) {
                Engine engine = new Engine();
                INSTANCE.engineHolder = new EngineHolder(engine, engineConfiguration.getVersion());

                new PhantomReference<>(engine, INSTANCE.engineRefQueue);
                System.out.println("Engine created for version " + engineConfiguration.getVersion());
            }

            Reference<? extends Engine> referenceFromQueue;

            // Clean inaccessible native resources.
            while ((referenceFromQueue = INSTANCE.engineRefQueue.poll()) != null) {
                // This block doesn't work at all.
                System.out.println("Engine will be destroyed.");
                referenceFromQueue.get().close();
                referenceFromQueue.clear();
            }
        }
    }
}
// Application.java

public class Application {
    public static void main(String[] args) throws InterruptedException {
        EngineService engineService = EngineService.getInstance();

        while (true) {
            System.gc();
            engineService.setEngineConfiguration(new EngineConfiguration());
            Thread.sleep(100);
        }
    }
}

person Emre Turan    schedule 01.06.2021    source источник
comment
Нет вопросов...   -  person Alex Shesterov    schedule 01.06.2021
comment
@AlexShesterov Извините, я объяснил проблему выше. Спасибо за отзыв.   -  person Emre Turan    schedule 01.06.2021


Ответы (1)


Я думаю, вы не совсем поняли, как использовать фантомные ссылки.

  1. Из фантомного справочника javadoc:

Метод get фантомной ссылки всегда возвращает null.

Итак, в этой строке вы должны получить NullPointerException:

referenceFromQueue.get().close();
  1. Посмотрите на эту строку:

    INSTANCE.engineHolder = new EngineHolder(engine, engineConfiguration.getVersion());
    

Ссылка на engine всегда доступна (из статического класса), и это означает, что она никогда не будет собрана сборщиком мусора. А это значит, что ваша фантомная ссылка никогда не будет поставлена ​​в очередь в engineRefQueue. Чтобы проверить это, вам нужно убедиться, что вы теряете эту ссылку, и двигатель будет технически доступен только через PhantomReference.

Если сборщик мусора определяет в определенный момент времени, что референт фантомной ссылки является фантомно достижимым, то в это время или позже он поставит ссылку в очередь.

person Igor Konoplyanko    schedule 01.06.2021
comment
На самом деле я знаю о первой проблеме, но проблема в том, что очередь всегда пуста. Так что я ожидаю исключения NullPointerException, вы правы. Я думал, что когда объект EngineHolder недоступен, GC также очистит объект Engine, потому что на него есть ссылка в объекте EngineHolder. - person Emre Turan; 01.06.2021
comment
Доступность вашего GC теперь выглядит так: static EngineService INSTANCE -> INSTANCE.EngineHolder -> Engine engine. Постарайтесь избавиться от статических ссылок, так как они всегда будут доступны сборщику мусора. Посмотрите этот ответ: stackoverflow.com/questions /27186799/ - person Igor Konoplyanko; 01.06.2021
comment
Но в методе запуска я создаю новый Engine и EngineHolder, а затем назначаю его INSTANCE.engineHolder. Итак, предыдущий EngineHolder недоступен. Значит, Engine в предыдущем EngineHolder тоже недоступен, верно? Сборщик мусора должен очистить эту ветку. - person Emre Turan; 01.06.2021
comment
Да, верно, я упустил из виду. Попробуйте убедиться, что PhantomReferences действительно попадают в очередь с reference.isEnqueued()? (тогда нужно провести рефакторинг кода) - person Igor Konoplyanko; 01.06.2021
comment
Я проверю это, но, вероятно, я изменю свою реализацию. Вероятно, я буду реализовывать свою собственную систему сбора мусора для этого исходного кода. - person Emre Turan; 02.06.2021