Java 引用队列始终为空

Java 引用队列始终为空,java,phantom-reference,Java,Phantom Reference,我正在尝试清理无法访问的本机资源。该资源提供了清理已分配资源(内存、线程等)的方法。为了实现这一点,我使用了幻影参考 当库用户提供新配置时,应异步创建该资源 问题是,ReferenceQueue总是空的。我不引用文件之外的本机资源。即使在这种情况下,poll()方法也会返回null。所以我无法清理资源,导致内存泄漏。我怎样才能避免这种情况 您可以在下面找到示例代码。我使用了JDK 8 // Engine.java import java.util.HashMap; import java.ut

我正在尝试清理无法访问的本机资源。该资源提供了清理已分配资源(内存、线程等)的方法。为了实现这一点,我使用了幻影参考

当库用户提供新配置时,应异步创建该资源

问题是,ReferenceQueue总是空的。我不引用文件之外的本机资源。即使在这种情况下,poll()方法也会返回null。所以我无法清理资源,导致内存泄漏。我怎样才能避免这种情况

您可以在下面找到示例代码。我使用了JDK 8

// 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.");
    }
}

我想你不太明白如何使用幻象参照

  • 从幻影参考:
  • 幻影引用的get方法始终返回null

    因此,在这一行中,您应该得到NullPointerException:

    referenceFromQueue.get().close();
    
  • 请看这一行:

    INSTANCE.engineHolder = new EngineHolder(engine, engineConfiguration.getVersion());
    
  • 引擎的引用始终是可访问的(从静态类),这意味着它永远不会被GC收集。这意味着您的虚拟引用将永远不会排队进入
    engineRefQueue
    。 为了测试它,您需要确保您正在失去此引用,并且引擎在技术上只能通过
    PhantomReference
    访问

    如果垃圾收集器在某个时间点确定幻象引用的引用对象是幻象可访问的,那么在该时间或稍后某个时间它将使引用排队


    毫无疑问…@AlexShesterov抱歉,我解释了上面的问题。谢谢你的反馈。实际上我知道第一个问题,但问题是,队列总是空的。所以我期待一个NullPointerException,你是对的。我认为当EngineHolder对象不可访问时,GC也会清理Engine对象,因为它在EngineHolder对象中被引用。您的GC可访问性现在如下所示:static EngineService INSTANCE->INSTANCE.EngineHolder->Engine Engine。尝试摆脱静态引用,因为它们总是可以通过GC访问的。检查这个答案:但在run方法中,我创建了一个新引擎和EngineHolder,然后将其分配给INSTANCE.EngineHolder。因此,以前的EngineHolder无法访问。也就是说,前一个EngineHolder中的引擎也无法访问,对吗?总承包商应该清理那个分支。是的,没错,我忽略了这一点。请尝试确保PhantomReferences确实进入队列中,并使用
    reference.iseQueued()
    ?(需要重构一些代码)我会检查它,但可能会更改我的实现。可能我会为该本地源实现自己的垃圾收集系统。
    // 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);
            }
        }
    }
    
    referenceFromQueue.get().close();
    
    INSTANCE.engineHolder = new EngineHolder(engine, engineConfiguration.getVersion());