链式CompletableFuture的内存管理

时间:2019-05-07 21:34:11

标签: java asynchronous memory-management garbage-collection completable-future

我正在编写一个异步客户端。每次发送请求时,客户端都会在内部创建,注册,然后返回CompletableFuture,一旦收到服务器对发送请求的响应,该请求便会完成。

问题是,某些请求永远不会收到响应,并且客户端代码会在超时后放弃这些请求。因此,我最终获得了多个已注册的CompletableFuture,客户端代码不再保留对其的引用,因此应该可以进行垃圾回收。

为了避免内存泄漏,我决定更改客户端的实现,以将WeakReference保留为返回的CompletableFuture而不是强数,以使未引用的期货获得如果客户端代码不再需要它们,则将其驱逐。

以下(简化的)代码演示了客户端:

interface Message {
    Object getID();
    byte[] toByteArray();
}

abstract class AsyncClient<T extends Message, R extends Message> {
    /**
     * This map serves as a registry of CompletableFutures for sent requests.
     * 
     * Previously it was Map<Object, CompletableFuture<R>>. Changed to References
     * to avoid CompletableFutures staying in registry after the recipient code
     * does not wait for them anymore.
     */
    Map<Object, Reference<CompletableFuture<R>>> requestFutureRegistry = new ConcurrentHashMap<>();

    /**
     * Write the request to the service
     * @param serializedRequest bytes to write
     */
    protected abstract void writeRequest(byte[] serializedRequest);

    /**
     * Make sure to clean up the registry from Futures that arte not held by the 
     * recipient code anymore.
     */
    private void deleteDanglingFutures() {
        requestFutureRegistry.entrySet().removeIf(entry -> entry.getValue().get() == null);
    }

    /**
     * 
     * @param request to be written to service
     * @return a CompletableFuture for the request's response
     */
    public CompletableFuture<R> sendRequest(T request) {
        deleteDanglingFutures();

        CompletableFuture<R> future = new CompletableFuture<>();
        requestFutureRegistry.put(request.getID(), new WeakReference<>(future));

        writeRequest(request.toByteArray());

        return future;
    }

    void receivedResponse(R response) {
        deleteDanglingFutures();

        Reference<CompletableFuture<R>> futureRef = requestFutureRegistry.get(response.getID());
        if (futureRef != null) {
            CompletableFuture<R> future = futureRef.get();
            if (future != null) {
                future.complete(response);
            }
        }
    }
}

这种方法似乎有效,直到我开始使用thenXXX方法将CompletableFuture链接起来。例如,当我执行以下操作时:

AsyncClient client;
Request request1;
// ...

Response response = client.sendRequest(request1)
                          .thenCompose(response1 -> 
                                       client.sendRequest(request2)
                           ).get(timeout, TimeUnit.MILLISECONDS);

我注意到内部的未来(从对client.sendRequest(request2)的呼叫)有时在获得完成机会之前就已经进行了GC。

深入研究JDK实现,似乎没有对内部未来的强大参考存储(如this post中所述)。

我的问题是-如果客户代码不再(直接或通过thenXXX链接)引用已注册的期货,并且仍保留足够长的时间以防万一,则正确的方法是确保注销已注册的期货还需要吗?

编辑+动机

我现在了解(感谢@Holger)Future::get不是CompletableFuture的常用用法,但是,在我的情况下,这很常见。具体来说,我需要支持的一个用例是发送多个请求,等待第一个请求响应(使用anyOf,然后使用get超时)。事先知道,许多请求将永远不会从服务中得到响应,因此我需要(最好是自动地)在超时后清理未使用的期货。

0 个答案:

没有答案