5

I am creating an interface for executing methods concurrently, while abstracting away the synchronization details (To swap for a distributed implementation when needed). I've created a single jvm implementation that allows Strings to be used as the mutex by storing them in a map to ensure one reference is used, even if Strings of different references are passed in. The concurrency seems to work fine, however I was surprised to see that the test was showing the reference count is never decreasing. I assumed using WeakValues() would be enough to prevent memory leaks, but it seems that is not the case. Can anyone point out what could be causing this leak?

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();
    } 
}

}

Here is the test that is failing at the very last assertion:

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);        
        }

    };
}

}

This last assertion is what breaks the test: assertEquals(0, methodExecutor.mutexMap.size());

2 Answers2

5

You're storing the exact same String object as both the key and the value. The key is a strong reference to the object, and as long as a strong reference to it exists, the weak reference to it is meaningless. The definition of weakly reachable (here) states that:

An object is weakly reachable if it is neither strongly nor softly reachable but can be reached by traversing a weak reference.

By the way, even with this corrected I don't think you can assume that the map will always be empty at the end. It will probably be close to empty, but I think that's all that can be said about it.

ColinD
  • 108,630
  • 30
  • 201
  • 202
1

Weak references will only be collected when the JVM absolutely needs more memory.

Jack Edmonds
  • 31,931
  • 18
  • 65
  • 77
  • But in my test I am explicitly failing if an OutOfMemoryError does not occur, but that assertion does not break. I put the last assertion which runs after the OOM occurs, and I expect there the values to be removed but they are not. – Anthony DePalma Jun 21 '11 at 01:13
  • 1
    The OutOfMemoryError is occurring because you are storing a list of strong references. That is, your infiniteList is a strong reference and contains strong references to arrays of longs. – Jack Edmonds Jun 21 '11 at 01:14
  • Soft references are collected when the JVM needs more memory. Weak references may be collected even if it doesn't. – ColinD Jun 21 '11 at 01:26