当无法再访问本机资源时,我正在尝试清理它。该资源提供了一种清理分配资源(内存、线程等)的方法。为此,我使用了 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);
}
}
}