日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

调试以了解终结器

發布時間:2023/12/3 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 调试以了解终结器 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

這篇文章涵蓋了Java內置概念之一,稱為Finalizer 。 這個概念實際上是眾所周知的,也是眾所周知的,這取決于您是否有足夠的時間來仔細研究一下java.lang.Object類。 就在java.lang.Object本身中,有一個名為finalize()的方法。 該方法的實現是空的,但是基于這種方法的存在,力量和危險都取決于JVM內部行為。

當JVM檢測到該類具有finalize()方法時,魔術就開始發生。 因此,讓我們繼續使用非平凡的finalize()方法創建一個類,這樣我們就可以了解JVM在這種情況下處理對象的方式。 為此,讓我們開始構建一個示例程序:

終結類的示例

import java.util.concurrent.atomic.AtomicInteger;class Finalizable {static AtomicInteger aliveCount = new AtomicInteger(0);Finalizable() {aliveCount.incrementAndGet();}@Overrideprotected void finalize() throws Throwable {Finalizable.aliveCount.decrementAndGet();}public static void main(String args[]) {for (int i = 0;; i++) {Finalizable f = new Finalizable();if ((i % 100_000) == 0) {System.out.format("After creating %d objects, %d are still alive.%n", new Object[] {i, Finalizable.aliveCount.get() });}}} }

該示例在一個無終止循環中創建新對象。 這些對象使用靜態aliveCount變量來跟蹤已創建的實例數。 每當創建新實例時,計數器都會遞增,并且在GC之后每次調用finalize()時 ,計數器值都會減少。

那么,從這樣一個簡單的代碼片段中您會得到什么呢? 由于沒有從任何地方引用新創建的對象,因此它們應立即可以用于GC。 因此,您可能希望代碼與程序的輸出一起永久運行,類似于以下內容:

After creating 345,000,000 objects, 0 are still alive. After creating 345,100,000 objects, 0 are still alive. After creating 345,200,000 objects, 0 are still alive. After creating 345,300,000 objects, 0 are still alive.

顯然并非如此。 現實是完全不同的,例如,在我的Mac OS X上的JDK 1.7.0_51上,我看到程序因java.lang.OutOfMemoryError而失敗:創建約120萬個對象后, GC開銷限制已超出 :

After creating 900,000 objects, 791,361 are still alive. After creating 1,000,000 objects, 875,624 are still alive. After creating 1,100,000 objects, 959,024 are still alive. After creating 1,200,000 objects, 1,040,909 are still alive. Exception in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceededat java.lang.ref.Finalizer.register(Finalizer.java:90)at java.lang.Object.(Object.java:37)at eu.plumbr.demo.Finalizable.(Finalizable.java:8)at eu.plumbr.demo.Finalizable.main(Finalizable.java:19)

垃圾收集行為

要了解發生了什么,我們需要在運行時查看示例代碼。 為此,讓我們在打開-XX:+ PrintGCDetails標志的情況下運行示例:

[GC [PSYoungGen: 16896K->2544K(19456K)] 16896K->16832K(62976K), 0.0857640 secs] [Times: user=0.22 sys=0.02, real=0.09 secs] [GC [PSYoungGen: 19440K->2560K(19456K)] 33728K->31392K(62976K), 0.0489700 secs] [Times: user=0.14 sys=0.01, real=0.05 secs] [GC-- [PSYoungGen: 19456K->19456K(19456K)] 48288K->62976K(62976K), 0.0601190 secs] [Times: user=0.16 sys=0.01, real=0.06 secs] [Full GC [PSYoungGen: 16896K->14845K(19456K)] [ParOldGen: 43182K->43363K(43520K)] 60078K->58209K(62976K) [PSPermGen: 2567K->2567K(21504K)], 0.4954480 secs] [Times: user=1.76 sys=0.01, real=0.50 secs] [Full GC [PSYoungGen: 16896K->16820K(19456K)] [ParOldGen: 43361K->43361K(43520K)] 60257K->60181K(62976K) [PSPermGen: 2567K->2567K(21504K)], 0.1379550 secs] [Times: user=0.47 sys=0.01, real=0.14 secs] --- cut for brevity--- [Full GC [PSYoungGen: 16896K->16893K(19456K)] [ParOldGen: 43351K->43351K(43520K)] 60247K->60244K(62976K) [PSPermGen: 2567K->2567K(21504K)], 0.1231240 secs] [Times: user=0.45 sys=0.00, real=0.13 secs] [Full GCException in thread "main" java.lang.OutOfMemoryError: GC overhead limit exceeded[PSYoungGen: 16896K->16866K(19456K)] [ParOldGen: 43351K->43351K(43520K)] 60247K->60218K(62976K) [PSPermGen: 2591K->2591K(21504K)], 0.1301790 secs] [Times: user=0.44 sys=0.00, real=0.13 secs] at eu.plumbr.demo.Finalizable.main(Finalizable.java:19)

從日志中我們可以看到,僅用幾個次要GC清理了Eden之后,JVM便轉而使用了昂貴得多的Full GC周期來清理使用期限和舊空間。 為什么這樣? 由于沒有東西引用我們的對象,難道所有實例都不應該在伊甸園中早逝嗎? 我們的代碼有什么問題?

要理解GC行為的原因,讓我們對代碼做些微改動,并刪除finalize()方法的主體。 現在,JVM檢測到不需要終結我們的類,并將行為更改回“正常”狀態。 查看GC日志,我們只會看到廉價的次要GC永遠運行。


就像在此修改示例中一樣,沒有任何東西確實指向Eden中的對象(所有對象都在其中誕生),GC可以做非常有效的工作并立即丟棄整個Eden。 因此,我們立即清洗了整個伊甸園,而永無止境的循環將永遠持續下去。

另一方面,在我們原始的示例中,情況有所不同。 JVM會為每個Finalized實例創建一個個人看門狗,而不是沒有任何引用的對象。 這個看門狗是Finalizer的一個實例 。 而所有這些實例又被Finalizer類引用。 因此,由于有了這個參考鏈,整個團伙仍然活著。

現在,伊甸園已滿,所有對象都已被引用,GC除了將所有內容復制到Survivor空間之外,別無選擇。 或更糟糕的是,如果“幸存者”中的自由空間也受到限制,則擴展到“終身制”空間。 您可能還記得,終身制空間中的GC是完全不同的野獸,并且比用于清理伊甸園的“讓一切都扔掉”方法昂貴得多。

終結器隊列

只有在GC完成之后,JVM才知道除終結器之外,沒有任何東西可以引用我們的實例,因此它可以標記所有指向這些實例的終結器以供處理。 因此,GC內部將所有Finalizer對象添加到位于java.lang.ref.Finalizer.ReferenceQueue的特殊隊列中。

只有完成所有這些麻煩之后,我們的應用程序線程才能繼續進行實際工作。 這些線程之一現在對我們特別有趣- “ Finalizer”守護程序線程。 您可以通過jstack進行線程轉儲來查看該線程的運行情況:

My Precious:~ demo$ jps 1703 Jps 1702 Finalizable My Precious:~ demo$ jstack 1702--- cut for brevity --- "Finalizer" daemon prio=5 tid=0x00007fe33b029000 nid=0x3103 runnable [0x0000000111fd4000]java.lang.Thread.State: RUNNABLEat java.lang.ref.Finalizer.invokeFinalizeMethod(Native Method)at java.lang.ref.Finalizer.runFinalizer(Finalizer.java:101)at java.lang.ref.Finalizer.access$100(Finalizer.java:32)at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:190) --- cut for brevity ---

從上面我們可以看到“ Finalizer”守護程序線程正在運行。 “最終確定器”線程是僅具有單一責任的線程。 線程運行一個未終止的循環,該循環被阻塞,等待新實例出現在java.lang.ref.Finalizer.ReferenceQueue隊列中。 每當“ Finalizer”線程在隊列中檢測到新對象時,它將彈出對象,調用finalize()方法并從Finalizer類中刪除引用,因此,下次GC運行Finalizer時 ,現在可以將引用的對象設為GCd。

因此,我們現在有兩個未終止的循環在兩個不同的線程中運行。 我們的主線程正在忙于創建新對象。 這些對象都有自己的個人看門狗,稱為終結器 ,它們已由GC添加到java.lang.ref.Finalizer.ReferenceQueue中 。 然后,“ Finalizer ”線程正在處理該隊列,從該隊列中彈出所有實例,并在這些實例上調用finalize()方法。

在大多數情況下,您會避免這樣做。 調用finalize()方法應該比我們實際創建新實例更快。 因此,在許多情況下, “ Finalizer”線程將能夠趕上并清空下一個隊列,然后再將下一個GC注入更多Finalizer 。 就我們而言,這顯然沒有發生。

為什么這樣? “終結器”線程的運行優先級低于主線程。 這意味著它將減少CPU時間,因此無法跟上正在創建的對象的步伐。 到這里,我們有了對象-創建對象的速度比“ Finalizer”線程能夠完成這些對象的finalize()更快,從而消耗了所有可用堆。 結果–我們親愛的朋友java.lang.OutOfMemoryError的口味不同。

如果您仍然不相信我,請進行堆轉儲并查看內部。 例如,當使用-XX:+ HeapDumpOnOutOfMemoryError參數啟動我們的代碼片段時,我在Eclipse MAT Dominator Tree中看到以下圖片:
從屏幕快照中可以看到,我的64m堆完全充滿了Finalizers 。

結論

綜上所述, Finalizable對象的生命周期與標準行為完全不同,即:

  • JVM將創建Finalizable對象的實例
  • JVM將創建java.lang.ref.Finalizer的實例,指向我們新創建的對象實例。
  • java.lang.ref.Finalizer類保留剛剛創建的java.lang.ref.Finalizer實例。 這會阻止下一個較小的GC收集我們的對象并使它們保持活動狀態。
  • 次要GC無法清潔伊甸園,并擴展到幸存者和/或保有權空間。
  • GC檢測到這些對象有資格完成,并將這些對象添加到java.lang.ref.Finalizer.ReferenceQueue
  • 該隊列將由“ Finalizer ”線程處理,一個接一個地彈出對象并調用其finalize()方法。
  • 在調用finalize()之后,“ Finalizer ”線程從Finalizer類中刪除該引用,因此在下一個GC中可以將對象限定為GCd。
  • “ Finalizer ”線程與我們的“ main ”線程競爭,但是由于優先級較低,CPU時間減少,因此永遠無法跟上。
  • 該程序將耗盡所有可用資源,并拋出OutOfMemoryError 。

故事的道德啟示? 下次,當您認為finalize()優于通常的清理,拆除或finally塊時,請再考慮一下。 您可能對所生成的干凈代碼感到滿意,但是,不斷增長的Finalizable對象隊列使您的老一輩人受挫,這可能表明需要重新考慮。

翻譯自: https://www.javacodegeeks.com/2014/05/debugging-to-understand-finalizers.html

總結

以上是生活随笔為你收集整理的调试以了解终结器的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。