中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

JVM中怎么判斷對象是否已死

發布時間:2021-08-02 13:43:34 來源:億速云 閱讀:160 作者:Leah 欄目:編程語言

這篇文章給大家介紹JVM中怎么判斷對象是否已死,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。


public class ReferenceCountingGC {

    public Object instance = null;
    private static final int _1MB = 1024 * 1024;
    /**
     * 這個成員屬性的唯一意義就是占點內存, 以便能在GC日志中看清楚是否有回收過
     */
    private byte[] bigSize = new byte[2 * _1MB];

    public static void main(String[] args) {
        testGC();
    }

    public static void testGC() {
        ReferenceCountingGC objA = new ReferenceCountingGC();
        ReferenceCountingGC objB = new ReferenceCountingGC();
        objA.instance = objB;
        objB.instance = objA;
        objA = null;
        objB = null;
        // 假設在這行發生GC, objA和objB是否能被回收?
        System.gc();
    }

}

例子要說明的結果是,相互引用下卻已經置為null的兩個對象,是否會被GC回收。如果只是按照引用計數器算法來看,那么這兩個對象的計數標識不會為0,也就不能被回收。但到底有沒有被回收呢?

這里我們先采用 jvm 工具指令,jstat來監控。因為監控的過程需要我手敲代碼,比較耗時,所以我們在調用testGC()前,睡眠會 Thread.sleep(55000);。啟動代碼后執行如下指令。

E:\itstack\git\github.com\interview>jps -l
10656
88464
38372 org.itstack.interview.ReferenceCountingGC
26552 sun.tools.jps.Jps
110056 org.jetbrains.jps.cmdline.Launcher

E:\itstack\git\github.com\interview>jstat -gc 38372 2000
 S0C    S1C    S0U    S1U      EC       EU        OC         OU       MC     MU    CCSC   CCSU   YGC     YGCT    FGC    FGCT     GCT
10752.0 10752.0  0.0    0.0   65536.0   6561.4   175104.0     0.0     4480.0 770.9  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0    0.0   65536.0   6561.4   175104.0     0.0     4480.0 770.9  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0    0.0   65536.0   6561.4   175104.0     0.0     4480.0 770.9  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0    0.0   65536.0   6561.4   175104.0     0.0     4480.0 770.9  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0    0.0   65536.0   6561.4   175104.0     0.0     4480.0 770.9  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0    0.0   65536.0   6561.4   175104.0     0.0     4480.0 770.9  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0    0.0   65536.0   6561.4   175104.0     0.0     4480.0 770.9  384.0   75.9       0    0.000   0      0.000    0.000
10752.0 10752.0  0.0   1288.0 65536.0    0.0     175104.0     8.0     4864.0 3982.6 512.0  440.5       1    0.003   1      0.000    0.003
10752.0 10752.0  0.0    0.0   65536.0   437.3    175104.0    1125.5   4864.0 3982.6 512.0  440.5       1    0.003   1      0.012    0.015
10752.0 10752.0  0.0    0.0   65536.0   437.3    175104.0    1125.5   4864.0 3982.6 512.0  440.5       1    0.003   1      0.012    0.015
 
  • S0C、S1C,第一個和第二個幸存區大小
  • S0U、S1U,第一個和第二個幸存區使用大小
  • EC、EU,伊甸園的大小和使用
  • OC、OU,老年代的大小和使用
  • MC、MU,方法區的大小和使用
  • CCSC、CCSU,壓縮類空間大小和使用
  • YGC、YGCT,年輕代垃圾回收次數和耗時
  • FGC、FGCT,老年代垃圾回收次數和耗時
  • GCT,垃圾回收總耗時

「注意」:觀察后面三行,S1U = 1288.0GCT = 0.003,說明已經在執行垃圾回收。

接下來,我們再換種方式測試。在啟動的程序中,加入GC打印參數,觀察GC變化結果。

-XX:+PrintGCDetails  打印每次gc的回收情況 程序運行結束后打印堆空間內存信息(包含內存溢出的情況)
-XX:+PrintHeapAtGC  打印每次gc前后的內存情況
-XX:+PrintGCTimeStamps 打印每次gc的間隔的時間戳 full gc為每次對新生代老年代以及整個空間做統一的回收 系統中應該盡量避免
-XX:+TraceClassLoading  打印類加載情況
-XX:+PrintClassHistogram 打印每個類的實例的內存占用情況
-Xloggc:/Users/xiaofuge/Desktop/logs/log.log  配合上面的使用將上面的日志打印到指定文件
-XX:HeapDumpOnOutOfMemoryError 發生內存溢出將堆信息轉存起來 以便分析
 

這回就可以把睡眠去掉了,并添加參數 -XX:+PrintGCDetails,如下:

JVM中怎么判斷對象是否已死  

「測試結果」

[GC (System.gc()) [PSYoungGen: 9346K->936K(76288K)] 9346K->944K(251392K), 0.0008518 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
[Full GC (System.gc()) [PSYoungGen: 936K->0K(76288K)] [ParOldGen: 8K->764K(175104K)] 944K->764K(251392K), [Metaspace: 3405K->3405K(1056768K)], 0.0040034 secs] [Times: user=0.08 sys=0.00, real=0.00 secs] 
Heap
 PSYoungGen      total 76288K, used 1966K [0x000000076b500000, 0x0000000770a00000, 0x00000007c0000000)
  eden space 65536K, 3% used [0x000000076b500000,0x000000076b6eb9e0,0x000000076f500000)
  from space 10752K, 0% used [0x000000076f500000,0x000000076f500000,0x000000076ff80000)
  to   space 10752K, 0% used [0x000000076ff80000,0x000000076ff80000,0x0000000770a00000)
 ParOldGen       total 175104K, used 764K [0x00000006c1e00000, 0x00000006cc900000, 0x000000076b500000)
  object space 175104K, 0% used [0x00000006c1e00000,0x00000006c1ebf100,0x00000006cc900000)
 Metaspace       used 3449K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 376K, capacity 388K, committed 512K, reserved 1048576K
 
  • 從運行結果可以看出內存回收日志,Full GC 進行了回收。
  • 也可以看出JVM并不是依賴引用計數器的方式,判斷對象是否存活。     否則他們就不會被回收啦

「有了這個例子,我們再接著看看JVM垃圾回收的知識框架!」

 

四、JVM 垃圾回收知識框架

垃圾收集(Garbage Collection,簡稱GC),最早于1960年誕生于麻省理工學院的Lisp是第一門開始使用內存動態分配和垃圾收集技術的語言。

垃圾收集器主要做的三件事:哪些內存需要回收什么時候回收、怎么回收。

而從垃圾收集器的誕生到現在有半個世紀的發展,現在的內存動態分配和內存回收技術已經非常成熟,一切看起來都進入了“自動化”。但在某些時候還是需要我們去監測在高并發的場景下,是否有內存溢出、泄漏、GC時間過程等問題。所以在了解和知曉垃圾收集的相關知識對于高級程序員的成長就非常重要。

垃圾收集器的核心知識項主要包括:判斷對象是否存活、垃圾收集算法、各類垃圾收集器以及垃圾回收過程。如下圖;

JVM中怎么判斷對象是否已死  
圖 27-1 垃圾收集器知識框架

原圖下載鏈接:http://book.bugstack.cn/#s/6jJp2icA

 

1. 判斷對象已死

 
1.1 引用計數器
  1. 為每一個對象添加一個引用計數器,統計指向該對象的引用次數。
  2. 當一個對象有相應的引用更新操作時,則對目標對象的引用計數器進行增減。
  3. 一旦當某個對象的引用計數器為0時,則表示此對象已經死亡,可以被垃圾回收。

從實現來看,引用計數器法(Reference Counting)雖然占用了一些額外的內存空間來進行計數,但是它的實現方案簡單,判斷效率高,是一個不錯的算法。

也有一些比較出名的引用案例,比如:微軟COM(Component Object Model) 技術、使用ActionScript 3的FlashPlayer、 Python語言等。

「但是」,在主流的Java虛擬機中并沒有選用引用技術算法來管理內存,主要是因為這個簡單的計數方式在處理一些相互依賴、循環引用等就會非常復雜。可能會存在不再使用但又不能回收的內存,造成內存泄漏

 
1.2 可達性分析法

Java、C#等主流語言的內存管理子系統,都是通過可達性分析(Reachability Analysis)算法來判定對象是否存活的。

它的算法思路是通過定義一系列稱為 GC Roots 根對象作為起始節點集,從這些節點出發,窮舉該集合引用到的全部對象填充到該集合中(live set)。這個過程教過標記,只標記那些存活的對象 好,那么現在未被標記的對象就是可以被回收的對象了。

GC Roots 包括;

  1. 全局性引用,對方法區的靜態對象、常量對象的引用
  2. 執行上下文,對 Java方法棧幀中的局部對象引用、對 JNI handles 對象引用
  3. 已啟動且未停止的 Java 線程

「兩大問題」

  1. 誤報:已死亡對象被標記為存活,垃圾收集不到。多占用一會內存,影響較小。
  2. 漏報:引用的對象(正在使用的)沒有被標記為存活,被垃圾回收了。那么直接導致的就是JVM奔潰。(STW可以確保可達性分析法的準確性,避免漏報)
 

2. 垃圾回收算法

 
2.1 標記-清除算法(mark-sweep)
JVM中怎么判斷對象是否已死  
標記-清除算法(mark-sweep)
  • 標記無引用的死亡對象所占據的空閑內存,并記錄到空閑列表中(free list)。
  • 當需要創建新對象時,內存管理模塊會從 free list 中尋找空閑內存,分配給新建的對象。
  • 這種清理方式其實非常簡單高效,但是也有一個問題內存碎片化太嚴重了。
  • 「Java 虛擬機的堆中對象」,必須是連續分布的,所以極端的情況下可能即使總剩余內存充足,但尋找連續內存分配效率低,或者嚴重到無法分配內存。     重啟湯姆貓!
  • 在CMS中有此類算法的使用,GC暫停時間短,但存在算法缺陷。
 
2.2 標記-復制算法(mark-copy)
JVM中怎么判斷對象是否已死  
標記-復制算法(mark-copy)
  • 從圖上看這回做完垃圾清理后連續的內存空間就大了。
  • 這種方式是把內存區域分成兩份,分別用兩個指針 from 和 to 維護,并且只使用 from 指針指向的內存區域分配內存。
  • 當發生垃圾回收時,則把存活對象復制到 to 指針指向的內存區域,并交換 from 與 to 指針。
  • 它的好處很明顯,就是解決內存碎片化問題。但也帶來了其他問題,堆空間浪費了一半。
 
2.3 標記-壓縮算法(mark-compact)
JVM中怎么判斷對象是否已死  
標記-壓縮算法(mark-compact)
  • 1974年,Edward Lueders 提出了標記-壓縮算法,標記的過程和標記清除算法一樣,但在后續對象清理步驟中,先把存活對象都向內存空間一端移動,然后在清理掉其他內存空間。
  • 這種算法能夠解決內存碎片化問題,但壓縮算法的性能開銷也不小。


關于JVM中怎么判斷對象是否已死就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

jvm
AI

衡水市| 江陵县| 佛学| 石河子市| 邯郸县| 大同县| 运城市| 枞阳县| 蒙城县| 北辰区| 平江县| 无为县| 尉犁县| 沂水县| 抚顺县| 突泉县| 灵川县| 康保县| 楚雄市| 西乌珠穆沁旗| 丹凤县| 磐安县| 海原县| 正蓝旗| 虎林市| 万安县| 平阴县| 罗江县| 丰原市| 晋中市| 明星| 安新县| 仁怀市| 驻马店市| 临泉县| 威宁| 武隆县| 华容县| 建平县| 永登县| 萍乡市|