一、jvm運(yùn)行時(shí)內(nèi)存區(qū)域劃分:
看圖說(shuō)話轻抱,JVM大致分為五個(gè)區(qū)域:方法區(qū)倘要、堆區(qū)、程序計(jì)數(shù)器、虛擬機(jī)棧和本地方法棧封拧。其中方法區(qū)和堆區(qū)是線程共享志鹃,剩下三者則屬于非線程共享。
1.1泽西、程序計(jì)數(shù)器(線程私有曹铃,生命周期與線程相同)
????????一塊較小的內(nèi)存區(qū)域,用于存儲(chǔ)當(dāng)前線程指令的地址捧杉,通過(guò)改變程序計(jì)數(shù)器的值來(lái)執(zhí)行下一條需要執(zhí)行的指令陕见。因?yàn)橐粋€(gè)處理器同一時(shí)間只能處理一條線程種的指令。所以為了線程恢復(fù)后味抖,線程能從正確的位置開(kāi)始執(zhí)行评甜,必須要每條開(kāi)辟一個(gè)獨(dú)立的程序計(jì)數(shù)器記錄。所以:該內(nèi)存屬于“線程私有”內(nèi)存仔涩。
1.2忍坷、Java虛擬機(jī)棧(線程私有,生命周期與線程相同)
?????????虛擬機(jī)棧描述的是Java方法執(zhí)行的內(nèi)存模型:每個(gè)方法被執(zhí)行的時(shí)候都會(huì)創(chuàng)建一個(gè)棧幀用于存儲(chǔ)局部變量表熔脂,操作棧佩研,動(dòng)態(tài)鏈接,方法出口等信息霞揉。每一個(gè)方法被調(diào)用的過(guò)程就對(duì)應(yīng)一個(gè)棧幀在虛擬機(jī)棧中從入棧到出棧的過(guò)程旬薯。
? ? ? ? 局部變量表存放了編譯器可知的各種基本數(shù)據(jù)類型、對(duì)象引用和returnAddress類型(指向一條字節(jié)碼指令的地址)适秩。局部變量表所需的內(nèi)存空間在編譯器完成分配绊序,當(dāng)進(jìn)入一個(gè)方法時(shí)這個(gè)方法需要在幀中分配多大的內(nèi)存空間是完全確定的,運(yùn)行期間不會(huì)改變局部變量表的大小秽荞。(64為長(zhǎng)度的long和double會(huì)占用兩個(gè)局部變量空間政模,其他的數(shù)據(jù)類型占用一個(gè))
?????Java虛擬機(jī)棧可能出現(xiàn)兩種類型的異常:1. 線程請(qǐng)求的棧深度大于虛擬機(jī)允許的棧深度蚂会,將拋出StackOverflowError淋样。2.虛擬機(jī)棧空間可以動(dòng)態(tài)擴(kuò)展胁住,當(dāng)動(dòng)態(tài)擴(kuò)展是無(wú)法申請(qǐng)到足夠的空間時(shí)趁猴,拋出OutOfMemory異常。
1.3彪见、本地方法棧(線程私有儡司,生命周期與線程相同)
? ? ? ? 發(fā)揮的作用和虛擬機(jī)棧類似,只不過(guò)它是為虛擬機(jī)的Native方法服務(wù)余指。(一個(gè)Native方法就是一個(gè)java調(diào)用非java代碼的接口捕犬。目的是:使用其它語(yǔ)言跷坝,調(diào)用與更底層進(jìn)行直接交互。跳過(guò)中間步驟碉碉,提高程序的執(zhí)行效率)
1.4柴钻、堆區(qū)(線程共享,生命周期與jvm存亡)
????????Java的堆是一個(gè)運(yùn)行時(shí)數(shù)據(jù)區(qū),類的對(duì)象從中分配空間垢粮。這些對(duì)象內(nèi)存不需要程序代碼來(lái)顯式的釋放贴届。堆是由垃圾回收來(lái)負(fù)責(zé)的,堆的優(yōu)勢(shì)是可以動(dòng)態(tài)地分配內(nèi)存大小蜡吧,生存期也不必事先告訴編譯器毫蚓,因?yàn)樗窃谶\(yùn)行時(shí)動(dòng)態(tài)分配內(nèi)存的。
????????Java堆是垃圾收集器管理的主要區(qū)域昔善,所以也稱為“GC堆”元潘。由于現(xiàn)在的垃圾收集器基本上都是采用分代收集算法,所以Java堆還可細(xì)分為:新生代和老生代君仆。在細(xì)致一點(diǎn)可分為Eden空間翩概,F(xiàn)rom Survivor空間,To Survivor空間袖订。如果從內(nèi)存分配的角度看,線程共享的Java堆可劃分出多個(gè)線程私有的分配緩沖區(qū)嗅虏。
1.5洛姑、方法區(qū)(線程共享,生命周期與jvm存亡)
????????方法區(qū)也是線程共享的區(qū)域皮服,用于存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息楞艾,常量,靜態(tài)變量和即時(shí)編譯器(JIT)編譯后的代碼等數(shù)據(jù)龄广。
1.6硫眯、運(yùn)行時(shí)常量池
????????運(yùn)行時(shí)常量池是方法區(qū)的一部分。CLass文件中除了有類的版本择同、字段两入、方法、接口等描述信息外敲才,還有一項(xiàng)信息是常量池裹纳,用于存放編譯期生成的各種字面量和符號(hào)引用,這部分內(nèi)容將在類加載后進(jìn)入方法區(qū)的運(yùn)行時(shí)常量池中存放紧武。
????????運(yùn)行時(shí)常量池相對(duì)于CLass文件常量池的另外一個(gè)重要特征是具備動(dòng)態(tài)性剃氧,Java語(yǔ)言并不要求常量一定只有編譯期才能產(chǎn)生,也就是并非預(yù)置入CLass文件中常量池的內(nèi)容才能進(jìn)入方法區(qū)運(yùn)行時(shí)常量池阻星,運(yùn)行期間也可能將新的常量放入池中朋鞍,這種特性被開(kāi)發(fā)人員利用比較多的就是String類的intern()方法。
二、各個(gè)區(qū)域的內(nèi)存溢出問(wèn)題
????????內(nèi)存溢出理解:當(dāng)程序需要申請(qǐng)內(nèi)存的時(shí)候滥酥,由于沒(méi)有足夠的內(nèi)存更舞,此時(shí)就會(huì)拋出OutOfMemoryError,這就是內(nèi)存溢出恨狈。
????????2.1疏哗、程序計(jì)數(shù)器是jvm唯一一塊區(qū)域不存在內(nèi)存溢出問(wèn)題的。
????????2.2禾怠、虛擬機(jī)棧和本地方法棧內(nèi)存溢出:如果某線程請(qǐng)求的棧幀深度大于虛擬機(jī)允許的最大深度返奉,將拋出StackOverflow異常。
????????2.3吗氏、方法區(qū)內(nèi)存溢出:根據(jù)它存儲(chǔ)的Class相關(guān)信息(類名芽偏、常量池、字段描述弦讽、方法描述)來(lái)看污尉。當(dāng)大量的類占用填滿方法區(qū)時(shí),自然會(huì)拋出OutOfMemoryError異常往产。
????????2.4被碗、堆區(qū)內(nèi)存溢出:當(dāng)對(duì)象實(shí)例大量存在,并超出虛擬機(jī)設(shè)置對(duì)內(nèi)存設(shè)置上限仿村,自然會(huì)拋出OutOfMemoryError異常锐朴。
三、內(nèi)存溢出產(chǎn)生的一個(gè)不可忽視原因——內(nèi)存泄漏
? ? ? ? 3.1蔼囊、內(nèi)存泄漏理解:已經(jīng)申請(qǐng)的內(nèi)存焚志,但無(wú)法被釋放。這一部分內(nèi)存空間就會(huì)一直占用著畏鼓,當(dāng)越來(lái)越多的內(nèi)存泄漏酱酬,隨之而來(lái)的就是內(nèi)存空間不足,拋出OutOfMemoryError異常云矫。
? ? ? ? 3.2膳沽、jvm對(duì)對(duì)象釋放的處理算法:可達(dá)性分析算法。
? ? ? ? ? ? 這個(gè)算法的基本思路就是通過(guò)一系列名為"GC Roots"的對(duì)象作為起始點(diǎn)让禀,從這些節(jié)點(diǎn)開(kāi)始向下搜索贵少,搜索所走過(guò)的路徑稱為引用鏈(Reference Chain),當(dāng)一個(gè)對(duì)象到GC Roots沒(méi)有任何引用鏈相連時(shí)堆缘,則證明此對(duì)象是不可用的滔灶,下圖對(duì)象object5, object6, object7雖然有互相判斷,但它們到GC Roots是不可達(dá)的吼肥,所以它們將會(huì)判定為是可回收對(duì)象录平。
在Java語(yǔ)言里麻车,可作為GC Roots對(duì)象的包括如下幾種:
??? a.虛擬機(jī)棧的本地變量表中的引用的對(duì)象
??? b.方法區(qū)中的類靜態(tài)屬性引用的對(duì)象
??? c.方法區(qū)中的常量引用的對(duì)象
????d.本地方法棧中JNI的引用的對(duì)象?
四、常見(jiàn)內(nèi)存泄漏場(chǎng)景
? ??4.1斗这、靜態(tài)集合類引起內(nèi)存泄漏:
????????像HashMap动猬、Vector等這些靜態(tài)變量的生命周期和應(yīng)用程序一致,他們所引用的所有的對(duì)象也不能被釋放表箭,因?yàn)樗麄円矊⒁恢北籚ector等引用著赁咙。
? ??4.2、監(jiān)聽(tīng)器
????????在java 編程中免钻,我們都需要和監(jiān)聽(tīng)器打交道彼水,通常一個(gè)應(yīng)用當(dāng)中會(huì)用到很多監(jiān)聽(tīng)器,我們會(huì)調(diào)用一個(gè)控件的諸如addXXXListener()等方法來(lái)增加監(jiān)聽(tīng)器极舔,但往往在釋放對(duì)象的時(shí)候卻沒(méi)有記住去刪除這些監(jiān)聽(tīng)器凤覆,從而增加了內(nèi)存泄漏的機(jī)會(huì)。
? ??4.3拆魏、各種連接
????????比如數(shù)據(jù)庫(kù)連接(dataSourse.getConnection())盯桦,網(wǎng)絡(luò)連接(socket)和io連接,除非其顯式的調(diào)用了其close()方法將其連接關(guān)閉渤刃,否則是不會(huì)自動(dòng)被GC 回收的拥峦。這種情況下一般都會(huì)在try里面去的連接,在finally里面釋放連接卖子。
? ??4.4略号、單例模式
????????不正確使用單例模式是引起內(nèi)存泄漏的一個(gè)常見(jiàn)問(wèn)題,單例對(duì)象在初始化后將在JVM的整個(gè)生命周期中存在(以靜態(tài)變量的方式)揪胃,如果單例對(duì)象持有外部的引用璃哟,那么這個(gè)對(duì)象將不能被JVM正撤兆粒回收喊递,導(dǎo)致內(nèi)存泄漏.
問(wèn)答環(huán)節(jié)。
一阳似、為什么需要了解jvm內(nèi)存分配呢骚勘?平常碼代碼,我只要規(guī)范使用java手冊(cè)撮奏,似乎問(wèn)題照樣能做好需求俏讹。
????????了解內(nèi)存分配主要是為了,當(dāng)出現(xiàn)內(nèi)存泄漏和內(nèi)存溢出時(shí)畜吊,javaer能更好的排查錯(cuò)誤泽疆。也是為了能更好的提升java的性能,對(duì)其調(diào)優(yōu)玲献。規(guī)范是別人總結(jié)的經(jīng)驗(yàn)殉疼,遵守它梯浪,能盡可能避免異常問(wèn)題;好奇它瓢娜,了解它挂洛,我們更是能在出現(xiàn)問(wèn)題時(shí),自己解決它眠砾。豈不美滋虏劲。