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

首頁(yè) > 編程 > Java > 正文

淺析Java內(nèi)存模型與垃圾回收

2019-11-26 14:20:48
字體:
來(lái)源:轉(zhuǎn)載
供稿:網(wǎng)友

1、Java內(nèi)存模型

Java虛擬機(jī)在執(zhí)行程序時(shí)把它管理的內(nèi)存分為若干數(shù)據(jù)區(qū)域,這些數(shù)據(jù)區(qū)域分布情況如下圖所示:

程序計(jì)數(shù)器:一塊較小內(nèi)存區(qū)域,指向當(dāng)前所執(zhí)行的字節(jié)碼。如果線程正在執(zhí)行一個(gè)Java方法,這個(gè)計(jì)數(shù)器記錄正在執(zhí)行的虛擬機(jī)字節(jié)碼指令的地址,如果執(zhí)行的是Native方法,這個(gè)計(jì)算器值為空。

Java虛擬機(jī)棧:線程私有的,其生命周期和線程一致,每個(gè)方法執(zhí)行時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表、操作數(shù)棧、動(dòng)態(tài)鏈接、方法出口等信息。

本地方法棧:與虛擬機(jī)棧功能類(lèi)似,只不過(guò)虛擬機(jī)棧為虛擬機(jī)執(zhí)行Java方法服務(wù),而本地方法棧則為使用到的Native方法服務(wù)。

Java堆:是虛擬機(jī)管理內(nèi)存中最大的一塊,被所有線程共享,該區(qū)域用于存放對(duì)象實(shí)例,幾乎所有的對(duì)象都在該區(qū)域分配。Java堆是內(nèi)存回收的主要區(qū)域,從內(nèi)存回收角度看,由于現(xiàn)在的收集器大都采用分代收集算法,所以Java堆還可以細(xì)分為:新生代和老年代,再細(xì)分一點(diǎn)的話可以分為Eden空間、From Survivor空間、To Survivor空間等。根據(jù)Java虛擬機(jī)規(guī)范規(guī)定,Java堆可以處于物理上不連續(xù)的空間,只要邏輯上是連續(xù)的就行。

方法區(qū):與Java一樣,是各個(gè)線程所共享的,用于存儲(chǔ)已被虛擬機(jī)加載類(lèi)信息、常亮、靜態(tài)變量、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)。

運(yùn)行時(shí)常量池,運(yùn)行時(shí)常量池是方法區(qū)的一部分,Class文件中除了有類(lèi)的版本、字段、方法、接口等描述信息外,還有一項(xiàng)信息是常量池,用于存放編譯期生成的各種字面量和符號(hào)引用。運(yùn)行期間可以將新的常量放入常量池中,用得比較多的就是String類(lèi)的intern()方法,當(dāng)一個(gè)String實(shí)例調(diào)用intern時(shí),Java查找常量池中是否有相同的Unicode的字符串常量,若有,則返回其引用;若沒(méi)有,則在常量池中增加一個(gè)Unicode等于該實(shí)例字符串并返回它的引用。

2、垃圾對(duì)象如何確定

Java堆中存放著幾所所有的對(duì)象實(shí)例,垃圾收集器在對(duì)堆進(jìn)行回收前,首先需要確定哪些對(duì)象還"活著",哪些已經(jīng)"死亡",也就是不會(huì)被任何途徑使用的對(duì)象。

引用計(jì)數(shù)法

引用計(jì)數(shù)法實(shí)現(xiàn)簡(jiǎn)單,效率較高,在大部分情況下是一個(gè)不錯(cuò)的算法。其原理是:給對(duì)象添加一個(gè)引用計(jì)數(shù)器,每當(dāng)有一個(gè)地方引用該對(duì)象時(shí),計(jì)數(shù)器加1,當(dāng)引用失效時(shí),計(jì)數(shù)器減1,當(dāng)計(jì)數(shù)器值為0時(shí)表示該對(duì)象不再被使用。需要注意的是:引用計(jì)數(shù)法很難解決對(duì)象之間相互循環(huán)引用的問(wèn)題,主流Java虛擬機(jī)沒(méi)有選用引用計(jì)數(shù)法來(lái)管理內(nèi)存。

可達(dá)性分析算法

這個(gè)算法的基本思路就是通過(guò)一系列的稱(chēng)為“GC Roots”的對(duì)象作為起始點(diǎn),從這些節(jié)點(diǎn)開(kāi)始向下搜索,搜索所走過(guò)的路徑稱(chēng)為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連(用圖論的話來(lái)說(shuō),就是從GC Roots到這個(gè)對(duì)象不可達(dá))時(shí),則證明此對(duì)象是不可用的。如圖所示,對(duì)象object 5、object 6、object 7雖然互相有關(guān)聯(lián),但是它們到GC Roots是不可達(dá)的,所以它們將會(huì)被判定為是可回收的對(duì)象。

在Java語(yǔ)言中,可作為GC Roots的對(duì)象包括下面幾種:

虛擬機(jī)棧(棧幀中的本地變量表)中引用的對(duì)象。

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

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

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

現(xiàn)在問(wèn)題來(lái)了,可達(dá)性分析算法會(huì)不會(huì)出現(xiàn)對(duì)象間循環(huán)引用問(wèn)題呢?答案是肯定的,那就是不會(huì)出現(xiàn)對(duì)象間循環(huán)引用問(wèn)題。GC Root在對(duì)象圖之外,是特別定義的“起點(diǎn)”,不可能被對(duì)象圖內(nèi)的對(duì)象所引用。

對(duì)象生存還是死亡(To Die Or Not To Die)

即使在可達(dá)性分析算法中不可達(dá)的對(duì)象,也并非是“非死不可”的,這時(shí)候它們暫時(shí)處于“緩刑”階段,要真正宣告一個(gè)對(duì)象死亡,至少要經(jīng)歷兩次標(biāo)記過(guò)程:如果對(duì)象在進(jìn)行可達(dá)性分析后發(fā)現(xiàn)沒(méi)有與GC Roots相連接的引用鏈,那它將會(huì)被第一次標(biāo)記并且進(jìn)行一次篩選,篩選的條件是此對(duì)象是否有必要執(zhí)行finapze()方法。當(dāng)對(duì)象沒(méi)有覆蓋finapze()方法,或者finapze()方法已經(jīng)被虛擬機(jī)調(diào)用過(guò),虛擬機(jī)將這兩種情況都視為“沒(méi)有必要執(zhí)行”。程序中可以通過(guò)覆蓋finapze()來(lái)一場(chǎng)"驚心動(dòng)魄"的自我拯救過(guò)程,但是,這只有一次機(jī)會(huì)呦。

/**  * 此代碼演示了兩點(diǎn):  * 1.對(duì)象可以在被GC時(shí)自我拯救。  * 2.這種自救的機(jī)會(huì)只有一次,因?yàn)橐粋€(gè)對(duì)象的finapze()方法最多只會(huì)被系統(tǒng)自動(dòng)調(diào)用一次  * @author zzm  */ pubpc class FinapzeEscapeGC {   pubpc static FinapzeEscapeGC SAVE_HOOK = null;   pubpc void isApve() {   System.out.println("yes, i am still apve :)");  }   @Override  protected void finapze() throws Throwable {   super.finapze();   System.out.println("finapze mehtod executed!");   FinapzeEscapeGC.SAVE_HOOK = this;  }   pubpc static void main(String[] args) throws Throwable {   SAVE_HOOK = new FinapzeEscapeGC();    //對(duì)象第一次成功拯救自己   SAVE_HOOK = null;   System.gc();   //因?yàn)閒inapze方法優(yōu)先級(jí)很低,所以暫停0.5秒以等待它   Thread.sleep(500);   if (SAVE_HOOK != null) { SAVE_HOOK.isApve();   } else { System.out.println("no, i am dead :(");   }    //下面這段代碼與上面的完全相同,但是這次自救卻失敗了   SAVE_HOOK = null;   System.gc();   //因?yàn)閒inapze方法優(yōu)先級(jí)很低,所以暫停0.5秒以等待它   Thread.sleep(500);   if (SAVE_HOOK != null) { SAVE_HOOK.isApve();   } else { System.out.println("no, i am dead :(");   }  } }

運(yùn)行結(jié)果為:

finapze mehtod executed! yes, i am still apve :) no, i am dead :( 

接著說(shuō)引用

無(wú)論是通過(guò)引用計(jì)數(shù)算法判斷對(duì)象的引用數(shù)量,還是通過(guò)可達(dá)性分析算法判斷對(duì)象的引用鏈?zhǔn)欠窨蛇_(dá),判定對(duì)象是否存活都與“引用”有關(guān)。在JDK 1.2以前,Java中的引用的定義很傳統(tǒng):如果reference類(lèi)型的數(shù)據(jù)中存儲(chǔ)的數(shù)值代表的是另外一塊內(nèi)存的起始地址,就稱(chēng)這塊內(nèi)存代表著一個(gè)引用。在JDK 1.2之后,Java對(duì)引用的概念進(jìn)行了擴(kuò)充,將引用分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4種,這4種引用強(qiáng)度依次逐漸減弱。

• 強(qiáng)引用就是指在程序代碼之中普遍存在的,類(lèi)似“Object obj = new Object()”這類(lèi)的引用,只要強(qiáng)引用還存在,垃圾收集器永遠(yuǎn)不會(huì)回收掉被引用的對(duì)象。

• 軟引用是用來(lái)描述一些還有用但并非必需的對(duì)象。對(duì)于軟引用關(guān)聯(lián)著的對(duì)象,在系統(tǒng)將要發(fā)生內(nèi)存溢出異常之前,將會(huì)把這些對(duì)象列進(jìn)回收范圍之中進(jìn)行第二次回收。如果這次回收還沒(méi)有足夠的內(nèi)存,才會(huì)拋出內(nèi)存溢出異常。在JDK 1.2之后,提供了SoftReference類(lèi)來(lái)實(shí)現(xiàn)軟引用。

• 弱引用也是用來(lái)描述非必需對(duì)象的,但是它的強(qiáng)度比軟引用更弱一些,被弱引用關(guān)聯(lián)的對(duì)象只能生存到下一次垃圾收集發(fā)生之前。當(dāng)垃圾收集器工作時(shí),無(wú)論當(dāng)前內(nèi)存是否足夠,都會(huì)回收掉只被弱引用關(guān)聯(lián)的對(duì)象。在JDK 1.2之后,提供了WeakReference類(lèi)來(lái)實(shí)現(xiàn)弱引用。

• 虛引用也稱(chēng)為幽靈引用或者幻影引用,它是最弱的一種引用關(guān)系。一個(gè)對(duì)象是否有虛引用的存在,完全不會(huì)對(duì)其生存時(shí)間構(gòu)成影響,也無(wú)法通過(guò)虛引用來(lái)取得一個(gè)對(duì)象實(shí)例。為一個(gè)對(duì)象設(shè)置虛引用關(guān)聯(lián)的唯一目的就是能在這個(gè)對(duì)象被收集器回收時(shí)收到一個(gè)系統(tǒng)通知。在JDK 1.2之后,提供了PhantomReference類(lèi)來(lái)實(shí)現(xiàn)虛引用。

軟引用使用示例:

package jvm;import java.lang.ref.SoftReference;class Node {pubpc String msg = "";}pubpc class Hello {pubpc static void main(String[] args) {Node node1 = new Node(); // 強(qiáng)引用node1.msg = "node1";SoftReference<Node> node2 = new SoftReference<Node>(node1); // 軟引用node2.get().msg = "node2";System.out.println(node1.msg);System.out.println(node2.get().msg);}}

輸出結(jié)果為:

node2node2

3、典型的垃圾回收算法

1.Mark-Sweep(標(biāo)記-清除)算法

這是最基礎(chǔ)的垃圾回收算法,之所以說(shuō)它是最基礎(chǔ)的是因?yàn)樗钊菀讓?shí)現(xiàn),思想也是最簡(jiǎn)單的。標(biāo)記-清除算法分為兩個(gè)階段:標(biāo)記階段和清除階段。標(biāo)記階段的任務(wù)是標(biāo)記出所有需要被回收的對(duì)象,清除階段就是回收被標(biāo)記的對(duì)象所占用的空間。具體過(guò)程如下圖所示:

從圖中可以很容易看出標(biāo)記-清除算法實(shí)現(xiàn)起來(lái)比較容易,但是有一個(gè)比較嚴(yán)重的問(wèn)題就是容易產(chǎn)生內(nèi)存碎片,碎片太多可能會(huì)導(dǎo)致后續(xù)過(guò)程中需要為大對(duì)象分配空間時(shí)無(wú)法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動(dòng)作。

2. Copying(復(fù)制)算法

為了解決Mark-Sweep算法的缺陷,Copying算法就被提了出來(lái)。它將可用內(nèi)存按容量劃分為大小相等的兩塊,每次只使用其中的一塊。當(dāng)這一塊的內(nèi)存用完了,就將還存活著的對(duì)象復(fù)制到另外一塊上面,然后再把已使用的內(nèi)存空間一次清理掉,這樣一來(lái)就不容易出現(xiàn)內(nèi)存碎片的問(wèn)題。具體過(guò)程如下圖所示:

這種算法雖然實(shí)現(xiàn)簡(jiǎn)單,運(yùn)行高效且不容易產(chǎn)生內(nèi)存碎片,但是卻對(duì)內(nèi)存空間的使用做出了高昂的代價(jià),因?yàn)槟軌蚴褂玫膬?nèi)存縮減到原來(lái)的一半。

很顯然,Copying算法的效率跟存活對(duì)象的數(shù)目多少有很大的關(guān)系,如果存活對(duì)象很多,那么Copying算法的效率將會(huì)大大降低。

3. Mark-Compact(標(biāo)記-整理)算法

為了解決Copying算法的缺陷,充分利用內(nèi)存空間,提出了Mark-Compact算法。該算法標(biāo)記階段和Mark-Sweep一樣,但是在完成標(biāo)記之后,它不是直接清理可回收對(duì)象,而是將存活對(duì)象都向一端移動(dòng),然后清理掉端邊界以外的內(nèi)存。具體過(guò)程如下圖所示:

4.Generational Collection(分代收集)算法

分代收集算法是目前大部分JVM的垃圾收集器采用的算法。它的核心思想是根據(jù)對(duì)象存活的生命周期將內(nèi)存劃分為若干個(gè)不同的區(qū)域。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation),老年代的特點(diǎn)是每次垃圾收集時(shí)只有少量對(duì)象需要被回收,而新生代的特點(diǎn)是每次垃圾回收時(shí)都有大量的對(duì)象需要被回收,那么就可以根據(jù)不同代的特點(diǎn)采取最適合的收集算法。

目前大部分垃圾收集器對(duì)于新生代都采取Copying算法,因?yàn)樾律忻看卫厥斩家厥沾蟛糠謱?duì)象,也就是說(shuō)需要復(fù)制的操作次數(shù)較少,但是實(shí)際中并不是按照1:1的比例來(lái)劃分新生代的空間的,一般來(lái)說(shuō)是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間(一般為8:1:1),每次使用Eden空間和其中的一塊Survivor空間,當(dāng)進(jìn)行回收時(shí),將Eden和Survivor中還存活的對(duì)象復(fù)制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過(guò)的Survivor空間。

而由于老年代的特點(diǎn)是每次回收都只回收少量對(duì)象,一般使用的是Mark-Compact算法。

以上這篇淺析Java內(nèi)存模型與垃圾回收就是小編分享給大家的全部?jī)?nèi)容了,希望能給大家一個(gè)參考,也希望大家多多支持武林網(wǎng)。

發(fā)表評(píng)論 共有條評(píng)論
用戶名: 密碼:
驗(yàn)證碼: 匿名發(fā)表
主站蜘蛛池模板: 资讯 | 曲麻莱县| 宣武区| 黑河市| 余庆县| 南漳县| 苗栗县| 册亨县| 临夏县| 班玛县| 双鸭山市| 龙岩市| 新乡县| 炎陵县| 图木舒克市| 偃师市| 左云县| 靖边县| 邛崃市| 新巴尔虎左旗| 临安市| 怀化市| 舒兰市| 通许县| 麻阳| 昭苏县| 萍乡市| 宜宾县| 盐边县| 肥城市| 叙永县| 东平县| 平顺县| 曲松县| 平乐县| 大石桥市| 绵竹市| 石河子市| 上栗县| 南康市| 黄浦区|