亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

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

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

java虛擬機垃圾收集器的由來

發布時間:2020-07-11 11:19:21 來源:億速云 閱讀:155 作者:清晨 欄目:編程語言

這篇文章將為大家詳細講解有關java虛擬機垃圾收集器的由來,小編覺得挺實用的,因此分享給大家做個參考,希望大家閱讀完這篇文章后可以有所收獲。

1.垃圾收集器的由來

為什么要有垃圾收集器啊?不知道有沒有想過這個問題,你說我運行一個程序要什么垃圾收集器啊?

隨意看一下下面兩行代碼:

User user = new User("root","123456")
user = new User("lisi","123123")

簡單畫一下內存圖,可以看到user這個局部變量本來是指向root這個對象,現在改為指向lisi這個對象,那么此時這個root對象沒有人用,假如類似root這樣的對象非常多的話,那么jvm性能就會越來越低,直至最后創建個對象可能都要十幾秒,而且堆內存總有一天會裝滿就會報內存溢出異常;

所以我們就要想辦法把類似root這種對象給清理掉,這樣才能保證jvm高效的運行;

java虛擬機垃圾收集器的由來

假如虛擬機沒有提供gc你覺得會怎么樣?其實也行,只不過你每次需要你用代碼手動釋放不需要的對象,關于這點有好處有壞處,好處就是有利于我們對堆內存的控制,壞處就是我們在一些比較復雜的程序之中由于手動釋放內存難免會出錯,但是這中錯誤還不怎么明顯,可能要你去慢慢調試好久才能看到!

所以java就把這種工作自己處理了,讓一個gc線程一直在后臺運行,隨時準備清理不需要用的對象,雖然相當程度上會對jvm性能造成一些影響,但是由于gc太好用了,我們不用再人為的去關心垃圾對象的釋放,簡化了我們編寫程序的難度,所以這種影響程度完全可以接受!

這里順便一提兩個基本概念,內存泄漏和內存溢出:

內存溢出(Memory Overflow)比較好理解,就是我們保存對象需要的空間太大了,但是申請內存比較小,于是裝不下,于是就會報內存溢出異常,比如說你申請了一個integer,但給它存了long才能存下的數,那就是內存溢出;專業點的說法就是:你要求分配的內存超出了系統能給你的,系統不能滿足需求,于是產生溢出。

內存泄漏(Memory Leak)指的就是我們new出來的對象保存在堆中但是沒有釋放,于是堆中內存會越來越少,會導致系統運行速度減慢,嚴重情況會使程序卡死;專業點的說法就是:你用malloc或new申請了一塊內存,但是沒有通過free或delete將內存釋放,導致這塊內存一直處于占用狀態。

對于我們jvm來說,通常情況下我們不用擔心內存泄漏,因為有一個強大的gc在我們程序的背后默默地為我們清理,但是也會有特殊情況,比如當被分配的對象可達但已無用(未對作廢數據內存單元的賦值null)即會引起,至于這個可達是什么意思,后面會慢慢說到;

相對而言內存溢出我們比較常見,還有gc只會對堆內存進行回收,所以靜態變量是不會回收的;

再順便提一下另外兩個小概念,非守護線程(也叫用戶線程)和守護線程,看下面這個丑陋的程序運行會有幾個線程啊?

public class User{
 public static void main(String[] args){
  System.out.println("我是java小新人");
 }
}

兩個線程,一個是執行main方法的線程,后臺還有gc執行gc的線程,在這里,用戶線程就是執行main方法的那個線程,執行gc的線程就是守護線程,默默地守護者jvm,假如jvm是雅典娜,那么守護線程就是黃金圣斗士;

當用戶線程停止之后整個程序直接停止,守護線程也會終止;但是黃金圣斗士掛了雅典娜還是可以好好活著的繼續愉快的玩耍的;

2.堆內存結構

哎,內存中的結構如果真的要通過源代碼去看,簡直讓人崩潰,除了專業搞這方面的不然真的很難懂,本來我想自己畫一下草圖了,發現太丑陋了,于是去順手借了一張圖:

java虛擬機垃圾收集器的由來

途中可以很清楚的看到,整塊堆內存分為年輕人聚集的地方和老年人聚集的地方,年輕人比較少趨勢占用1/3空間(新生代),老年人比較多就占用2/3的空間(老年代),然而啊,年輕人又要分分類,分別是Eden區占新生代8/10,From Survivor區占新生代1/10,To Survivor區占新生代1/10,emmm。。。我特意查了一下百度翻譯,Eden---->樂園,Survivor----->幸存者;哦~~~我感覺我仿佛明白了命名人的意圖!

那么新生代和老年代到底是干什么的呢?我們創建的對象是放在哪里啊?

新生代:java對象申請內存和存放對象的地方,而且存放的對象都是那種死的比較快的對象,很多時候創建沒多久就清理掉了,那些活的時間比較長的對象都被移動到了老年代。

老年代:存大對象比如長字符串、數組由于需要大量連續的內存空間,可以直接進入老年代;還有長期存活的對象也會進入老年代,具體是多長時間呢,其實默認就是經過15 對新生代的清理(Minor Gc)還能活著的對象。

而垃圾收集器對這兩塊內存有兩種行為,一種是對新生代的清理,叫做Minor Gc,另外一種是對老年代的清理被叫做Major Gc。

順便提一點:很多博客中都把Major GC和Full GC說成是一種,其實還是有區別的,因為很多java虛擬機的實現不一樣,所以就有各種各樣的名稱,比如Minor Gc又叫做Young GC,Major GC也可以叫做Old GC,但是Full GC卻有點不同,Full GC 是清理整個堆空間 —— 包括年輕代、老年代和永久代(也叫做方法區)。因此 Full GC 可以說是 Minor GC 和 Major GC 的結合。當然在我們這里,為了好理解我們也就把Full GC當作Major GC就可以了。 

3.篩選清理對象

GC要工作的話,必須首先知道哪些對象要被清理,你想一下,在新生代和老年代有這么多對象,怎么篩選會又快又省事呢?可以有以下兩種方法

1.引用計數算法,相當于給你創建的對象偷偷的添加一個計數器,每引用一次這個對象,計數器就加一,引用失效就減一,當這個計數器為0的時候,說明這個對象沒有變量引用了,于是我們就可以說這個對象可以被清理了

2.根搜索算法(jvm用的就是這個),這個怎么理解呢?你可以想象現在有一個數組,這個數組里面包含了一些東西的引用,我們將這個數組叫做”GC Root“,然后我們根據這個數組中的引用去找到對應的對象,看看這個對象中又引用了哪些對象,一直往下找,這樣就形成了很多線路,在這個線路上的對象就叫做”可達對象“,不在這個線路上的對象就是不可達對象,而不可達對象也就是我們要清理的對象;

其中可以作為GC Root的對象:

(1).類中的靜態變量,當它持有一個指向一個對象的引用時,它就作為root

(2).活動著的線程,可以作為root

(3).一個Java方法的參數或者該方法中的局部變量,這兩種對象可以作為root

(4).JNI方法中的局部變量或者參數,這兩種對象可以作為root

(5).其它。

關于這個根搜索算法專業一點的說法就是:通過一系列的名為“GC Root”的對象作為起始點,從這些節點開始向下搜索,搜索所有走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Root沒有任何引用鏈相連時(用圖論來說就是GC Root到這個對象不可達時),證明該對象是可以被回收的。

4.進行垃圾回收

前面已經篩選出了我們要清理的對象,但是怎么清理比較快呢?難道要一個一個對象慢慢刪除嘛?就好像你要清理手機中的垃圾,你會一個應用一個應用去慢慢清理數據嗎?當然不可能,這也太浪費時間了!我們當然是用手機管家或者360管家先把要清理的東西給收集起來放在一起,然后我們一清理就是全部,一個字,爽!

ok,在這里也一樣,我們要想辦法把所有的要清理的對象給放在一起清理,有什么辦法呢?

1.標記-----清除算法:這種方法分為兩步,先標記然后清除,其實就是需要回收的對象標記一下,然后就是把有標記的對象全部清理即可;這種方式比較適合對象比較少的內存,假如對象太多標記都要好半天,更別說清除了,而且用這種方法清除的內存空間會東一塊西一塊,下次再創建一個大的對象可能會出問題1

2.復制算法:按內存容量將內存劃分為等大小的兩塊。每次只使用其中一塊,當這一塊內存滿后將尚存活的對象復制到另一塊上去,把已經使用的那塊內存直接全部清理掉;這種方法最大的缺陷就是耗內存啊,只能用總內存的一半,而且如果對象很多復制都要花很多時間。

3.標記----整理算法:結合以上兩種方法優缺點進行改良的一種方法,標記和第一種方法一樣把要清理的對象做好標記,然后把所有標記的對象移動到本內存的一個小角落,最后集中力量對那個小角落進行消滅

4.分代收集算法:這是集中了上面三種方法的優點所實現的一種最好的方法,是目前大部分JVM所采用的方法,這種算法的核心思想是根據對象存活的時間不同將內存劃分為不同的域,一般情況下將GC堆劃分為新生代和老年代;新生代的特點是每次垃圾回收時都有大量垃圾需要被回收,少數對象存活,因此可以使用復制算法;老年代的特點是每次垃圾回收時只有少量對象需要被回收,可以選用”標記--清除方法“”或者標記--整理算法“

所以目前大部分JVM的GC都是使用分代收集算法。

5.執行GC的步驟

前面說了這么多無非是介紹堆的內部結構,然后怎么找到要被清理的對象,然后為了提高效率怎么清理最快!

現在我們就大概說說GC的清理步驟(詳細版):

1.我們創建對象的時候會進行一個判斷,極少數很大的對象直接放進老年代中,除此之外所有新創建的對象都放進新生代的Eden區中;

2.此時新生代中只有Eden區中有對象,兩個Survivor區中是空的;當我們創建了很多對象,使得Eden區快滿的時候第一次GC發生(就是執行了一次Minior GC),Eden區和”From“區(此時“From”區是空的)存活的對象將會被移動到Surviver區的“To”區,并且為每個對象設置一個計數器記錄年齡,初始值為1;每進行一次GC,會給那些存活的對象設置一個年齡+1 的操作,默認是當年齡達到15歲,下次GC就會直接把這種”老油條“丟到老年代中。

3.Minior GC之后,會進行一個比較厲害的操作,就是將”To“區和”From“換個名字,沒錯,就是換個名字,然后進行下一次Minior GC。

4.由于又創建了很多對象使得Eden區要滿了,于是又一次Minior GC,Eden區還存活的對象會直接移動到Surviver區的“To”區,此時”From“區(這里就是交換名字之前的”To“區)中的對象有兩個地方可以去,要么年齡滿15歲了去老年代,要么就移動到”To“區

5.此時我們看一下,只有”To“區的對象是活著的,Eden區都是垃圾對象可以直接全部清理,”From“區是空的;不管怎樣,在進行下一次Minior GC之前保證名為”To“的Survivor區域是空的就ok了

6.當老年代中快要裝滿之后,就會進行一次Major GC,這個清理事件很慢,至少比Minior GC慢十幾倍,甚至更多,所以我們盡量要少執行Major GC

注意:如果在移動過程中”To“ 區被填滿了,剩余的對象會被直接移動到老年代中。還有在每次Minior GC之前會先進性判斷,只要老年代里面的連續空間大于新生代對象總大小或者歷次晉升的平均大小進行Minor GC,否則進行Major GC。

簡化版:

(1)Eden 區活著的對象 + From Survivor 存儲的對象被復制到 To Survivor ;

(2)清空 Eden 和 From Survivor ;

(3)顛倒 From Survivor 和 To Survivor 的邏輯關系: From 變 To , To 變 From 。

(4)老年代的Major GC執行時間很長,盡量少執行

只有在Eden空間快滿的時候才會觸發 Minor GC 。而 Eden 空間占新生代的絕大部分,所以 Minor GC 的頻率得以降低。當然,使用兩個 Survivor 這種方式我們也付出了一定的代價,如 10% 的空間浪費、復制對象的開銷等。

6.知識點補充

通過查看了很多大佬的博客看到的很多有關的東西還是挺有趣的,于是簡單做個小筆記:

6.1.新創建的對象是在堆中的新生代的Eden區,由于堆中內存是所有線程共享,所以在堆中分配內存需要加鎖。而Sun JDK為提升效率,會為每個新建的線程在Eden上分配一塊獨立的空間由該線程獨享,這塊空間稱為TLAB(Thread Local Allocation Buffer)。在TLAB上分配內存不需要加鎖,因此JVM在給線程中的對象分配內存時會盡量在TLAB上分配。如果對象過大或TLAB用完,則仍然在堆上Eden區或者老年代進行分配。如果Eden區內存也用完了,則會進行一次Minor GC(young GC)。

6.2.很多人認為方法區(或者HotSpot虛擬機中的永久代)是沒有垃圾收集的,Java虛擬機規范中確實說過可以不要求虛擬機在方法區實現垃圾收集,而且在方法區進行垃圾收集的“性價比”一般比較低:在堆中,尤其是在新生代中,常規應用進行一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠低于此。

6.3對象調用.finalize方法被調用后,對象一定會被回收嗎?

在經過可達性分析后,到GC Roots不可達的對象可以被回收(但并不是一定會被回收,至少要經過兩次標記),此時對象被第一次標記,并進行一次判斷,如果該對象沒有調用過或者沒有重寫finalize()方法,那么在第二次標記后可以被回收了;否則,該對象會進入一個FQueue中,稍后由JVM建立的一個Finalizer線程中去執行回收,此時若對象中finalize中“自救”,即和引用鏈上的任意一個對象建立引用關系,到GC Roots又可達了,在第二次標記時它會被移除“即將回收”的集合;如果finalize中沒有逃脫,那就面臨被回收。因此finalize方法被調用后,對象不一定會被回收。

6.4.如果在Survivor空間中相同年齡所有對象大小總和大于Survivor空間的一半,年齡大于或者等于該年齡的對象直接進入老年代。不需要等到15歲。

關于java虛擬機垃圾收集器的由來就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

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

AI

马鞍山市| 布拖县| 平乐县| 凤翔县| 汾西县| 黎平县| 驻马店市| 莎车县| 康定县| 丽水市| 法库县| 阳城县| 麻城市| 甘孜县| 泰来县| 彝良县| 元江| 涟源市| 紫阳县| 涞源县| 永川市| 宜宾市| 孙吴县| 丹江口市| 台北县| 锦州市| 河曲县| 仁布县| 美姑县| 全南县| 武夷山市| 上饶县| 交口县| 米脂县| 桑植县| 修武县| 金华市| 南郑县| 绍兴市| 宁明县| 屯留县|