5

Estoy creando una interfaz para ejecutar métodos al mismo tiempo, mientras abstrae los detalles de sincronización (Para intercambiar para una implementación distribuida cuando sea necesario). Creé una implementación de jvm única que permite que Strings se use como mutex al almacenarlos en un mapa para garantizar que se utiliza una referencia, incluso si se transmiten cadenas de referencias diferentes. La concurrencia parece funcionar bien, sin embargo yo estaba sorprendido al ver que la prueba mostraba que el recuento de referencia nunca está disminuyendo. Supuse que usar WeakValues ​​() sería suficiente para evitar fugas de memoria, pero parece que no es el caso. ¿Alguien puede señalar qué podría estar causando esta fuga?Fuga de memoria en la referencia de mapa weakValue para el método sincronizado

public class SynchronousMethodExecutorSynchronizedImpl implements ISynchronousMethodExecutor { 

// mutex map to provide string references 
final Map<String, String> mutexMap = new MapMaker() 
    .weakValues() 
    .makeComputingMap(
     new Function<String, String>() { 
     @Override 
     public String apply(String id) { 
      return id; 
     } 
    }); 

@Override 
public Object doSynchronousMethod(String domain, String id, ISynchronousMethod synchronousMethod) { 
    synchronized(mutexMap.get(domain + "." + id)) 
    { 
     return synchronousMethod.execute(); 
    } 
} 

}

Aquí está la prueba de que está fallando en el último afirmación:

public class SynchronousMethodExecutorSynchronizedImplTest extends TestCase { 
int counter; 
SynchronousMethodExecutorSynchronizedImpl methodExecutor; 

@Override 
public void before() throws Exception { 
    super.before(); 

    methodExecutor = new SynchronousMethodExecutorSynchronizedImpl(); 
} 

@Test 
public void concurrentExecute() throws InterruptedException { 
    assertEquals(0, counter); 

    for(int i=0; i<1000; i++) 
     getConcurrentExecutorThread().start(); 

    // wait for threads to complete 
    Thread.sleep(1000); 

    assertEquals(1, methodExecutor.mutexMap.size()); 

    try 
    { 
     final List<long[]> infiniteList = new LinkedList<long[]>(); 

     for(long i = Long.MIN_VALUE; i < Long.MAX_VALUE; i++) 
      infiniteList.add(new long[102400]); 

     fail("An OutOfMemoryError should be thrown"); 
    } 
    catch(OutOfMemoryError e) 
    { 

    } 

    assertEquals(2000, counter); 
    assertEquals(0, methodExecutor.mutexMap.size()); 
} 

// synchronous method 
private ISynchronousMethod method = new ISynchronousMethod() { 
    @Override 
    public Object execute() { 
     counter++; 
     return null; 
    } 
}; 

/** 
* Executes a line of code. 
* 
* @return Thread 
*/ 
private Thread getConcurrentExecutorThread() { 
    return new Thread() { 
     @Override 
     public void run() { 
      methodExecutor.doSynchronousMethod("TEST", "1", method); 
      try 
      { 
       Thread.sleep(500); 
      } 
      catch (InterruptedException e) 
      { 

      } 
      methodExecutor.doSynchronousMethod("TEST", new String("1"), method);   
     } 

    }; 
} 

}

Esta última afirmación es lo que rompe la prueba: assertEquals (0 , methodExecutor.mutexMap.size());

Respuesta

5

Está almacenando exactamente el mismo objeto String que la clave y el valor. La clave es una referencia fuerte al objeto, y mientras exista una referencia fuerte a él, la referencia débil a él no tiene sentido. La definición de débilmente accesible (here) establece que:

Un objeto es débilmente alcanzable si no es ni fuerte ni en voz baja alcanzable, pero se puede llegar atravesando una referencia débil.

Por cierto, incluso con esta corrección, no creo que pueda suponer que el mapa siempre estará vacío al final. Probablemente estará cerca de estar vacío, pero creo que eso es todo lo que se puede decir al respecto.

+0

¡Muy bien señor! Lo cambié para almacenar un nuevo String (id), ¡y funciona como un amuleto! –

+0

¿Por qué dice que el mapa no siempre estará vacío? –

+0

@Anthony: podría estar equivocado, pero no creo que haya ninguna garantía sobre cuándo exactamente se eliminarán las referencias débiles. – ColinD

1

Las referencias débiles solo se recopilarán cuando la JVM necesite absolutamente más memoria.

+0

Pero en mi prueba estoy fallando explícitamente si no ocurre un OutOfMemoryError, pero esa afirmación no se rompe. Pongo la última afirmación que se ejecuta después de que se produce el OOM, y espero que los valores se eliminen, pero no lo son. –

+1

OutOfMemoryError se produce porque está almacenando una lista de referencias sólidas. Es decir, su infiniteList es una referencia fuerte y contiene referencias sólidas a matrices de longs. –

+0

Las referencias suaves se recopilan cuando la JVM necesita más memoria. Se pueden recopilar referencias débiles incluso si no lo hace. – ColinD

Cuestiones relacionadas