當前位置:首頁 > 芯聞號 > 充電吧
[導讀]Java內(nèi)存運行時區(qū)域的各個部分,其中程序計數(shù)器、虛擬機棧、本地方法棧3個區(qū)域隨線程而生,隨線程而滅;棧中的棧幀隨著方法的進入和退出而有條不紊地執(zhí)行著出棧和入棧操作。每一個棧幀中分配多少內(nèi)存基本上是在

Java內(nèi)存運行時區(qū)域的各個部分,其中程序計數(shù)器、虛擬機棧、本地方法棧3個區(qū)域隨線程而生,隨線程而滅;棧中的棧幀隨著方法的進入和退出而有條不紊地執(zhí)行著出棧和入棧操作。每一個棧幀中分配多少內(nèi)存基本上是在類結(jié)構(gòu)確定下來時就已知的(盡管在運行期會由JIT編譯器進行一些優(yōu)化),因此這幾個區(qū)域的內(nèi)存分配和回收都具備確定性,在這幾個區(qū)域內(nèi)就不需要過多考慮回收的問題,因為方法結(jié)束或者線程結(jié)束時,內(nèi)存自然就跟隨著回收了。


而Java堆和方法區(qū)則不一樣,一個接口中的多個實現(xiàn)類需要的內(nèi)存可能不一樣,一個方法中的多個分支需要的內(nèi)存也可能不一樣,我們只有在程序處于運行期間時才能知道會創(chuàng)建哪些對象,這部分內(nèi)存的分配和回收都是動態(tài)的,垃圾收集器所關注的是這部分內(nèi)存。


對象已死嗎?

在堆里面存放著Java世界中幾乎所有的對象實例,垃圾收集器在對堆進行回收前,第一件事情就是要確定這些對象之中哪些還“存活”著,哪些已經(jīng)“死去”(即不可能再被任何途徑使用的對象)。


引用計數(shù)算法

給對象中添加一個引用計數(shù)器,每當有一個地方引用它時,計數(shù)器值就加1;當引用失效時,計數(shù)器值就減1;任何時刻計數(shù)器為0的對象就是不可能再被使用的。

客觀地說,引用計數(shù)算法(Reference Counting)的實現(xiàn)簡單,判定效率也很高,在大部分情況下它都是一個不錯的算法,也有一些比較著名的應用案例。但是,至少主流的Java虛擬機里面沒有選用引用計數(shù)算法來管理內(nèi)存,其中最主要的原因是它很難解決對象之間相互循環(huán)引用的問題。


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

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

對象objA和objB都有字段instance,賦值令objA.instance=objB及objB.instance=objA,除此之外,這兩個對象再無任何引用,實際上這兩個對象已經(jīng)不可能再被訪問,但是它們因為互相引用著對方,導致它們的引用計數(shù)都不為0,于是引用計數(shù)算法無法通知GC收集器回收它們。



可達性分析算法?

在主流的商用程序語言(Java、C#)的主流實現(xiàn)中,都是稱通過可達性分析(Reachability Analysis)來判定對象是否存活的。這個算法的基本思路就是通過一系列的稱為“GC Roots”的對象作為起始點,從這些節(jié)點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連(用圖論的話來說,就是從GC Roots到這個對象不可達)時,則證明此對象是不可用的。如圖3,對象object 5、object 6、object 7雖然互相有關聯(lián),但是它們到GC Roots是不可達的,所以它們將會被判定為是可回收的對象。

在Java語言中,可作為GC Roots的對象包括下面幾種:

?虛擬機棧(棧幀中的本地變量表)中引用的對象。?

方法區(qū)中類靜態(tài)屬性引用的對象。

?方法區(qū)中常量引用的對象。

?本地方法棧中JNI(即一般說的Native方法)引用的對象。



在JDK 1.2之后,將引用分為強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種。?

強引用,類似“Object obj=new Object()”這類的引用,只要強引用還存在,垃圾收集器永遠不會回收掉被引用的對象。?

軟引用,描述一些還有用但并非必需的對象。在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會把 軟引用關聯(lián)著的對象 列進回收范圍之中進行第二次回收。如果這次回收還沒有足夠的內(nèi)存,才會拋出內(nèi)存溢出異常。

?弱引用,描述非必需對象的,被弱引用關聯(lián)的對象只能生存到下一次垃圾收集發(fā)生之前。當垃圾收集器工作時,無論當前內(nèi)存是否足夠,都會回收掉只被弱引用關聯(lián)的對象。

虛引用也稱為幽靈引用或者幻影引用,它是最弱的一種引用關系。一個對象是否有虛引用的存在,完全不會對其生存時間構(gòu)成影響,也無法通過虛引用來取得一個對象實例。為一個對象設置虛引用關聯(lián)的唯一目的就是能在這個對象被收集器回收時收到一個系統(tǒng)通知。

生存還是死亡?

即使在可達性分析算法中不可達的對象,也并非是“非死不可”的,這時候它們暫時處于“緩刑”階段,要真正宣告一個對象死亡,至少要經(jīng)歷兩次標記過程:如果對象在進行可達性分析后發(fā)現(xiàn)沒有與GC Roots相連接的引用鏈,那它將會被第一次標記并且進行一次篩選,篩選的條件是此對象是否有必要執(zhí)行finalize()方法。當對象沒有覆蓋finalize()方法,或者finalize()方法已經(jīng)被虛擬機調(diào)用過,虛擬機將這兩種情況都視為“沒有必要執(zhí)行”。 如果這個對象被判定為有必要執(zhí)行finalize()方法,那么這個對象將會放置在一個叫做F-Queue的隊列之中,并在稍后由一個由虛擬機自動建立的、低優(yōu)先級的Finalizer線程去執(zhí)行它。這里所謂的“執(zhí)行”是指虛擬機會觸發(fā)這個方法,但并不承諾會等待它運行結(jié)束,這樣做的原因是,如果一個對象在finalize()方法中執(zhí)行緩慢,或者發(fā)生了死循環(huán),將很可能會導致F-Queue隊列中其他對象永久處于等待,甚至導致整個內(nèi)存回收系統(tǒng)崩潰。finalize()方法是對象逃脫死亡命運的最后一次機會,稍后GC將對F-Queue中的對象進行第二次小規(guī)模的標記,如果對象要在finalize()中成功拯救自己——只要重新與引用鏈上的任何一個對象建立關聯(lián)即可,譬如把自己(this關鍵字)賦值給某個類變量或者對象的成員變量,那在第二次標記時它將被移除出“即將回收”的集合;如果對象這時候還沒有逃脫,那基本上它就真的被回收了。從代碼清單3-2中我們可以看到一個對象的finalize()被執(zhí)行,但是它仍然可以存活。


?*1.對象可以在被GC時自我拯救。?
?*2.這種自救的機會只有一次,因為一個對象的finalize()方法最多只會被系統(tǒng)自動調(diào)用一次
?public?class?FinalizeEscapeGC{
?public?static?FinalizeEscapeGC?SAVE_HOOK=null;
?public?void?isAlive(){
?System.out.println("yes,i?am?still?alive:)");
?}
?protected?void?finalize()throws?Throwable{
?super.finalize();
?System.out.println("finalize?mehtod?executed!");
?FinalizeEscapeGC.SAVE_HOOK=this;
?}
?public?static?void?main(String[]args)throws?Throwable{
?SAVE_HOOK=new?FinalizeEscapeGC();
?//對象第一次成功拯救自己
?SAVE_HOOK=null;?
?System.gc();
?//因為finalize方法優(yōu)先級很低,所以暫停0.5秒以等待它
?Thread.sleep(500);
?if(SAVE_HOOK!=null){
?SAVE_HOOK.isAlive();
?}else{
?System.out.println("no,i?am?dead:(");
?}
?//下面這段代碼與上面的完全相同,但是這次自救卻失敗了?
?SAVE_HOOK=null;?
?System.gc();
?//因為finalize方法優(yōu)先級很低,所以暫停0.5秒以等待它
?Thread.sleep(500);
?if(SAVE_HOOK!=null){
?SAVE_HOOK.isAlive();
?}else{
?System.out.println("no,i?am?dead:(");
?}
??}
?}

SAVE_HOOK對象的finalize()方法確實被GC收集器觸發(fā)過,并且在被收集前成功逃脫了。


代碼中有兩段完全一樣的代碼片段,執(zhí)行結(jié)果卻是一次逃脫成功,一次失敗,這是因為任何一個對象的finalize()方法都只會被系統(tǒng)自動調(diào)用一次,如果對象面臨下一次回收,它的finalize()方法不會被再次執(zhí)行,因此第二段代碼的自救行動失敗了。

回收方法區(qū) Java虛擬機規(guī)范不要求虛擬機在方法區(qū)實現(xiàn)垃圾收集,而且在方法區(qū)中進行垃圾收集的“性價比”一般比較低:在堆中,尤其是在新生代中,常規(guī)應用進行一次垃圾收集一般可以回收70%~95%的空間,而永久代的垃圾收集效率遠低于此。

永久代的垃圾收集主要回收兩部分內(nèi)容:廢棄常量和無用的類。以常量池中字面量的回收為例,假如一個字符串“abc”已經(jīng)進入了常量池中,但是當前系統(tǒng)沒有任何一個String對象是叫做“abc”的,換句話說,就是沒有任何String對象引用常量池中的“abc”常量,也沒有其他地方引用了這個字面量,如果這時發(fā)生內(nèi)存回收,而且必要的話,這個“abc”常量就會被系統(tǒng)清理出常量池。常量池中的其他類(接口)、方法、字段的符號引用也與此類似。

判定一個常量是否是“廢棄常量”比較簡單,而要判定一個類是否是“無用的類”的條件則相對苛刻許多。類需要同時滿足下面3個條件才能算是“無用的類”:
該類所有的實例都已經(jīng)被回收,也就是Java堆中不存在該類的任何實例。 ?加載該類的ClassLoader已經(jīng)被回收。 ?該類對應的java.lang.Class對象沒有在任何地方被引用,無法在任何地方通過反射訪問該類的方法。
虛擬機可以對滿足上述3個條件的無用類進行回收,這里說的僅僅是“可以”,而并不是和對象一樣,不使用了就必然會回收。是否對類進行回收,HotSpot虛擬機提供了-Xnoclassgc參數(shù)進行控制,還可以使用-verbose:class以及-XX:+TraceClassLoading、-XX:+TraceClassUnLoading查看類加載和卸載信息,其中-verbose:class和-XX:+TraceClassLoading可以在Product版的虛擬機中使用,-XX:+TraceClassUnLoading參數(shù)需要FastDebug版的虛擬機支持。

在大量使用反射、動態(tài)代理、CGLib等ByteCode框架、動態(tài)生成JSP以及OSGi這類頻繁自定義ClassLoader的場景都需要虛擬機具備類卸載的功能,以保證永久代不會溢出。

垃圾收集算法 標記-清除算法
首先標記出所有需要回收的對象,在標記完成后統(tǒng)一回收所有被標記的對象

它的主要不足有兩個:一個是效率問題,標記和清除兩個過程的效率都不高;另一個是空間問題,標記清除之后會產(chǎn)生大量不連續(xù)的內(nèi)存碎片,空間碎片太多可能會導致以后在程序運行過程中需要分配較大對象時,無法找到足夠的連續(xù)內(nèi)存而不得不提前觸發(fā)另一次垃圾收集動作


復制算法 它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當這一塊的內(nèi)存用完了,就將還存活著的對象復制到另外一塊上面,然后再把已使用過的內(nèi)存空間一次清理掉。
這樣使得每次都是對整個半?yún)^(qū)進行內(nèi)存回收,內(nèi)存分配時也就不用考慮內(nèi)存碎片等復雜情況,只要移動堆頂指針,按順序分配內(nèi)存即可,實現(xiàn)簡單,運行高效。只是這種算法的代價是將內(nèi)存縮小為了原來的一半。


標記-整理算法 復制收集算法在對象存活率較高時就要進行較多的復制操作,效率將會變低。更關鍵的是,如果不想浪費50%的空間,就需要有額外的空間進行分配擔保,以應對被使用的內(nèi)存中所有對象都100%存活的極端情況,所以在老年代一般不能直接選用這種算法。
?根據(jù)老年代的特點,有人提出了另外一種“標記-整理”(Mark-Compact)算法,標記過程仍然與“標記-清除”算法一樣,但后續(xù)步驟不是直接對可回收對象進行清理,而是讓所有存活的對象都向一端移動,然后直接清理掉端邊界以外的內(nèi)存。


分代收集算法 只是根據(jù)對象存活周期的不同將內(nèi)存劃分為幾塊。一般是把Java堆分為新生代和老年代,這樣就可以根據(jù)各個年代的特點采用最適當?shù)氖占惴?。在新生代中,每次垃圾收集時都發(fā)現(xiàn)有大批對象死去,只有少量存活,那就選用復制算法,只需要付出少量存活對象的復制成本就可以完成收集。而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須使用“標記—清理”或者“標記—整理”算法來進行回收。

本站聲明: 本文章由作者或相關機構(gòu)授權(quán)發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內(nèi)容真實性等。需要轉(zhuǎn)載請聯(lián)系該專欄作者,如若文章內(nèi)容侵犯您的權(quán)益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

9月2日消息,不造車的華為或?qū)⒋呱龈蟮莫毥谦F公司,隨著阿維塔和賽力斯的入局,華為引望愈發(fā)顯得引人矚目。

關鍵字: 阿維塔 塞力斯 華為

加利福尼亞州圣克拉拉縣2024年8月30日 /美通社/ -- 數(shù)字化轉(zhuǎn)型技術(shù)解決方案公司Trianz今天宣布,該公司與Amazon Web Services (AWS)簽訂了...

關鍵字: AWS AN BSP 數(shù)字化

倫敦2024年8月29日 /美通社/ -- 英國汽車技術(shù)公司SODA.Auto推出其旗艦產(chǎn)品SODA V,這是全球首款涵蓋汽車工程師從創(chuàng)意到認證的所有需求的工具,可用于創(chuàng)建軟件定義汽車。 SODA V工具的開發(fā)耗時1.5...

關鍵字: 汽車 人工智能 智能驅(qū)動 BSP

北京2024年8月28日 /美通社/ -- 越來越多用戶希望企業(yè)業(yè)務能7×24不間斷運行,同時企業(yè)卻面臨越來越多業(yè)務中斷的風險,如企業(yè)系統(tǒng)復雜性的增加,頻繁的功能更新和發(fā)布等。如何確保業(yè)務連續(xù)性,提升韌性,成...

關鍵字: 亞馬遜 解密 控制平面 BSP

8月30日消息,據(jù)媒體報道,騰訊和網(wǎng)易近期正在縮減他們對日本游戲市場的投資。

關鍵字: 騰訊 編碼器 CPU

8月28日消息,今天上午,2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會開幕式在貴陽舉行,華為董事、質(zhì)量流程IT總裁陶景文發(fā)表了演講。

關鍵字: 華為 12nm EDA 半導體

8月28日消息,在2024中國國際大數(shù)據(jù)產(chǎn)業(yè)博覽會上,華為常務董事、華為云CEO張平安發(fā)表演講稱,數(shù)字世界的話語權(quán)最終是由生態(tài)的繁榮決定的。

關鍵字: 華為 12nm 手機 衛(wèi)星通信

要點: 有效應對環(huán)境變化,經(jīng)營業(yè)績穩(wěn)中有升 落實提質(zhì)增效舉措,毛利潤率延續(xù)升勢 戰(zhàn)略布局成效顯著,戰(zhàn)新業(yè)務引領增長 以科技創(chuàng)新為引領,提升企業(yè)核心競爭力 堅持高質(zhì)量發(fā)展策略,塑強核心競爭優(yōu)勢...

關鍵字: 通信 BSP 電信運營商 數(shù)字經(jīng)濟

北京2024年8月27日 /美通社/ -- 8月21日,由中央廣播電視總臺與中國電影電視技術(shù)學會聯(lián)合牽頭組建的NVI技術(shù)創(chuàng)新聯(lián)盟在BIRTV2024超高清全產(chǎn)業(yè)鏈發(fā)展研討會上宣布正式成立。 活動現(xiàn)場 NVI技術(shù)創(chuàng)新聯(lián)...

關鍵字: VI 傳輸協(xié)議 音頻 BSP

北京2024年8月27日 /美通社/ -- 在8月23日舉辦的2024年長三角生態(tài)綠色一體化發(fā)展示范區(qū)聯(lián)合招商會上,軟通動力信息技術(shù)(集團)股份有限公司(以下簡稱"軟通動力")與長三角投資(上海)有限...

關鍵字: BSP 信息技術(shù)
關閉
關閉