本篇文章主要介紹一下jvm的內(nèi)存管理機(jī)制硝全,包括內(nèi)存區(qū)域和垃圾收集相關(guān)內(nèi)容批糟。
1赚爵、jvm運(yùn)行時(shí)數(shù)據(jù)區(qū)域包括方法區(qū)(Method Area)庶柿、堆(Heap)村怪、虛擬機(jī)棧(VM Stack)、本地方法棧(Native Method Stack)和程序計(jì)數(shù)器(Program Counter Register)浮庐。
①程序計(jì)數(shù)器甚负,可理解為當(dāng)前線程所執(zhí)行的字節(jié)碼的行號(hào)指示器,每一個(gè)線程都會(huì)有一個(gè)獨(dú)立的程序計(jì)數(shù)器审残,線程之間獨(dú)立存儲(chǔ)梭域、互不影響。
②虛擬機(jī)棧搅轿,每個(gè)方法在執(zhí)行的同時(shí)都會(huì)創(chuàng)建一個(gè)棧幀用戶存儲(chǔ)局部變量表病涨、操作數(shù)棧、動(dòng)態(tài)鏈接璧坟、方法出口等信息既穆,每個(gè)方法的調(diào)用到完成對(duì)應(yīng)著虛擬機(jī)棧中入棧到出棧的過程。局部變量表存放各種基本數(shù)據(jù)類型雀鹃、對(duì)象引用和returnAddree類型(指向一條字節(jié)碼指令的地址)幻工。若線程請(qǐng)求棧深超過虛擬機(jī)允許的深度,會(huì)拋出StackOverflowError黎茎;虛擬機(jī)擴(kuò)展時(shí)無法申請(qǐng)足夠內(nèi)存囊颅,會(huì)拋出OutOfMemoryError。
③本地方法棧,類似于虛擬機(jī)棧踢代,虛擬機(jī)棧為java方法服務(wù)先鱼,而本地方法棧為native方法服務(wù)。(Sun HotSpot直接將本地方法棧和虛擬機(jī)棧合二為一)
④Java堆奸鬓,是虛擬機(jī)內(nèi)存管理中最大的一塊,所有線程共享掸读,存放對(duì)象實(shí)例串远。Java堆是垃圾收集器管理的主要區(qū)域,又稱“GC堆”(Garbage Collected Heap)儿惫。從垃圾回收的角度澡罚,可以劃分為老年代和新生代,再細(xì)致又可劃分Eden空間肾请、From Survivor空間留搔,To Survivor空間,從內(nèi)存分配又可劃分多個(gè)線程私有的分配緩沖區(qū)(Thread Local Allocation Buffer)铛铁,但存儲(chǔ)的還都是對(duì)象實(shí)例隔显。
⑤方法區(qū),線程共享饵逐,存儲(chǔ)類信息括眠、常亮、靜態(tài)變量倍权、即時(shí)編譯器編譯后的代碼等數(shù)據(jù)掷豺。HotSpot之前實(shí)現(xiàn)上又稱方法區(qū)為“永久代”,其實(shí)更容易出現(xiàn)內(nèi)存溢出問題(對(duì)此區(qū)域完全不收集)薄声,或逐步改為Native Memory實(shí)現(xiàn)当船,并且將字符串常量池移出(Java技術(shù)——你真的了解String類的intern()方法嗎,jdk1.7+移到Java Heap)默辨。此外趁機(jī)補(bǔ)充一下內(nèi)存泄漏和內(nèi)存溢出德频,內(nèi)存泄漏:應(yīng)用使用資源之后沒有及時(shí)釋放資源,導(dǎo)致應(yīng)用內(nèi)存中持有的了不需要的資源廓奕;內(nèi)存溢出:應(yīng)用內(nèi)存已經(jīng)不能滿足正常的使用抱婉,堆棧已經(jīng)達(dá)到系統(tǒng)設(shè)置的最大值,導(dǎo)致崩潰桌粉。
⑥運(yùn)行時(shí)常量池蒸绩,用戶存放編譯期各種字面量和符號(hào)引用,運(yùn)行期間也可將常量放入池中铃肯,例如String類的intern()方法患亿。
⑦直接內(nèi)存(Direct Memory),并不是虛擬機(jī)運(yùn)行時(shí)數(shù)據(jù)區(qū)的一部分,也不是java虛擬機(jī)規(guī)范中定義的內(nèi)存區(qū)域步藕。jdk1.4引入NIO惦界,基于通道(Channel)與緩沖區(qū)(buffer)的I/O方式,通過Native庫函數(shù)直接分配堆外內(nèi)存咙冗,通過Java堆中的DirectByteBuffer對(duì)象作為引用進(jìn)行操作沾歪,避免java堆和Native堆來回復(fù)制數(shù)據(jù),提高性能雾消。
2灾搏、對(duì)象的創(chuàng)建。
①遇到new指令立润,在常量池中尋找類的符號(hào)引用狂窑,檢查是否被加載、解析和初始化過桑腮,否則進(jìn)行加載②為新對(duì)象分配內(nèi)存空間③初始化0值(不包括對(duì)象頭)泉哈,保證對(duì)象不賦初始值可直接使用④設(shè)置對(duì)象頭(Object Header),例如類的元數(shù)據(jù)破讨、哈希碼丛晦、分代年齡、是否啟用偏向鎖等⑤執(zhí)行init()方法
步驟二為對(duì)象分配內(nèi)存空間有兩種方式:“指針碰撞”和“空閑列表”提陶,依據(jù)內(nèi)存規(guī)整性做分配采呐,使用Serial(串行收集器)、ParNew(Serial多線程版本)等帶有compact(聚集)過程的收集器搁骑,系統(tǒng)采用指針碰撞(一邊存放對(duì)象斧吐,一邊空閑,每新增一個(gè)對(duì)象仲器,指針向空閑區(qū)域挪動(dòng))煤率;而使用CMS(Concurrent Mark Sweep 并發(fā)標(biāo)記清理)基于Mark-Sweep算法收集器,通常采用空閑列表法(在分配的時(shí)候從列表中找到一塊足夠大的空間劃分給對(duì)象)乏冀。在并發(fā)過程中蝶糯,虛擬機(jī)分配內(nèi)存有兩種策略,對(duì)分配內(nèi)存空間動(dòng)作進(jìn)行同步處理辆沦,CAS+失敗重試昼捍;把內(nèi)存分配動(dòng)作按線程劃分在不同空間進(jìn)行操作,即本地線程分配緩沖(Thread Local Allocation Buffer, TLAB)肢扯,通過-XX:+/-UseTLAB設(shè)置妒茬。
3、對(duì)象的內(nèi)存布局
在HotSopt虛擬機(jī)中蔚晨,對(duì)象在內(nèi)存中的存儲(chǔ)布局可分為對(duì)象頭乍钻、實(shí)例數(shù)據(jù)和對(duì)齊填充。對(duì)象頭包含兩部分內(nèi)容①對(duì)象自身運(yùn)行時(shí)數(shù)據(jù),如哈希碼银择、GC分代年齡多糠、鎖狀態(tài)標(biāo)準(zhǔn)、線程持有的鎖浩考、偏向線程ID夹孔、偏向時(shí)間戳,此部分?jǐn)?shù)據(jù)又稱“Mark Word”析孽,另一部分析蝴,存放類型指針,即類元數(shù)據(jù)指針绿淋,用于判斷對(duì)象屬于哪個(gè)類的實(shí)例。 實(shí)例數(shù)據(jù)尝盼,即各種字段內(nèi)容吞滞、繼承信息,默認(rèn)分配策略是相同寬度的字段分配到一起盾沫。對(duì)齊填充裁赠,對(duì)象起始地址必須是8字節(jié)的整數(shù)倍。
4赴精、對(duì)象訪問定位
使用對(duì)象佩捞,即通過棧上的reference數(shù)據(jù)類操作堆上的具體對(duì)象,目前reference存儲(chǔ)句柄(handle)地址或直接指針蕾哟。句柄方式一忱,堆中會(huì)分出一塊內(nèi)存作為句柄池,reference存放句柄地址谭确,當(dāng)對(duì)象移動(dòng)是改變句柄中實(shí)例數(shù)據(jù)的指針帘营,reference本身不必修改;直接指針方式逐哈,訪問更快芬迄,避免一次指針定位時(shí)間開銷,需要修改reference昂秃,HotSpot使用直接指針方式禀梳。
5、判斷對(duì)象“存活”的辦法
①引用計(jì)數(shù)算法:給對(duì)象添加一個(gè)引用計(jì)數(shù)器肠骆,有一個(gè)地方就+1算途,引用失效就-1,任何時(shí)刻蚀腿,計(jì)數(shù)為0的對(duì)象不可再被引用郊艘。jvm沒有選用該種算法,該種算法不能解決循環(huán)引用問題。
②可達(dá)性分析算法:通過一系列“GC Roots”對(duì)象作為起始點(diǎn)纱注,從節(jié)點(diǎn)向下搜索的路徑成為引用鏈(Reference Chain)畏浆,當(dāng)一個(gè)對(duì)象到GC Roots沒有任務(wù)引用鏈,證明不可用狞贱。能夠作為GC Roots的對(duì)象有:虛擬機(jī)棧(本地變量表)中引用的對(duì)象刻获、方法區(qū)中類靜態(tài)屬性應(yīng)用的變量、方法區(qū)中常量引用的對(duì)象瞎嬉、本地方法棧JNI(一般是Native)引用的對(duì)象蝎毡。
③引用:當(dāng)一個(gè)對(duì)象處于“食之無味棄之可惜”時(shí),以上兩種方法就無法判斷氧枣。在jdk1.2之后沐兵,將引用分為強(qiáng)引用(Strong Reference)、軟引用(Soft Reference)便监、弱引用(Weak Reference)扎谎、虛引用(Phantom Reference)
強(qiáng)引用:在虛擬機(jī)中普遍存在,類似于Object obj = new Object()烧董,只要強(qiáng)引用在毁靶,就不會(huì)被回收。
軟引用:描述有用但并非必需的對(duì)象逊移。提供SoftReference類來實(shí)現(xiàn)预吆。系統(tǒng)在發(fā)生內(nèi)存溢出之前,會(huì)把對(duì)象列進(jìn)回收范圍中進(jìn)行第二次回收胳泉,如果回收后還沒有足夠內(nèi)存拐叉,則拋出內(nèi)存溢出異常。
弱引用:描述非必需對(duì)象扇商,比軟引用更弱巷嚣。提供WeakReference類實(shí)現(xiàn)。只能生存到下一次垃圾收集發(fā)生之前钳吟。收集器工作一定會(huì)回收弱引用對(duì)象廷粒。
虛引用:又稱幽靈引用或幻影引用,無法通過虛引用來取得實(shí)例红且。提供PhantomReference坝茎。使用虛引用的目的就是在該對(duì)象被收集器回收收到一個(gè)通知
④java finalize()方法,當(dāng)對(duì)象沒有覆蓋finalize()方法或finalize()已經(jīng)被調(diào)用暇番,則視為“沒有必要執(zhí)行”嗤放,避免通過finalize方法拯救對(duì)象。
6壁酬、回收方法區(qū)次酌,主要回收廢棄常量和無用的類恨课。系統(tǒng)沒有其他對(duì)象引用常量,就會(huì)將該常量清理出去岳服。無用的類需要滿足:①所有的實(shí)例已經(jīng)被回收②加載該類的ClassLoader已經(jīng)被回收③該類的Class對(duì)象沒有被引用剂公,無法在任何地方通過反射訪問該類。滿足以上條件吊宋,可進(jìn)行回收(應(yīng)用在大量使用反射纲辽、動(dòng)態(tài)代理、CGLib璃搜,動(dòng)態(tài)生成JSP拖吼、OSGi等功能)。
7这吻、垃圾收集算法
①標(biāo)記-清除算法(Mark-Sweep)吊档,標(biāo)記和清除兩個(gè)過程效率不高,容易產(chǎn)生內(nèi)存碎片唾糯。
②復(fù)制算法怠硼,將內(nèi)存分兩半,每次只用其中一半趾断,gc時(shí),將存活的移到另一半吩愧,對(duì)之前進(jìn)行清理芋酌。代價(jià)太高,內(nèi)存縮小一半雁佳。IBM專門研究脐帝,將內(nèi)存(新生代)分配成8:1:1即Eden,form survivor糖权,to survivor堵腹,每次只用Eden和一個(gè)survivor,清理時(shí)將存活的對(duì)象移到另一個(gè)survivor星澳,空間不足疚顷,則依賴?yán)夏甏鷥?nèi)存。
③標(biāo)記-整理算法(Mark-Compact)禁偎,跟據(jù)老年代特點(diǎn)設(shè)計(jì)腿堤,跟標(biāo)記-清除類似,不過標(biāo)記之后如暖,將存活對(duì)象往一端移動(dòng)笆檀,清除端邊界以外的內(nèi)存。
④分代收集(Generational Collection)盒至,根據(jù)對(duì)象存活周期不同酗洒,劃分為幾塊士修,選擇合適的收集算法。
參考:
《深入理解Java虛擬機(jī)》