[英]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代碼保存指針,就可以保存對直接緩沖區對象的引用,例如使用NewGlobalRef
和DeleteGlobalRef
。
關於您的具體問題,這可以直接在JLS§12.6.1中解決。 實施定稿 :
可以設計優化程序的轉換,以減少可達到的對象的數量,使其少於可以被認為可達的對象的數量。 ...
如果對象字段中的值存儲在寄存器中,則會出現另一個示例。 ...請注意,只有在引用位於堆棧上且未存儲在堆中時,才允許進行此類優化。
(最后一句很重要)
在該章中通過一個與你的例子沒有太大不同的例子來說明。 簡而言之, Runnable
實例中的localObject
引用將使引用對象的生命周期至少與Runnable
實例的生命周期一樣長。
也就是說,這里的關鍵點是Runnable
實例的實際生命周期。 由於上面指定的規則,如果它也被一個不受優化影響的對象引用,它將被認為是絕對活着的,即不受優化影響,但即使Executor
也不一定是全局可見對象。
也就是說,方法內聯是最簡單的優化之一,之后JVM會檢測到ThreadPoolExecutor
的afterExecute
是無操作。 順便說一句,在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.