JAVA
1. Java運行時內(nèi)存區(qū)域
在JAVA運行時的內(nèi)存區(qū)域中,由JVM管理的內(nèi)存區(qū)域分為以下幾個模塊:
- 程序計數(shù)區(qū):由當前線程獨占,記錄當前線程的字節(jié)碼文件執(zhí)行到哪一行。
- 虛擬機棧:由當前線程獨占,存放當前線程調(diào)用方法的棧幀的棧。
- 本地方法棧:由當前線程獨占,和虛擬機棧類似, 只不過虛擬機棧記錄的是JAVA方法,本地方法棧記錄的是native方法。
- 堆:由所有線程共享,存放對象實例。
- 方法區(qū):由所有線程共享,存儲已經(jīng)被虛擬機加載的類信息,final常量,靜態(tài)變量,編譯器即時編譯的代碼等。
棧幀補充:每一個線程都有一個虛擬機棧,每當線程中執(zhí)行一個方法的時候,就會向虛擬機棧中插入一個棧幀,當方法執(zhí)行完后,再將棧幀出棧。棧幀中包含局部變量表,操作站,方法出口等。
局部變量表中存儲著方法相關(guān)的局部變量,包括基本數(shù)據(jù)類型,對象的引用,返回地址等。
具體請參考Java內(nèi)存區(qū)域與內(nèi)存溢出
方法區(qū)補充:方法區(qū)屬于垃圾回收機制中的永久代(一共有青年帶,老年代,永久代三種),因此方法區(qū)的垃圾回收很少,但不代表不會發(fā)生垃圾回收,其上的垃圾回收主要針對常量池的內(nèi)存回收和對已加載類的卸載。
2. JAVA對象的訪問方式
一般來說,一個JAVA引用至少會涉及到三個內(nèi)存區(qū)域,虛擬機棧、堆、方法區(qū)。
例如 Object obj = new Object();
- Object obj表示一個本地引用,存儲在虛擬機棧的本地變量表中
- new Object()作為實例對象存放在堆中
- 堆中還存儲了Object類的類型信息(接口,方法,field,對象類型等)的地址,這些地址所指向的內(nèi)容存放在方法區(qū)中
3. JAVA內(nèi)存分配及回收機制
分代分配,分代回收。
JAVA內(nèi)存分為年輕代、老年代、永久代
- 年輕代:對象被創(chuàng)建時,內(nèi)存分配首先創(chuàng)建在年輕代的Eden區(qū)(如果年輕代空間不足,則大對象直接分配在老年代上)。大部分對象很快就不再使用。年輕代的內(nèi)存區(qū)域分為:一個Eden區(qū)和兩個Survivor區(qū)(比例為8:1:1)
- 老年代:對象如果在年輕代存活了很長時間沒有被回收掉,就會被復制到老年代。老年代的空間比年輕代大,發(fā)生的GC次數(shù)也比年輕代少。
- 永久代:方法區(qū)屬于永久代,永久代的垃圾回收次數(shù)很少,但是也會發(fā)生GC。
GC分為兩種:
- Minor GC
只會在年輕代的Eden區(qū)進行垃圾回收 - FULL GC
會在年輕代, 老年代, 永久帶都進行垃圾回收
有如下原因可能導致Full GC:
1.年老代(Tenured)被寫滿;
2.持久代(Perm)被寫滿;
3.System.gc()被顯示調(diào)用;
4.上一次GC之后Heap的各域分配策略動態(tài)變化.
GC機制:
- 年輕代:主要使用“停止-復制”算法,停止指的是,發(fā)生GC的時候會暫停除了GC線程以外的所有線程的運行。
復制的過程如下:- 絕大部分的對象剛創(chuàng)建的時候會被分配到Eden區(qū),其中大部分的對象會很快消亡。Eden區(qū)是連續(xù)的內(nèi)存空間,因此在其上分配內(nèi)存是很快的。
- 最初一次,當Eden區(qū)滿的時候,執(zhí)行Minor GC,將Eden區(qū)的消亡對象清除掉,并將剩余的對象放到Survivor1中(此時Survivor2是空的,兩個Survivor總有一個是空的)
- 下次Eden再滿的時候,執(zhí)行Minor GC,將Eden區(qū)的消亡對象清除掉,將剩余的對象放到Survivor1中
- 當Survivor1滿了的時候則將Eden和Survivor1中的消亡的對象清除掉,并將Eden和Survivor1中剩余的對象復制到Survivor2中
- 當兩個Survivor交換了幾次后,就可以將剩下的對象復制到老年代中了
- 老年代:使用“標記-整理”算法,即將存活的對象向一邊移動,以此來保證回收后,內(nèi)存依然是連續(xù)的,不會出現(xiàn)內(nèi)存碎片。每次年輕代的Eden發(fā)生Minor GC時,虛擬機都會檢查每次晉級老年代的大小是否大于老年代的剩余大小,如果大于則會觸發(fā)FULL GC。
- 永久代:永久代的回收有兩種,常量池中的常量,無用的類的信息。
常量沒有引用了就可以回收。
無用的類必須保證3點才可以回收:- 類的所有實例已經(jīng)被回收
- 加載類的ClassLoader已經(jīng)被回收
- 類對象的class對象沒有被引用(即沒有反射調(diào)用該類的地方)
4. 減少GC開銷的措施
根據(jù)上述GC的機制,程序的運行會直接影響系統(tǒng)環(huán)境的變化,從而影響GC的觸發(fā)。若不針對GC的特點進行設計和編碼,就會出現(xiàn)內(nèi)存駐留等一系列負面影響。為了避免這些影響,基本的原則就是盡可能地減少垃圾和減少GC過程中的開銷。具體措施包括以下幾個方面:
不要顯式調(diào)用System.gc()
此函數(shù)建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發(fā)主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數(shù)。盡量減少臨時對象的使用
臨時對象在跳出函數(shù)調(diào)用后,會成為垃圾,少用臨時變量就相當于減少了垃圾的產(chǎn)生,從而延長了出現(xiàn)上述第二個觸發(fā)條件出現(xiàn)的時間,減少了主GC的機會。對象不用時最好顯式置為Null
一般而言,為Null的對象都會被作為垃圾處理,所以將不用的對象顯式地設為Null,有利于GC收集器判定垃圾,從而提高了GC的效率。盡量使用StringBuffer,而不用String來累加字符串
由于String是固定長的字符串對象,累加String對象時,并非在一個String對象中擴增,而是重新創(chuàng)建新的String對象,如Str5=Str1+Str2+Str3+Str4,這條語句執(zhí)行過程中會產(chǎn)生多個垃圾對象,因為對次作“+”操作時都必須創(chuàng)建新的String對象,但這些過渡對象對系統(tǒng)來說是沒有實際意義的,只會增加更多的垃圾。避免這種情況可以改用StringBuffer來累加字符串,因StringBuffer是可變長的,它在原有基礎上進行擴增,不會產(chǎn)生中間對象。能用基本類型如Int,Long,就不用Integer,Long對象
基本類型變量占用的內(nèi)存資源比相應對象占用的少得多,如果沒有必要,最好使用基本變量。盡量少用靜態(tài)對象變量
靜態(tài)變量屬于全局變量,不會被GC回收,它們會一直占用內(nèi)存。分散對象創(chuàng)建或刪除的時間
集中在短時間內(nèi)大量創(chuàng)建新對象,特別是大對象,會導致突然需要大量內(nèi)存,JVM在面臨這種情況時,只能進行主GC,以回收內(nèi)存或整合內(nèi)存碎片,從而增加主GC的頻率。集中刪除對象,道理也是一樣的。它使得突然出現(xiàn)了大量的垃圾對象,空閑空間必然減少,從而大大增加了下一次創(chuàng)建新對象時強制主GC的機會。