Java 是否保证当前同步的对象不被垃圾回收?

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

是否可以保证当线程持有监视器时对象不会被垃圾回收?

例如

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

具体来说,在这种情况下,如果在 thread1() 中另一个线程正在休眠时调用 thread2(),是否可以保证 b 将不是 null?我们假设整个 thread2() 被执行,而 thread1() 在另一个线程中休眠。

为了在 synchronized 块的末尾移除监视器锁,synchronized 块必须保留对 getMonitorObject().[=13 返回的对象的引用=]

该引用阻止了 GC,所以答案是肯定的。

sleep 方法不会离开同步块,因此不会释放锁或监视器对象,它只是暂时阻塞执行。由于锁从未被释放,垃圾收集器不会收集它,除非持有它的线程完成同步块的执行或使用 wait() 方法释放它。

所以,是的,如果 thread1() 有足够的时间完成 getMonitorObject() 调用,它保证 thread2() 是非空的。

更新:啊哈!现在我看到那里的弱参考。傻我!

是的,弱引用自动符合垃圾回收条件。但它在class的范围内,所以没那么弱。除非 class x 的实例在范围内,否则它将不符合垃圾回收条件。所以,是的,在这种情况下,它 保证是非空的

同步可以防止垃圾回收,但不是一般情况。在您的具体情况下,不能保证。

比较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.

因此,由于您的对象没有自定义终结器,因此在终结期间可能不会发生同步,原则上,您的对象是一个允许消除锁的临时对象,在这种情况下它不会阻止垃圾收集。

但是存在一个实际障碍,即您存储 WeakReference 的方式使得另一个线程可以在未收集对象时检索该对象,一旦存在这种可能性,该对象就不再是本地对象并消除锁定无法申请。

理论上的实现在构造后立即积极收集对象(或完全消除它的存在)并在它逃逸之前清除弱引用或首先创建一个空 WeakReference,将在规范内,就像在那个执行场景中一样,锁消除是合理的。


请注意,即使您插入 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 explicitlyThread.sleep 也没有同步语义。

在我详细检查过的所有 Java 实现中,object 的原始互斥锁或锁的状态在部分 1 中表示按 object header 中的位。当锁被释放时,JVM需要更新header位2,所以它必须仍然有对object的引用,或者在栈上或者在寄存器中。

1 - 当发生互斥锁争用时,锁"inflated"记录额外信息。所以我们不能说entire状态在objectheader.

2 - 在大多数情况下,解锁代码不知道锁 object 是否无法访问。但如果它确实是由于积极的 JIT 编译器优化,那么假设它也可以知道不再需要更新 object header。


但是假设在解锁互斥量时未使用/不需要 object 引用。

这是 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."

为了使 object 成为垃圾 collection 的候选对象,它必须不可访问。换句话说,它不能从任何实时线程访问。

锁定的互斥体怎么办?那么,一个线程的"potential continuing computation"可能需要解锁互斥量:

  • 如果不引用 object 就无法解锁互斥锁,则可以访问 object。
  • 如果可以在不引用 object 的情况下解锁互斥量,那么锁 可能 无法访问,或者 finalizer-reachable。

但是等等...

JLS 12.6.2中,有一些关于可达性决策点的复杂语言。

"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. - ..."

现在,如果我理解正确的话,那就是说,如果活动线程 可以 仍然释放相应的互斥锁,则可终结的 object 无法终结。


总结:

  1. 在当前的 JVM 中,互斥锁的锁定状态取决于 object header。如果一个互斥量 可以 仍然被一个线程释放,那么 object 必须是可访问的......作为一个实现细节。

  2. 假设存在一个 JVM,其中互斥量 可以 在不引用 object 的情况下被释放,那么 object 可能在它仍然被锁定时无法访问。

  3. 但是如果 object 是可终结的,那么它不会被终结,直到所有可能需要释放锁的活动(应用程序)线程都这样做了。

我刚刚在 javadocs 中看到一个 "aside" 是这样说的:

"[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."

这好像是说被锁定的对象不能被垃圾回收。

我不确定这是否优先于 JLS 12.6.1 和 12.6.2;请参阅我的其他答案,或者我们是否应该将此解读为仅适用于 Oracle / OpenJDK Java class 库的 Java(语言)实现。