2017-06-13 2 views
3

Je suis en train de déboguer une fuite de mémoire et j'ai dû plonger dans des internes CompletableFuture. Il y a ce morceau de code (CompletableFuture.uniComposeStage):Expulsion d'objets non intuitive à partir de la récupération de place

CompletableFuture<V> g = f.apply(t).toCompletableFuture(); 
... 
CompletableFuture<V> d = new CompletableFuture<V>(); 
UniRelay<V> copy = new UniRelay<V>(d, g); 
g.push(copy); 
copy.tryFire(SYNC); 
return d; 

Le code lui-même est tout à fait clair pour moi: appliquer une fonction qui retourne CompletionStage (g), créer un relais qui sera éventuellement des transferts de fonds à un autre CompletableFuture (d), puis renvoyer cet autre futur (d). Je vois suivant la situation de référence:

  • copy références à la fois d et g (et il n'y a pas de magie dans le constructeur, seules les affectations de terrain)
  • g références copy
  • d références rien

seulement d est retourné, donc, en fait, les deux g et copy semblent être une méthode interne varia Bles à moi, que (à première vue) ne devrait jamais quitter la méthode et être finalement gc'd. Les deux tests naïfs et le fait qu'il a été écrit il ya longtemps par des développeurs prouvés me suggère que je me trompe et qu'il manque quelque chose. Quelle est la raison pour laquelle ces objets sont omis de la récupération de place?

+0

Ok, j'ai raté l'appel tryFire, mais je ne sais toujours pas ce qui se passe à l'intérieur (et le code est assez difficile à lire), donc les réponses sont toujours très appréciées – Etki

+0

Vous devriez voir quelles références les empêchent d'être GC si vous regarde le tas de tas. – NickL

Répondre

2

Dans le code cité, rien n'empêche la récupération de place de ces futurs et il n'y a pas besoin de le faire. Ce code s'applique au scénario selon lequel le premier CompletableFuture (l'instance this) a été terminé et le CompletableFuture renvoyé par la fonction de composition directement évaluée n'est pas encore terminé.

Maintenant, il y a deux scénarios possibles

  1. Il y a une tentative d'achèvement en cours. Ensuite, le code qui finira par compléter le futur contiendra une référence à celui-ci et à la fin, il déclenchera l'achèvement des étapes dépendantes (enregistrées via g.push(copy)). Dans ce scénario, il n'est pas nécessaire que l'étape dépendante contienne une référence à son étape de prérequis.

    Ceci est un motif général. S'il y a une chaîne x --will complete-→ y, il n'y aura pas de référence de y à x.

  2. Il n'y a pas d'autre référence à cela CompletableFuture instance g et g n'a pas encore été achevée. Dans ce cas, il ne sera jamais terminé du tout et le fait de détenir une référence à g en interne ne changerait pas cela. Cela ne ferait que gaspiller des ressources.

Le programme d'exemple suivant illustre ceci:

public static void main(String[] args) throws Throwable { 
    ReferenceQueue<Object> discovered = new ReferenceQueue<>(); 
    Set<WeakReference<?>> holder = new HashSet<>(); 

    CompletableFuture<Object> initial = CompletableFuture.completedFuture("somevalue"); 

    CompletableFuture<Object> finalStage = initial.thenCompose(value -> { 
     CompletableFuture<Object> lost = new CompletableFuture<>(); 
     holder.add(new WeakReference<>(lost, discovered)); 
     return lost; 
    }); 
    waitFor(finalStage, holder, discovered); 
    finalStage = initial.thenCompose(value -> { 
     CompletableFuture<Object> saved = CompletableFuture.supplyAsync(()-> { 
      LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(1)); 
      return "newvalue"; 
     }); 
     holder.add(new WeakReference<>(saved, discovered)); 
     return saved; 
    }); 
    waitFor(finalStage, holder, discovered); 
} 
private static void waitFor(CompletableFuture<Object> f, Set<WeakReference<?>> holder, 
        ReferenceQueue<Object> discovered) throws InterruptedException { 
    while(!f.isDone() && !holder.isEmpty()) { 
     System.gc(); 
     Reference<?> removed = discovered.remove(100); 
     if(removed != null) { 
      holder.remove(removed); 
      System.out.println("future has been garbage collected"); 
     } 
    } 
    if(f.isDone()) { 
     System.out.println("stage completed with "+f.join()); 
     holder.clear(); 
    } 
} 

La première fonction passée à thenCompose crée et retourne un nouveau inachevés CompletableFuture sans aucune tentative de le compléter, et non la tenue, ni stocker toute autre référence à il. En revanche, la deuxième fonction crée le CompletableFuture via supplyAsync fournissant un Supplier qui retournera une valeur après une seconde.

Sur mon système, il toujours imprimé

future has been garbage collected 
stage completed with newvalue 

montrant que l'avenir abandonné ne sera pas empêché de collecte des ordures tandis que l'autre aura lieu au moins jusqu'à la fin.