Does overwriting a variable used in a synchronized lock prevent its garbage collection?

Adam :

I have a cache as a member variable in my service and I'm creating a method to expose it via a JMX MBean, so that I can tear down and recreate my vogon cache at runtime with a new cache expiry time:

public class CachedVogonService implements CachedVogonServiceMBean {

    private LoadingCache<String, Vogon> cache;
    private long expiryInSeconds;
    private VogonService service;

    public CachedVogonService(VogonService newService,
                                long newExpiryInSeconds) {
        this.expiryInSeconds = newExpiryInSeconds;
        this.service = newService;
        this.cache = createCache(newService, newExpiryInSeconds);
    }

    private LoadingCache<String, Vogon> createCache(
            VogonService newService,
            long expiryInSeconds) {
        return CacheBuilder.newBuilder()
                .refreshAfterWrite(expiryInSeconds, TimeUnit.SECONDS)
                .build(new VogonCacheLoader(
                        Executors.newCachedThreadPool(), newService));
    }

    /**
     * This is the method I am exposing in JMX
     */    
    @Override
    public void setExpiryInSeconds(long newExpiryInSeconds) {
        this.expiryInSeconds = newExpiryInSeconds;
        synchronized (this.cache) {
            this.cache = createCache(service, expiryInSeconds);
        }
    }

I'm worried that my locking technique will cause the JVM to keep a reference to the old cache and prevent it being garbage collected.

If my service object loses the reference to the old cache inside the synchronized block, then when execution exits the block, might it then leave the old cache object still marked as locked - making it unavailable for garbage collection?

miskender :

By looking at the byte-code that is generated for similar case, We can see object address of locking field is duplicated and used to acquire and release of the lock. So original locking object is used for locking.

After You change locking field with new object inside synchronized block, another thread can acquire lock on the new object, and can enter the synchronized code block. Using synchronized like this, does not provide synchronization between threads. You should use another object for locking. (like final Object cacheLock = new Object())

Just for information purposes, this kind of usage does not prevent garbage collection. Since object addresses mentioned here are inside the stack frame of this method, once the method finishes execution, stack frame will be destroyed and no reference to old object will remain. (But don't use synchronized like this.)

You can check JVM Instruction Set here

public class SyncTest {

    private Long value;

    public static void main(String[] args) {
        new SyncTest().testLock();
    }

    public SyncTest() {
        value = new Long(1);
    }

    private void testLock() {
        synchronized (this.value) {
            this.value = new Long(15);
        }
    }
}

  private void testLock();
     0  aload_0 [this]
     1  getfield t1.SyncTest.value : java.lang.Long [27] // push value field to operand stack
     4  dup // duplicate value field push it to operand stack
     5  astore_1 // store the top of the operand stack at [1] of local variable array (which is the duplicated value field)
     6  monitorenter // aquire lock on top of the operand stack 
     7  aload_0 [this]
     8  new java.lang.Long [22]
    11  dup
    12  ldc2_w <Long 15> [31]
    15  invokespecial java.lang.Long(long) [24]
    18  putfield t1.SyncTest.value : java.lang.Long [27]
    21  aload_1 // load the [1] of local variable array to the operand stack (which is the duplicated value field previously stored)
    22  monitorexit // release the lock on top of the operand stack
    23  goto 29
    26  aload_1
    27  monitorexit
    .....

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=160422&siteId=1