国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Java GC 的那些事(1)

2019-11-11 07:03:42
字體:
來源:轉載
供稿:網友

學習java的同學注意了!!! 學習過程中遇到什么問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:183993990  我們一起學Java!

前言

與C語言不同,Java內存(堆內存)的分配與回收由JVM垃圾收集器自動完成,這個特性深受大家歡迎,能夠幫助程序員更好的編寫代碼,本文以HotSpot虛擬機為例,說一說Java GC的那些事。

Java堆內存

在 JVM內存的那些事 一文中,我們已經知道Java堆是被所有線程共享的一塊內存區域,所有對象實例和數組都在堆上進行內存分配。為了進行高效的垃圾回收,虛擬機把堆內存劃分成新生代(Young Generation)、老年代(Old Generation)和永久代(Permanent Generation)3個區域。

新生代

新生代由 Eden 與 Survivor Space(S0,S1)構成,大小通過-Xmn參數指定,Eden 與 Survivor Space 的內存大小比例默認為8:1,可以通過-XX:SurvivorRatio 參數指定,比如新生代為10M 時,Eden分配8M,S0和S1各分配1M。

Eden:希臘語,意思為伊甸園,在圣經中,伊甸園含有樂園的意思,根據《舊約·創世紀》記載,上帝耶和華照自己的形像造了第一個男人亞當,再用亞當的一個肋骨創造了一個女人夏娃,并安置他們住在了伊甸園。

大多數情況下,對象在Eden中分配,當Eden沒有足夠空間時,會觸發一次Minor GC,虛擬機提供了-XX:+PRintGCDetails參數,告訴虛擬機在發生垃圾回收時打印內存回收日志。

Survivor:意思為幸存者,是新生代和老年代的緩沖區域。當新生代發生GC(Minor GC)時,會將存活的對象移動到S0內存區域,并清空Eden區域,當再次發生Minor GC時,將Eden和S0中存活的對象移動到S1內存區域。

存活對象會反復在S0和S1之間移動,當對象從Eden移動到Survivor或者在Survivor之間移動時,對象的GC年齡自動累加,當GC年齡超過默認閾值15時,會將該對象移動到老年代,可以通過參數-XX:MaxTenuringThreshold 對GC年齡的閾值進行設置。

老年代

老年代的空間大小即-Xmx 與-Xmn 兩個參數之差,用于存放經過幾次Minor GC之后依舊存活的對象。當老年代的空間不足時,會觸發Major GC/Full GC,速度一般比Minor GC慢10倍以上。

永久代

在JDK8之前的HotSpot實現中,類的元數據如方法數據、方法信息(字節碼,棧和變量大小)、運行時常量池、已確定的符號引用和虛方法表等被保存在永久代中,32位默認永久代的大小為64M,64位默認為85M,可以通過參數-XX:MaxPermSize進行設置,一旦類的元數據超過了永久代大小,就會拋出OOM異常。

虛擬機團隊在JDK8的HotSpot中,把永久代從Java堆中移除了,并把類的元數據直接保存在本地內存區域(堆外內存),稱之為元空間。

這樣做有什么好處?有經驗的同學會發現,對永久代的調優過程非常困難,永久代的大小很難確定,其中涉及到太多因素,如類的總數、常量池大小和方法數量等,而且永久代的數據可能會隨著每一次Full GC而發生移動。

而在JDK8中,類的元數據保存在本地內存中,元空間的最大可分配空間就是系統可用內存空間,可以避免永久代的內存溢出問題,不過需要監控內存的消耗情況,一旦發生內存泄漏,會占用大量的本地內存。

ps:JDK7之前的HotSpot,字符串常量池的字符串被存儲在永久代中,因此可能導致一系列的性能問題和內存溢出錯誤。在JDK8中,字符串常量池中只保存字符串的引用。

如何判斷對象是否存活

GC動作發生之前,需要確定堆內存中哪些對象是存活的,一般有兩種方法:引用計數法和可達性分析法。

1、引用計數法在對象上添加一個引用計數器,每當有一個對象引用它時,計數器加1,當使用完該對象時,計數器減1,計數器值為0的對象表示不可能再被使用。

引用計數法實現簡單,判定高效,但不能解決對象之間相互引用的問題。

public class GCtest { private Object instance = null; private static final int _10M = 10 * 1 << 20; // 一個對象占10M,方便在GC日志中看出是否被回收 private byte[] bigSize = new byte[_10M]; public static void main(String[] args) { GCtest objA = new GCtest(); GCtest objB = new GCtest(); objA.instance = objB; objB.instance = objA; objA = null; objB = null; System.gc(); }}

通過添加-XX:+PrintGC參數,運行結果:

[GC (System.gc()) [PSYoungGen: 26982K->1194K(75776K)] 26982K->1202K(249344K), 0.0010103 secs]

從GC日志中可以看出objA和objB雖然相互引用,但是它們所占的內存還是被垃圾收集器回收了。

2、可達性分析法通過一系列稱為 “GC Roots” 的對象作為起點,從這些節點開始向下搜索,搜索路徑稱為 “引用鏈”,以下對象可作為GC Roots:

本地變量表中引用的對象方法區中靜態變量引用的對象方法區中常量引用的對象Native方法引用的對象

當一個對象到 GC Roots 沒有任何引用鏈時,意味著該對象可以被回收。

在可達性分析法中,判定一個對象objA是否可回收,至少要經歷兩次標記過程:1、如果對象objA到 GC Roots沒有引用鏈,則進行第一次標記。2、如果對象objA重寫了finalize()方法,且還未執行過,那么objA會被插入到F-Queue隊列中,由一個虛擬機自動創建的、低優先級的Finalizer線程觸發其finalize()方法。finalize()方法是對象逃脫死亡的最后機會,GC會對隊列中的對象進行第二次標記,如果objA在finalize()方法中與引用鏈上的任何一個對象建立聯系,那么在第二次標記時,objA會被移出“即將回收”集合。

看看具體實現

public class FinalizerTest { public static FinalizerTest object; public void isAlive() { System.out.println("I'm alive"); } @Override protected void finalize() throws Throwable { super.finalize(); System.out.println("method finalize is running"); object = this; } public static void main(String[] args) throws Exception { object = new FinalizerTest(); // 第一次執行,finalize方法會自救 object = null; System.gc(); Thread.sleep(500); if (object != null) { object.isAlive(); } else { System.out.println("I'm dead"); } // 第二次執行,finalize方法已經執行過 object = null; System.gc(); Thread.sleep(500); if (object != null) { object.isAlive(); } else { System.out.println("I'm dead"); } }}

執行結果:

method finalize is runningI'm aliveI'm dead

從執行結果可以看出:第一次發生GC時,finalize方法的確執行了,并且在被回收之前成功逃脫;第二次發生GC時,由于finalize方法只會被JVM調用一次,object被回收。

當然了,在實際項目中應該盡量避免使用finalize方法。

學習Java的同學注意了!!! 學習過程中遇到什么問題或者想獲取學習資源的話,歡迎加入Java學習交流群,群號碼:183993990  我們一起學Java!


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 巴南区| 会昌县| 奎屯市| 吴川市| 江西省| 景德镇市| 东台市| 台湾省| 阿瓦提县| 深州市| 兰西县| 宽甸| 阳山县| 古丈县| 三门县| 永和县| 丽水市| 石林| 石城县| 黔东| 临夏市| 龙岩市| 黑龙江省| 色达县| 天峻县| 东乌珠穆沁旗| 兰溪市| 杭州市| 夏河县| 夹江县| 钦州市| 扬州市| 牟定县| 湘乡市| 九江市| 泰来县| 原平市| 泰和县| 福州市| 安丘市| 丽水市|