JAVA
1. Java運(yùn)行時(shí)內(nèi)存區(qū)域
在JAVA運(yùn)行時(shí)的內(nèi)存區(qū)域中驶乾,由JVM管理的內(nèi)存區(qū)域分為以下幾個(gè)模塊:
- 程序計(jì)數(shù)區(qū):由當(dāng)前線程獨(dú)占惰爬,記錄當(dāng)前線程的字節(jié)碼文件執(zhí)行到哪一行。
- 虛擬機(jī)棧:由當(dāng)前線程獨(dú)占辜王,存放當(dāng)前線程調(diào)用方法的棧幀的棧劈狐。
- 本地方法棧:由當(dāng)前線程獨(dú)占,和虛擬機(jī)棧類似呐馆, 只不過虛擬機(jī)棧記錄的是JAVA方法肥缔,本地方法棧記錄的是native方法。
- 堆:由所有線程共享汹来,存放對(duì)象實(shí)例续膳。
- 方法區(qū):由所有線程共享,存儲(chǔ)已經(jīng)被虛擬機(jī)加載的類信息俗慈,final常量姑宽,靜態(tài)變量,編譯器即時(shí)編譯的代碼等闺阱。
棧幀補(bǔ)充:每一個(gè)線程都有一個(gè)虛擬機(jī)棧炮车,每當(dāng)線程中執(zhí)行一個(gè)方法的時(shí)候,就會(huì)向虛擬機(jī)棧中插入一個(gè)棧幀酣溃,當(dāng)方法執(zhí)行完后瘦穆,再將棧幀出棧。棧幀中包含局部變量表赊豌,操作站扛或,方法出口等。
局部變量表中存儲(chǔ)著方法相關(guān)的局部變量碘饼,包括基本數(shù)據(jù)類型熙兔,對(duì)象的引用,返回地址等艾恼。
具體請(qǐng)參考Java內(nèi)存區(qū)域與內(nèi)存溢出
方法區(qū)補(bǔ)充:方法區(qū)屬于垃圾回收機(jī)制中的永久代(一共有青年帶住涉,老年代,永久代三種)钠绍,因此方法區(qū)的垃圾回收很少舆声,但不代表不會(huì)發(fā)生垃圾回收,其上的垃圾回收主要針對(duì)常量池的內(nèi)存回收和對(duì)已加載類的卸載柳爽。
2. JAVA對(duì)象的訪問方式
一般來說媳握,一個(gè)JAVA引用至少會(huì)涉及到三個(gè)內(nèi)存區(qū)域,虛擬機(jī)棧磷脯、堆蛾找、方法區(qū)。
例如 Object obj = new Object();
- Object obj表示一個(gè)本地引用赵誓,存儲(chǔ)在虛擬機(jī)棧的本地變量表中
- new Object()作為實(shí)例對(duì)象存放在堆中
- 堆中還存儲(chǔ)了Object類的類型信息(接口腋粥,方法晦雨,field,對(duì)象類型等)的地址隘冲,這些地址所指向的內(nèi)容存放在方法區(qū)中
3. JAVA內(nèi)存分配及回收機(jī)制
分代分配闹瞧,分代回收。
JAVA內(nèi)存分為年輕代展辞、老年代奥邮、永久代
- 年輕代:對(duì)象被創(chuàng)建時(shí),內(nèi)存分配首先創(chuàng)建在年輕代的Eden區(qū)(如果年輕代空間不足罗珍,則大對(duì)象直接分配在老年代上)洽腺。大部分對(duì)象很快就不再使用。年輕代的內(nèi)存區(qū)域分為:一個(gè)Eden區(qū)和兩個(gè)Survivor區(qū)(比例為8:1:1)
- 老年代:對(duì)象如果在年輕代存活了很長(zhǎng)時(shí)間沒有被回收掉覆旱,就會(huì)被復(fù)制到老年代蘸朋。老年代的空間比年輕代大,發(fā)生的GC次數(shù)也比年輕代少扣唱。
- 永久代:方法區(qū)屬于永久代藕坯,永久代的垃圾回收次數(shù)很少,但是也會(huì)發(fā)生GC噪沙。
GC分為兩種:
- Minor GC
只會(huì)在年輕代的Eden區(qū)進(jìn)行垃圾回收 - FULL GC
會(huì)在年輕代, 老年代, 永久帶都進(jìn)行垃圾回收
有如下原因可能導(dǎo)致Full GC:
1.年老代(Tenured)被寫滿;
2.持久代(Perm)被寫滿;
3.System.gc()被顯示調(diào)用;
4.上一次GC之后Heap的各域分配策略動(dòng)態(tài)變化.
GC機(jī)制:
- 年輕代:主要使用“停止-復(fù)制”算法炼彪,停止指的是,發(fā)生GC的時(shí)候會(huì)暫停除了GC線程以外的所有線程的運(yùn)行正歼。
復(fù)制的過程如下:- 絕大部分的對(duì)象剛創(chuàng)建的時(shí)候會(huì)被分配到Eden區(qū)辐马,其中大部分的對(duì)象會(huì)很快消亡。Eden區(qū)是連續(xù)的內(nèi)存空間局义,因此在其上分配內(nèi)存是很快的喜爷。
- 最初一次,當(dāng)Eden區(qū)滿的時(shí)候萄唇,執(zhí)行Minor GC贞奋,將Eden區(qū)的消亡對(duì)象清除掉,并將剩余的對(duì)象放到Survivor1中(此時(shí)Survivor2是空的穷绵,兩個(gè)Survivor總有一個(gè)是空的)
- 下次Eden再滿的時(shí)候,執(zhí)行Minor GC特愿,將Eden區(qū)的消亡對(duì)象清除掉仲墨,將剩余的對(duì)象放到Survivor1中
- 當(dāng)Survivor1滿了的時(shí)候則將Eden和Survivor1中的消亡的對(duì)象清除掉,并將Eden和Survivor1中剩余的對(duì)象復(fù)制到Survivor2中
- 當(dāng)兩個(gè)Survivor交換了幾次后揍障,就可以將剩下的對(duì)象復(fù)制到老年代中了
- 老年代:使用“標(biāo)記-整理”算法目养,即將存活的對(duì)象向一邊移動(dòng),以此來保證回收后毒嫡,內(nèi)存依然是連續(xù)的癌蚁,不會(huì)出現(xiàn)內(nèi)存碎片幻梯。每次年輕代的Eden發(fā)生Minor GC時(shí),虛擬機(jī)都會(huì)檢查每次晉級(jí)老年代的大小是否大于老年代的剩余大小努释,如果大于則會(huì)觸發(fā)FULL GC碘梢。
- 永久代:永久代的回收有兩種,常量池中的常量伐蒂,無用的類的信息煞躬。
常量沒有引用了就可以回收。
無用的類必須保證3點(diǎn)才可以回收:- 類的所有實(shí)例已經(jīng)被回收
- 加載類的ClassLoader已經(jīng)被回收
- 類對(duì)象的class對(duì)象沒有被引用(即沒有反射調(diào)用該類的地方)
4. 減少GC開銷的措施
根據(jù)上述GC的機(jī)制,程序的運(yùn)行會(huì)直接影響系統(tǒng)環(huán)境的變化,從而影響GC的觸發(fā)逸邦。若不針對(duì)GC的特點(diǎn)進(jìn)行設(shè)計(jì)和編碼,就會(huì)出現(xiàn)內(nèi)存駐留等一系列負(fù)面影響恩沛。為了避免這些影響,基本的原則就是盡可能地減少垃圾和減少GC過程中的開銷。具體措施包括以下幾個(gè)方面:
不要顯式調(diào)用System.gc()
此函數(shù)建議JVM進(jìn)行主GC,雖然只是建議而非一定,但很多情況下它會(huì)觸發(fā)主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數(shù)缕减。盡量減少臨時(shí)對(duì)象的使用
臨時(shí)對(duì)象在跳出函數(shù)調(diào)用后,會(huì)成為垃圾,少用臨時(shí)變量就相當(dāng)于減少了垃圾的產(chǎn)生,從而延長(zhǎng)了出現(xiàn)上述第二個(gè)觸發(fā)條件出現(xiàn)的時(shí)間,減少了主GC的機(jī)會(huì)雷客。對(duì)象不用時(shí)最好顯式置為Null
一般而言,為Null的對(duì)象都會(huì)被作為垃圾處理,所以將不用的對(duì)象顯式地設(shè)為Null,有利于GC收集器判定垃圾,從而提高了GC的效率。盡量使用StringBuffer,而不用String來累加字符串
由于String是固定長(zhǎng)的字符串對(duì)象,累加String對(duì)象時(shí),并非在一個(gè)String對(duì)象中擴(kuò)增,而是重新創(chuàng)建新的String對(duì)象,如Str5=Str1+Str2+Str3+Str4,這條語句執(zhí)行過程中會(huì)產(chǎn)生多個(gè)垃圾對(duì)象,因?yàn)閷?duì)次作“+”操作時(shí)都必須創(chuàng)建新的String對(duì)象,但這些過渡對(duì)象對(duì)系統(tǒng)來說是沒有實(shí)際意義的,只會(huì)增加更多的垃圾桥狡。避免這種情況可以改用StringBuffer來累加字符串,因StringBuffer是可變長(zhǎng)的,它在原有基礎(chǔ)上進(jìn)行擴(kuò)增,不會(huì)產(chǎn)生中間對(duì)象搅裙。能用基本類型如Int,Long,就不用Integer,Long對(duì)象
基本類型變量占用的內(nèi)存資源比相應(yīng)對(duì)象占用的少得多,如果沒有必要,最好使用基本變量。盡量少用靜態(tài)對(duì)象變量
靜態(tài)變量屬于全局變量,不會(huì)被GC回收,它們會(huì)一直占用內(nèi)存总放。分散對(duì)象創(chuàng)建或刪除的時(shí)間
集中在短時(shí)間內(nèi)大量創(chuàng)建新對(duì)象,特別是大對(duì)象,會(huì)導(dǎo)致突然需要大量?jī)?nèi)存,JVM在面臨這種情況時(shí),只能進(jìn)行主GC,以回收內(nèi)存或整合內(nèi)存碎片,從而增加主GC的頻率呈宇。集中刪除對(duì)象,道理也是一樣的。它使得突然出現(xiàn)了大量的垃圾對(duì)象,空閑空間必然減少,從而大大增加了下一次創(chuàng)建新對(duì)象時(shí)強(qiáng)制主GC的機(jī)會(huì)局雄。