1

当无法再访问本机资源时,我正在尝试清理它。该资源提供了一种清理分配资源(内存、线程等)的方法。为此,我使用了 Phantom Reference。

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

问题是,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.");
    }
}
// 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);
        }
    }
}
4

2 回答 2

1

我认为您不太了解如何使用 Phantom References。

  1. 来自幻影参考javadoc

幻像引用的 get 方法总是返回 null。

所以在这一行中你应该得到 NullPointerException:

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

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

引用engine始终是可访问的(来自静态类),这意味着它永远不会被 GC 收集。这意味着您的幻像引用将永远不会被排入engineRefQueue. 为了测试它,你需要确保你失去了这个引用,并且引擎在技术上只能通过PhantomReference.

如果垃圾收集器在某个时间点确定幻影引用的所指对象是幻影可到达的,那么在那个时间或稍后的某个时间它将将该引用入队。

于 2021-06-01T08:26:42.127 回答
1

java.lang.ref文档

注册的引用对象与其队列之间的关系是片面的。也就是说,队列不会跟踪向其注册的引用。如果已注册的引用本身变得无法访问,那么它将永远不会入队。只要程序对其所指对象感兴趣,程序就有责任使用引用对象来确保对象保持可访问性。

您的程序直接违反了这样做

new PhantomReference<>(engine, INSTANCE.engineRefQueue);

不保留对PhantomReference. 因此,由于队列不维护对PhantomReference两者的引用,它PhantomReference本身是不可访问的,只是被垃圾收集。

于 2022-02-09T08:39:39.570 回答