偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

來說說垃圾回收怎么樣~

云計(jì)算 虛擬化
JVM 的自動(dòng)內(nèi)存管理,讓原本應(yīng)該是開發(fā)人員去做的事情,變成了垃圾回收器來做的事情,既然是別人幫忙做的事情,那么可能就不是自己想要的,所以就需要我們了解一下垃圾回收相關(guān)的內(nèi)容。

[[354376]]

本文轉(zhuǎn)載自微信公眾號(hào)「Java極客技術(shù)」,作者鴨血粉絲 。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號(hào)。  

JVM 的自動(dòng)內(nèi)存管理,讓原本應(yīng)該是開發(fā)人員去做的事情,變成了垃圾回收器來做的事情

既然是別人幫忙做的事情,那么可能就不是自己想要的,所以就需要我們了解一下垃圾回收相關(guān)的內(nèi)容

引用計(jì)數(shù)法與可達(dá)性分析

垃圾回收,垃圾回收,那就是有的內(nèi)存分配給了一些對象,但是這些對象已經(jīng)用完了,那么它所占用的內(nèi)存也就應(yīng)該該釋放掉了,卻還沒有釋放

那么,這里就有個(gè)問題:該如何確定一個(gè)對象用完了呢?

其中一種方法就是引用計(jì)數(shù)法

引用計(jì)數(shù)法就是給每個(gè)對象添加一個(gè)引用計(jì)數(shù)器,來統(tǒng)計(jì)指向該對象的引用個(gè)數(shù)

比如:如果有一個(gè)引用,被賦值為某一個(gè)對象,那么這個(gè)對象的引用計(jì)數(shù)器就 +1 ,如果一個(gè)指向這個(gè)對象的引用,被賦值為了其他的值,那么這個(gè)對象的引用計(jì)數(shù)器就 -1 ,這樣如果這個(gè)對象的引用計(jì)數(shù)器為 0 ,我們就可以認(rèn)為這個(gè)對象已經(jīng)使用完畢,它所占用的內(nèi)存空間可以回收掉了

這種方案聽上去無懈可擊,但是有一個(gè)致命的漏洞,就是沒辦法處理循環(huán)引用的問題

比如說: A 和 B 互相引用,除此之外也沒有其他的引用指向 A 或者 B ,在這種情況下,其實(shí) A 和 B 所占用的內(nèi)存就可以釋放掉了,但是因?yàn)樗鼈兓ハ喽加幸?,所以此時(shí)的引用計(jì)數(shù)器并不為 0 ,在這種情況下,就不能對它們進(jìn)行回收

現(xiàn)在只是兩個(gè)對象,如果再來兩個(gè),再來兩個(gè),這樣循環(huán)引用的對象多了之后,就會(huì)造成內(nèi)存泄露

基于引用計(jì)數(shù)法的弊端,當(dāng)前 JVM 主流的垃圾回收器采取的是可達(dá)性分析算法

這個(gè)算法本質(zhì)就是將一系列的 GC Roots 作為初始的存活對象合集( live set ),然后從這個(gè)合集出發(fā),探索所有能夠被該集合引用到的對象,并把這些對象加入到集合中來,這個(gè)過程就叫做標(biāo)記( mark ),遍歷到最后,沒有被探索到的對象就是可以回收的對象

那么什么是 GC Roots 嘞?一般包括(但不限于)以下幾種:

  • Java 方法棧楨中的局部變量
  • 已加載類的靜態(tài)變量
  • JNI handles
  • 已啟動(dòng)并且沒有停止的 Java 線程

剛才說因?yàn)橐糜?jì)數(shù)法存在循環(huán)引用的問題,所以目前主流垃圾回收器選用的都是可達(dá)性分析法,也就是說,它解決了循環(huán)引用問題,其實(shí)這一點(diǎn)也比較好理解,雖然 A 和 B 相互引用,但是這個(gè)時(shí)候從 GC Roots 開始出發(fā),是沒有辦法到達(dá) A 和 B 的,那么就不會(huì)把它們放到存活對象合集之中,自然也就會(huì)被回收掉

但是在實(shí)際中還是會(huì)有問題的,比如:在多線程環(huán)境下,就會(huì)有其他線程更新已經(jīng)訪問過的對象中的引用,但是是多線程并行的嘛,這個(gè)時(shí)候可達(dá)性分析法已經(jīng)把這個(gè)引用設(shè)置成了 null ,或者這個(gè)對象還在使用,但可達(dá)性分析法把它標(biāo)記為了沒有被訪問過的對象,被回收掉了,這種情況可能直接導(dǎo)致 JVM 崩潰掉

Stop-the-world & safepoint

既然可達(dá)性分析法也有自己的一些缺陷,總得有解決方案吧?比較暴力的一種方法就是 Stop-the-world ,估計(jì)聽名字也能知道,就是讓全世界都停下來,也就是說,在進(jìn)行垃圾回收的時(shí)候,其他所有非垃圾回收線程的工作都需要停下來,先讓垃圾回收器工作完畢再說。這就是所謂的暫停時(shí)間( GC pause )

Stop-the-world 是通過安全點(diǎn)( safepoint )機(jī)制來實(shí)現(xiàn)的。啥意思嘞?咱先想個(gè)場景,現(xiàn)在你敲代碼敲的特別開心,又有思路,狀態(tài)又好,美滋滋的正在工作,突然毫無緣由的就讓你現(xiàn)在不準(zhǔn)敲代碼,你會(huì)不會(huì)不開心?好不容易思路來了對吧,就一點(diǎn)兒理由都不給的就讓我停下,不合理吧?

同樣的場景,一個(gè)線程現(xiàn)在跑的特別 happy ,而且再有一秒鐘就完成了任務(wù),這個(gè)時(shí)候 JVM 收到了 Stop-the-world 請求,二話不說就把所有的線程給停掉,不太好吧?那么這個(gè)時(shí)候安全點(diǎn)( safepoint )機(jī)制就登場了。有了安全點(diǎn)機(jī)制,當(dāng) JVM 收到 Stop-the-world 請求的時(shí)候,它就會(huì)等待所有的線程都達(dá)到安全點(diǎn),才允許請求 Stop-the-world 的線程進(jìn)行獨(dú)占的工作

那么,什么時(shí)候是安全點(diǎn)呢?舉個(gè)例子來說:當(dāng) Java 程序通過 JNI 執(zhí)行本地代碼時(shí),如果這段代碼不訪問 Java 對象,不調(diào)用 Java 方法,不返回到原 Java 方法,那么 Java 虛擬機(jī)的堆棧就不會(huì)發(fā)生改變,那這段本地代碼就可以作為一個(gè)安全點(diǎn)。只要不離開這個(gè)安全點(diǎn), JVM 就可以在垃圾回收的同時(shí),繼續(xù)運(yùn)行這段本地代碼

因?yàn)楸镜卮a需要通過 JNI 的 API 來完成上述三個(gè)操作,因此 JVM 只需要在 API 的入口處進(jìn)行安全點(diǎn)檢測( safepoint poll ),看看有沒有其他線程請求停留在安全點(diǎn)這里,就可以在必要的時(shí)候掛起當(dāng)前線程

垃圾回收的三種方式

當(dāng)標(biāo)記好存活的對象之后,就可以進(jìn)行垃圾回收了

主流的垃圾回收方式,可以分為三種:清除( sweep ),壓縮( compact ),復(fù)制( copy )

清除,就是把死亡對象所占據(jù)的內(nèi)存標(biāo)記成空閑內(nèi)存,并把它記錄在一個(gè)空閑列表( free list )中,當(dāng)需要新建對象的時(shí)候,就直接在空閑列表中尋找空閑內(nèi)存,劃分給新建的對象就完了

但是這里會(huì)產(chǎn)生一個(gè)問題,因?yàn)樗劳龅膶ο笏紦?jù)的內(nèi)存可能是隨機(jī)的,回收完畢之后,內(nèi)存就是碎片化的,如果此時(shí)有對象申請一塊連續(xù)的內(nèi)存空間,盡管碎片化的內(nèi)存空間是夠用的,也沒辦法進(jìn)行分配

壓縮,就是把存活的對象聚集到內(nèi)存區(qū)域的起始位置,這樣就可以留下一段連續(xù)的內(nèi)存空間。這樣去做的話,可以解決內(nèi)存碎片化的問題,代價(jià)就是壓縮算法帶來的性能開銷

復(fù)制,就是把內(nèi)存區(qū)域分成兩等分,分別用兩個(gè)指針 from 和 to 來維護(hù),并且只是用 from 指針指向的內(nèi)存區(qū)域來分配內(nèi)存。當(dāng)進(jìn)行垃圾回收時(shí),就把存活的對象復(fù)制到 to 指針指向的內(nèi)存區(qū)域中,并且交換 from 指針和 to 指針的內(nèi)容。

復(fù)制這種方式也可以解決內(nèi)存碎片化的問題,但是它的缺點(diǎn)也是比較明顯的,因?yàn)榘褍?nèi)存區(qū)域分成了兩等分嘛,那利用率就比較低咯,最高也是 50% 了,不能再高了

垃圾回收在 JVM 中的應(yīng)用

上面說的三種垃圾回收方式是理論上的,那么在 JVM 中是如何應(yīng)用的呢?

這就先要來了解下 JVM 的堆劃分,大概就是這樣子:

JVM 將堆劃分為新生代和老年代,在新生代中又劃分為 Eden 區(qū),還有兩個(gè)大小相同的 Survivor 區(qū)

當(dāng)程序調(diào)用 new 指令時(shí),會(huì)在 Eden 區(qū)中劃出一塊作為存儲(chǔ)對象的內(nèi)存,但是因?yàn)槎芽臻g是線程共享的,所以在這里面劃分空間的話就需要同步,要不然出現(xiàn)了兩個(gè)對象共用一段內(nèi)存,那不就該打架了嘛

JVM 為了避免兩個(gè)對象打架的事情發(fā)生,就讓每個(gè)線程向 JVM 申請一段連續(xù)的內(nèi)存,來作為線程私有的 TLAB ( Thread Local Allocation Buffer ,對應(yīng)虛擬機(jī)參數(shù) -XX:+UseTLAB ,默認(rèn)開啟的)

Eden 區(qū)一直進(jìn)行分配,總有空間分配完畢的時(shí)候,該怎么辦?此時(shí) JVM 就會(huì)觸發(fā)一次 Minor GC ,來收集新生代的垃圾,存活下來的對象就會(huì)被送到 Survivor 區(qū)

在圖中可以看到, Survivor 區(qū)有兩個(gè),一個(gè)是 from ,一個(gè)是 to ,其中 to 指向的 Survivor 區(qū)是空的

當(dāng)發(fā)生 Minor GC 時(shí), Eden 區(qū)和 from 指向的 Survivor 區(qū)中的存活對象會(huì)被復(fù)制到 to 指向的 Survivor 區(qū),然后交換 from 和 to 指針,這樣就保證了下一次 Minor GC 時(shí), to 指向的 Survivor 區(qū)還是空的

同時(shí) JVM 會(huì)記錄 Survivor 區(qū)的對象一共被來回復(fù)制了幾次,如果一個(gè)對象被復(fù)制的次數(shù)為 15 (對應(yīng)虛擬機(jī)參數(shù) -XX:+MaxTenuringThreshold ),這個(gè)對象就會(huì)被晉升( promote )到老年代

那么在發(fā)生 Minor GC 時(shí),采用哪種垃圾回收方式會(huì)比較好一些呢?采用復(fù)制方式,也就是 標(biāo)記-復(fù)制 算法會(huì)好一些。為什么呢?因?yàn)樵谛律?,大部分? Java 對象只存活一小段時(shí)間,那么我們就可以采用耗時(shí)比較短的垃圾回收算法,讓大部分的垃圾都能在新生代被回收掉。使用 標(biāo)記-復(fù)制 算法的話,理想情況下就是 Eden 區(qū)中的對象基本都死亡了,那么需要復(fù)制的數(shù)據(jù)非常少,此時(shí)這種算法的優(yōu)勢就被極大的體現(xiàn)了出來

 

責(zé)任編輯:武曉燕 來源: Java極客技術(shù)
相關(guān)推薦

2021-01-09 14:03:37

Vrrp協(xié)議網(wǎng)關(guān)

2021-01-04 10:08:07

垃圾回收Java虛擬機(jī)

2017-08-04 10:53:30

回收算法JVM垃圾回收器

2022-01-20 10:34:49

JVM垃圾回收算法

2022-03-21 11:33:11

JVM垃圾回收器垃圾回收算法

2014-02-18 11:24:07

云計(jì)算PaaS

2020-12-14 11:35:22

SPI Java機(jī)制

2021-11-05 15:23:20

JVM回收算法

2018-04-24 14:34:54

機(jī)器學(xué)習(xí)機(jī)器人互聯(lián)網(wǎng)

2020-07-09 08:26:42

Kubernetes容器開發(fā)

2009-06-25 17:48:24

Java垃圾回收

2021-03-03 08:13:57

模式垃圾回收

2023-12-19 21:52:51

Go垃圾回收開發(fā)

2010-12-13 11:14:04

Java垃圾回收算法

2023-08-08 10:29:55

JVM優(yōu)化垃圾回收

2019-07-19 15:42:57

Hadoop大數(shù)據(jù)YuniKorn

2023-06-30 08:23:36

Spring!SolonJavalin

2015-07-23 11:49:31

程序猿

2024-08-20 16:27:54

2022-06-22 09:54:45

JVM垃圾回收Java
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)