簡體   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