繁体   English   中英

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()返回的对象的引用。

该参考可以防止GC,因此答案是肯定的。

在我详细检查过的所有Java实现中,对象的原始互斥或锁的状态在第1部分中由对象标头中的位表示。 释放锁定后,JVM需要更新标头位2 ,因此它仍然必须在堆栈上或在寄存器中具有对该对象的引用。

1-发生互斥争用时,锁“膨胀”以记录额外的信息。 所以我们不能说整个状态都在对象头中。

2-在大多数情况下,解锁代码不会知道锁定对象是否不可访问。 但是,如果这样做是由于积极的JIT编译器优化而引起的,则假设它也可以知道不再需要更新对象标头。


但是,假设在解锁互斥锁时未使用/不需要对象引用。

这是JLS 12.6.1中的可达性定义:

可达对象是可以从任何活动线程进行任何潜在的连续计算中访问的任何对象。” “可以通过某个引用链从某个可终结对象访问终结器可到达的对象,但不能从任何活动线程中访问”。

“任何一种方法都无法达到无法到达的物体。”

为了使对象成为垃圾收集的候选对象,它必须不可访问。 换句话说,它一定不能从任何活动线程访问。

锁定的互斥锁呢? 好吧,线程的“潜在连续计算”可能需要解锁互斥锁:

  • 如果在没有引用该对象的情况下无法解锁该互斥锁,则该对象是可访问的。
  • 如果互斥锁可以在没有引用该对象的情况下被解锁,则该锁可以是不可达的,或者终结器可达的。

可是等等 ...

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

“在每个可达性决策点,将某些对象集标记为不可访问,并将这些对象的某些子集标记为可终结。”

“如果将对象X标记为在di处不可访问,则:-...-线程t中X之后对x的所有有效使用都必须发生在X的finalizer调用中,或者是由于线程t执行了读取操作而导致的引用X的di之后;并且-...”

“一个动作a是主动使用的X ,当且仅当以下条件至少有一个为真: - ...... - a锁定或解锁X并且对锁操作X出现这种情况,之后终结的调用代表X 。-...”

现在,如果我正确理解它,那就意味着如果活动线程仍可以释放相应的互斥对象,则无法完成可终结对象。


综上所述:

  1. 在当前的JVM中,互斥锁的锁定状态取决于对象标头。 如果一个互斥体仍然可以由一个线程被释放,那么对象必须是可到达的......作为一个实现细节。

  2. 假设存在一个可以释放互斥对象而无需引用该对象的JVM,则在对象仍处于锁定状态时它可能无法访问。

  3. 但是,如果对象是可终结的,则只有在可能需要释放锁的所有活动(应用程序)线程这样做之后,它才会终结。

同步可以阻止垃圾回收,但通常不会。 在您的特定情况下,不能保证。

JLS§12.6.1比较

这种转换可能导致对finalize方法的调用比预期的要早。 为了允许用户防止这种情况,我们强制执行以下概念:同步可以使对象保持活动状态。 如果对象的终结器可以导致该对象上的同步,则该对象必须处于活动状态,并且只要在其上持有锁,就认为该对象可以访问。

请注意,这不会阻止消除同步:如果终结器可能在对象上进行同步,则同步只会使对象保持活动状态。 由于终结器发生在另一个线程中,因此在许多情况下无论如何都无法删除同步。

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

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

理论上的实现是在规范内执行的,这种理论实现在构造后立即主动收集对象(或完全消除其存在)并在弱引用逃逸或首先创建空的WeakReference之前清除该弱引用,例如在该执行方案中,该锁消除是有道理的。


请注意,即使您插入了reachabilityFence ,调用thread1()的线程与另一个调用thread2()的线程之间也没有发生事前关系,因此第二个线程的行为就好像在另一个线程完成synchronized之后执行thread2()阻止或通过可达性围栏,即使您的真实生活时钟告诉您其他情况也是如此。 明确地Thread.sleep没有同步语义。

我刚刚在javadocs中碰到了这样的“旁听”:

“方法本身可确保可到达reachabilityFence结构不需要[m] ethodreachabilityFence。例如,由于通常无法回收已锁定的对象,因此在Resource类的所有方法中,如果对象的所有访问权限都足够(包括finalize)包含在同步的(this)块中。”

这似乎表明不能对已锁定的对象进行垃圾回收。

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

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

因此,是的,只要thread1()有足够的时间完成getMonitorObject()调用,就可以保证它不会为thread2()

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

是的,弱引用可以自动进行垃圾收集。 但这属于课程范围,所以说起来并不那么弱 除非类x的实例在范围内,否则它将不符合垃圾回收的条件。 因此,是的,在这种情况下,可以保证不为null

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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