繁体   English   中英

删除未使用的字段是否会导致垃圾回收?

[英]May the removal of an unused field cause a garbage collection?

对于涉及异步操作的库,我必须保持对对象的引用,直到满足某个条件。

(我知道,这听起来很不寻常。所以这里有一些上下文,虽然它可能并不严格相关:该对象可能被认为是在JNI操作中使用的直接 ByteBuffer .JNI操作将获取缓冲区的地址。此时,该地址只是一个“指针”, 被视为对字节缓冲区的引用。该地址可以在以后的时间内异步使用。因此,必须防止缓冲区被垃圾收集,直到JNI操作完成。)

为实现这一点,我实现了一个基本上等同于此的方法:

private static void keepReference(final Object object)
{
    Runnable runnable = new Runnable()
    {
        @SuppressWarnings("unused")
        private Object localObject = object;

        public void run()
        {
            // Do something that does NOT involve the "localObject" ...
            waitUntilCertainCondition();

            // When this is done, the localObject may be garbage collected
        }
    };
    someExecutor.execute(runnable);
}

我们的想法是创建一个Runnable实例,该实例将所需对象作为字段 ,将此runnable抛入执行程序,并让runnable等待直到满足条件。 执行程序将保持对可运行实例的引用,直到它被完成为止。 runnable 应该保持对所需对象的引用。 因此,只有满足条件之后 ,执行程序才会释放runnable,因此本地对象将有资格进行垃圾回收。

localObject字段run()方法的主体中使用。 可能编译器(或更准确地说:运行时)检测到这一点,并决定删除这个未使用的引用,从而允许对象过早地进行垃圾回收?

(我考虑过这方面的解决方法。例如,在logger.log(FINEST, localObject);中使用“虚拟语句”中的对象logger.log(FINEST, localObject);但即使这样,也不能确定“智能”优化器不会做某些事情内联并仍然检测到对象未被真正使用)


更新 :正如评论中所指出的:这是否完全可能取决于Executor的确切实现(尽管我必须更仔细地分析它)。 在给定的情况下,执行程序将是ThreadPoolExecutor

这可能是迈向答案的一步:

ThreadPoolExecutor有一个afterExecute方法。 可以覆盖此方法,然后使用反射大锤深入到作为参数给出的Runnable实例。 现在,人们可以简单地使用反射黑客走到这个引用,并使用runnable.getClass().getDeclaredFields()来获取字段(即localObject字段),然后获取该字段的值。 而且我认为 应该让它观察到与原来的价值不同的价值。

另一条评论指出, afterExecute的默认实现是空的,但我不确定这个事实是否会影响该字段是否可以删除的问题。

现在,我强烈认为该字段可能无法删除。 但是一些明确的参考(或至少更有说服力的论点)会很好。


更新2 :基于Holger的评论和答案 ,我认为不是删除“ 字段本身”可能是一个问题,而是周围的Runnable实例的GC。 所以现在,我假设有人可以尝试这样的事情:

private static long dummyCounter = 0;
private static Executor executor = new ThreadPoolExecutor(...) {
    @Override
    public void afterExecute(Runnable r, Throwable t) {
        if (r != null) dummyCounter++;
        if (dummyCounter == Long.MAX_VALUE) {
            System.out.println("This will never happen", r);
        }
    }
}

确保runnable中的localObject 确实存在,只要它应该存在。 但是我几乎不记得曾经被迫写过像这几行代码一样大声尖叫“粗暴黑客”的东西......

如果JNI代码获取直接缓冲区的地址,则JNI代码本身应负责,只要JNI代码保存指针,就可以保存对直接缓冲区对象的引用,例如使用NewGlobalRefDeleteGlobalRef

关于您的具体问题,这可以直接在JLS§12.6.1中解决。 实施定稿

可以设计优化程序的转换,以减少可达到的对象的数量,使其少于可以被认为可达的对象的数量。 ...

如果对象字段中的值存储在寄存器中,则会出现另一个示例。 ...请注意,只有在引用位于堆栈上且未存储在堆中时,才允许进行此类优化。

(最后一句很重要)

在该章中通过一个与你的例子没有太大不同的例子来说明。 简而言之, Runnable实例中的localObject引用将使引用对象的生命周期至少与Runnable实例的生命周期一样长。

也就是说,这里的关键点是Runnable实例的实际生命周期。 由于上面指定的规则,如果它也被一个不受优化影响的对象引用,它将被认为是绝对活着的,即不受优化影响,但即使Executor也不一定是全局可见对象。

也就是说,方法内联是最简单的优化之一,之后JVM会检测到ThreadPoolExecutorafterExecute是无操作。 顺便说一句,在Runnable传递给它的 Runnable传递到execute ,但传递给它不会是相同的submit在后一种情况下,如果您使用的方法,如头(只),它包裹在一个RunnableFuture

请注意,即使正在执行run()方法也不会阻止Runnable实现的实例的收集,如“在Java 8中对强可达对象调用的finalize()”中所示

最重要的是,当你试图打击垃圾收集器时,你将走在薄冰上。 正如上面引用的第一句话所述:“ 可以设计优化程序的转换,以减少可达到的对象数量,使其少于可以被认为可达的对象数量。 “虽然我们都可能发现自己的想法过于天真......

正如开头所说,你可以重新考虑责任。 值得注意的是,当你的类有一个close()方法,必须在所有线程完成工作后调用它来释放资源,这个必需的显式操作已经足以阻止资源的早期收集(假设该方法确实被称为正确的点)...

在线程池中执行Runnable不足以防止对象被垃圾回收。 即使是“这个”也可以收集! JDK-8055183

以下示例显示keepReference并未真正保留它。 虽然vanilla JDK不会出现问题(因为编译器不够智能),但是当对ThreadPoolExecutor.afterExecute的调用被注释掉时,它可以被重现。 绝对可能是优化,因为在默认的ThreadPoolExecutor实现中, afterExecute是no-op。

import java.lang.ref.WeakReference;
import java.util.concurrent.*;

public class StrangeGC {
    private static final ExecutorService someExecutor =
        Executors.newSingleThreadExecutor();

    private static void keepReference(final Object object) {
        Runnable runnable = new Runnable() {
            @SuppressWarnings("unused")
            private Object localObject = object;

            public void run() {
                WeakReference<?> ref = new WeakReference<>(object);
                if (ThreadLocalRandom.current().nextInt(1024) == 0) {
                    System.gc();
                }
                if (ref.get() == null) {
                    System.out.println("Object is garbage collected");
                    System.exit(0);
                }
            }
        };
        someExecutor.execute(runnable);
    }

    public static void main(String[] args) throws Exception {
        while (true) {
            keepReference(new Object());
        }
    }
}

你的hack with afterExecute将会工作。
你基本上发明了一种可达性栅栏 ,参见JDK-8133348

你所面临的问题是众所周知的。 它将作为JEP 193的一部分在Java 9中得到解决。 将有一个标准API将对象显式标记为可访问: Reference.reachabilityFence(obj)

更新

Reference.reachabilityFence Javadoc注释建议使用synchronized块作为替代构造以确保可达性。

暂无
暂无

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

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