简体   繁体   中英

Does Java guarantee that currently synchronized objects are not garbage collected?

Is there any guarantee that an object will not be garbage collected while a thread holds its monitor?

eg

class x {
    private WeakReference<Object> r;

    Object getMonitorObject() {
        Object o = new Object();
        r = new WeakReference<>(o);
        return o;
    }

    void thread1() throws Exception {
        synchronized (getMonitorObject()) {
            Thread.sleep(3000);
        }
    }

    void thread2() {
        Object b = r.get();
    }
}

In this case concretely, is there any guarantee that b will be non- null if thread2() is called while another thread is sleeping in thread1() ? Let's just assume the whole of thread2() is executed while thread1() is sleeping in a different thread.

In order for the end of the synchronized block to remove the monitor lock, the synchronized block has to keep a reference to the object returned by getMonitorObject() .

That reference prevents GC, so answer is yes.

In all Java implementations that I have examined in detail, the state of an object's primitive mutex or lock is represented in part 1 by bits in the object header. When the lock is released, the JVM needs to update the header bits 2 , so it must still have a reference to the object, either on the stack or in a register.

1 - When mutex contention occurs, the lock is "inflated" to record extra information. So we can't say that the entire state is in the object header.

2 - In most situations, the unlock code won't know if the lock object is unreachable. But if it did due to aggressive JIT compiler optimization, then hypothetically it could also know that updating the object header was no longer necessary.


But suppose that the object reference wasn't used / needed when unlocking a mutex.

Here is the definition of reachability from the JLS 12.6.1 :

"A reachable object is any object that can be accessed in any potential continuing computation from any live thread." "A finalizer-reachable object can be reached from some finalizable object through some chain of references, but not from any live thread."

"An unreachable object cannot be reached by either means."

In order for an object to be a candidate for garbage collection, it must not be reachable. In other words it must not be accessible from any live thread.

What about a mutex that is locked? Well, the "potential continuing computation" of a thread may need to unlock the mutex:

  • If the mutex cannot be unlocked without the reference to the object, then the object is reachable.
  • If the mutex can be unlocked without the reference to the object, it can, then the lock could be unreachable, or finalizer-reachable.

But wait ...

In JLS 12.6.2 , there is some complicated language about reachability decision points.

"At each reachability decision point, some set of objects are marked as unreachable, and some subset of those objects are marked as finalizable."

" If an object X is marked as unreachable at di, then: - ... - All active uses of X in thread t that come-after di must occur in the finalizerinvocation for X or as a result of thread t performing a read that comes-after di of a reference to X; and - ..."

"An action a is an active use of X if and only if at least one of the following is true: - ... - a locks or unlocks X and there is a lock action on X that happens-after the invocation of the finalizer for X . - ..."

Now if I understand it correctly, that is saying that an object which is finalizable cannot be finalized if an active thread could still release the corresponding mutex.


In summary:

  1. In current JVMs, the lock state of a mutex depends on the object header. If a mutex could still be released by a thread, then the object has to be reachable ... as an implementation detail.

  2. Assuming that there exists a JVM where a mutex can be released without reference to the object, then an object could be unreachable while it is still locked.

  3. But if the object is finalizable, then it will not be finalized until after all active (application) threads that might need to release the lock have done so.

Synchronization can prevent the garbage collection, but not in general. In your specific case, it is not guaranteed.

Compare with JLS §12.6.1

Transformations of this sort may result in invocations of the finalize method occurring earlier than might be otherwise expected. In order to allow the user to prevent this, we enforce the notion that synchronization may keep the object alive. If an object's finalizer can result in synchronization on that object, then that object must be alive and considered reachable whenever a lock is held on it.

Note that this does not prevent synchronization elimination: synchronization only keeps an object alive if a finalizer might synchronize on it. Since the finalizer occurs in another thread, in many cases the synchronization could not be removed anyway.

So, since your object has no custom finalizer, no synchronization during finalization may happen and in principle, your object is a temporary object that allows lock elimination, in which case it would not prevent the garbage collection.

But there is the practical obstacle that you stored a WeakReference in a way that another thread could retrieve the object while it is not collected and once this possibility exist, the object is not local anymore and lock elimination can not be applied.

A theoretical implementation that aggressively collects the object immediately after construction (or eliminates its existence completely) and clears the weak reference before it escapes or creates an empty WeakReference in the first place, would be within the specification, as in that execution scenario, the lock elimination is justified.


Note that even if you insert a reachabilityFence , there is no happens-before relationship between a thread calling thread1() and another calling thread2() , so the second thread may always behave as-if executing thread2() after the other completed the synchronized block or passed the reachability fence, even if your real life clock tells otherwise. It's said explicitly that Thread.sleep has no synchronization semantics.

I've just come across an "aside" in the javadocs that says this:

"[The m]ethod reachabilityFence is not required in constructions that themselves ensure reachability. For example, because objects that are locked cannot, in general, be reclaimed, it would suffice if all accesses of the object, in all methods of class Resource (including finalize) were enclosed in synchronized (this) blocks."

This seems to say that objects that are locked cannot be garbage collected.

I'm not sure if that takes precedence over JLS 12.6.1 and 12.6.2; see my other answer, or if we should read this as only applying to Java (language) implementations for the Oracle / OpenJDK Java class libraries.

The sleep method doesn't leave the syncronized block and hence doesn't release the lock or monitor object, it just blocks the execution for a while. Since the lock was never released, garbage collector will not collect it unless the thread that is holding it finishes execution of syncronized block or release it using wait() method.

So, yes it is guaranteed to be non null for thread2() provided thread1() had enough time to finish getMonitorObject() call.

Update : Aha! Now I see the weak reference there. Silly me!

Yes, weak references are automatically eligible for garbage collection. But it lies within the scope of the class, so it is not that weak to say. It will not be eligible for garbage collection unless the instance of class x is in scope. So, yes in this case it is guaranteed to be non null .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM