作者blog:http://pengjiaheng.iteye.com/
數據類型:基本類型和引用類型
基本類型的變量保存原始值,它代表的值就是數值本身。byte,short,int,long,char,float,double,Boolean,returnAddress
引用類型代表某個對象的引用,存放引用值的地址。類類型,接口類型,數組
棧stack 和 堆heap:
stack是運行時單位,每個線程都會有線程棧與之對應。里面存儲的是當前線程相關信息。包括局部變量,程序運行狀態,方法返回值等
heap是存儲數據的地方,所有線程共享。存放對象信息。
1 從軟件設計的角度看,stack代表處理邏輯,heap代表數據。
2 heap的數據被多個線程共享,則多個線程可以訪問同一對象。heap的數據可以供所有stack訪問,節省了空間
3 stack因為運行時的需要,保存系統運行的上下文,需要進行地址段的劃分。并且只能向上增長,因此限制了stack的存儲能力。而heap中的數據可以根據需要動態增長,相應stack中只需要記錄heap中的一個地址即可。
4 面向對象就是stack和heap的完美結構。對象的屬性就是數據,存放在heap中。而對象的方法就是運行邏輯,放在stack中。
在java中,main函數就是stack的起點,也是程序的起點。
heap中存放的是對象實例,stack中是基本數據類型和heap中對象的引用。一個對象的大小是不可以估計的,甚至動態變化的。但是在stack中,一個對象只對應了一個4byte的引用。這就是stack和heap分離的好處。
因為基本數據類型占用的空間是1到8個字節,需要空間比較小,而且不會出現動態增長的情況,因此stack中存儲就夠了。
java中參數傳遞時傳值還是傳引用?
程序永遠在stack中運行,因而參數傳遞的只是基本類型或者對象的引用。不會傳遞對象本身。
簡單說,java在方法調用傳遞參數時,都是進行傳值調用。
當傳遞引用的時候,程序會將引用查找到heap中的那個對象,這個時候進行修改,修改的是真實的對象,而不是引用本身!!!
所以傳遞引用也是傳遞的最終值。
另外,因為傳遞基本類型是傳遞了基本值,所以修改的也是另一個copy,而無法修改原值。只有傳遞的是對象引用時才能修改原對象。
stack是程序運行最根本的東西。程序運行可以沒有heap,但必須有stack。
java中,stack的大小是通過-Xss來設置的,當stack中數據比較多時,需要適當調大這個值,否則會出現java.long.StackOverflowError異常
Java對象的大小
一個空object的大小是8byte,這個大小只是保存heap中一個沒有任何屬性的對象大大小。如:
Object o = new Object();
它所占的空間為4byte + 8byte。4byte為stack中保存對象引用需要的空間,8byte是heap中對象的信息。
因為所有java非基本類型對象都是集成自Object,所以不論什么樣的java對象,大小都必須大于8byte
但是因為java在內存中對對象進行分配時都是以8的倍數來分配,因此會為NewObject對象實例分配 24byte。
需要注意基本類型的包裝類型的大小。因為包裝類型已經成為對象了,因此要把包裝類型當對象來看待。如Integer,Float,Double等
一個包裝類型最少占用16byte(8的倍數),它是使用基本類型的N倍,因此盡量少用包裝類。
對象引用分為:強引用,軟引用,弱引用和虛引用。
1 強引用StrongReference:我們一般聲明對象時jvm生成的引用,強引用環境下,垃圾回收需要嚴格判斷。如果被強引用則不會被回收
2 軟引用SoftReference:一般作為緩存來使用。在垃圾回收的時候,jvm會根據當前系統的剩余內存來決定是否對軟引用進行回收。如果jvm發生outOfMemory時,肯定是沒有軟引用存在的。
3 弱引用WeakReference:與軟引用類似,都是作為緩存來使用。不同的是在垃圾回收的時候,弱引用一定會被回收。因此其生命周期只存在一個垃圾回收周期內。
4 虛引用PhantomReference:形同虛設,隨時會被垃圾回收。其主要功能是與引用隊列(ReferenceQueue)聯合使用。當垃圾回收發現一個對象有虛引用時,就會在回收內存之前,將虛引用添加到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否有虛引用來了解引用對象是否將要被回收。從而決定是否采取行動。
系統一般使用強引用。軟引用和弱引用一般是在內存大小比較受限的情況下使用。常用在桌面引用系統中。
垃圾回收基本算法
1 引用計數 Reference Counting
古老的回收算法。對象多一個引用,就增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只收集計數為0的對象。
該算法最致命的是無法處理循環引用的問題。
2 標記-清除 Mark-Sweep
此算法分為兩個階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個heap,把未標記的對象清除。
此算法需要暫停整個應用。同時產生內存碎片。
3 復制 Copying
此算法把空間劃分為2個相等的區域。每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象復制到另一個區域中。此算法每次只處理正在使用中的對象,因此復制成本比較小,同時復制過去以后還能進行相應的內存整理,不會出現碎片。
此算法的缺點是需要兩倍的內存空間。
4 標記-整理 Mark-Compact
此算法結合了 Mark_Sweep 和 Copying 兩個算法的優點。
第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個heap,清除未標記對象并且把存活對象壓縮到heap的其中一塊,按照順序擺放。
此算法避免了碎片問題,和Copying算法的空間問題。
5 增量收集 Incremental Collecting
實時垃圾回收算法,在應用進行的同時進行垃圾回收,jdk5 沒有使用此算法
6 分代收集 Generational Collecting
基于對對象生命周期分析后得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同周期的對象采用不同的算法(上面算法的一個)進行回收。
7 串行收集
使用單線程處理所有垃圾回收工作,因為無序多線程交互,更容易實現,而且效率很高。但是無法使用多處理器的優勢,所以只適合單處理器的機器。
8 并行收集
使用多線程處理垃圾回收工作速度快,效率高。理論上cpu數目越多,越能體現并行收集的優勢
9 并發收集
前面兩個在進行垃圾回收的時候,需要暫停整個運行環境。因此系統會有明顯的暫停,暫停時間因為heap越大而越長。
垃圾回收面臨的問題
1 如何區分垃圾?
因為引用計數無法解決循環引用。所有后來的垃圾回收算法都是從程序運行的根節點出發,遍歷整個對象引用,查找存活的對象。因為stack是真正進行程序執行的地方,所以要知道哪些對象正在被使用,需要從java stack開始。如果有多個線程,必須對這些線程對應的所有stack進行檢查。
除了stack外,還有系統運行時的寄存器,也是存儲程序運行時數據的地方。這樣以stack和寄存器中的引用為起點,來找到heap中的對象,又從這些對象中找到對heap中其他對象的引用,這樣逐步擴展。最終以null引用或基本類型結束,這樣就形成了一棵以java stack中引用所對應的對象為根節點的一棵對象數。如果stack中有多個引用,則最終形成多棵對象樹。這些對象樹上的對象,都是當前系統運行所需要的對象,不能被回收。而其他剩余對象,視為無法被引用的對象,可以被回收。
因此垃圾回收的起點是一些根對象(java stack,static 變量,寄存器……)。最簡單的java stack就是main函數。這種回收方式,就是Mark-Sweep。
2 如何處理碎片?
因為不同java對象存活時間不同,因此程序運行一段時間后,會出現零散的內存碎片。碎片最直接的問題就是導致無法分配大塊的內存空間,以及程序運行效率降低。Copying和Mark-Compact都可以解決碎片問題
3 如何解決同時存在的對象創建和對象回收問題
垃圾回收線程是回收內存的,程序運行是消耗內存的,一個回收內存,一個分配內存,兩者是毛段的。因此,在現有的垃圾回收方式中,在垃圾回收前,一般都需要暫停整個應用(暫停內存分配),然后進行垃圾回收,回收完成后再繼續應用。
這樣的弊端是,當heap空間持續增大時,垃圾回收的時間也將相應增長,相應的程序暫停時間也增長。一些對時間要求很高的應用,比如最大暫停時間要求是幾百ms,那么當heap空間大于幾個G時,就可能超時。這種情況下,垃圾回收會成為系統運行的一個瓶頸。為了解決這個矛盾,有了并發垃圾回收算法。使用這個算法,垃圾回收線程與程序運行線程同時運行。沒有暫停,算法復雜性會大大增加,系統處理能力也相應降低。同時碎片問題將會比較難解決。
分代垃圾回收詳述:
1 為什么要分代?
基于這樣一個事實:不同對象的生命周期是不一樣的。因此不同生命周期的對象可以采取不同的收集方式,以便提高回收效率。
在java運行過程中會產生大量對象。其中有些對象是與業務信息相關的。比如http請求中的session對象、線程、socket連接,這些跟業務直接掛鉤,因此生命周期比較長。但是程序運行過程中生成的臨時變量,生命周期會比較短。比如String對象等。
2 如何分代?
jvm中共劃為三個代:年輕代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)
持久代主要存放Java類信息,與垃圾回收要收集的java對象關系不大。年青代和年老代是對垃圾收集影響最大的。
年輕代:
1 所有新生成的對象首先都是放在“年輕代"里的。年輕代的目標就是盡可能快速的收集那些生命周期短的對象。
年輕代分為三個區:1個Eden區,2個Survivor(幸存,殘余)區
大部分對象在Eden區生成,當Eden區滿時,還存活的對象將被復制到Survivor1區,當Survivor1區滿時,此區的存活對象將被復制到Survivor2區,此時,Eden區滿時還存活的對象將復制到Survivor2中,Survivor1區會被清空。當Survivor2區也滿時(包含從1中復制過來的對象和從Eden區過來的對象),從Survivor1區復制過來的并且還存活的對象,將被復制到"年老代"的年老區(Tenured Space)。Survivor2區中新增加的從Eden區過來的還存活的對象,將復制到Survivor1中,Survivor2被清空。之后,Eden區滿時還存活的對象就會復制到Survivor1中。重復這樣的循環。
兩個Survivor區是對稱的,沒有先后順序。所以同一個區中可能同時存在從Eden復制過來的對象和另一個Survivor區復制過來的對象。
而復制到年老代的Tenured區的只有從第一個Survivor區過來的對象,因為Tenured區存放的是從第一個Survivor區過來,依舊存活的對象。
兩個Survivor區中總有一個是空的。同時根據需要,可以配置多個Survivor區,延長對象在年輕代中的存在時間,減少被放到年老代的可能。
2 年老代
在年輕代中經歷了N次垃圾回收后仍然存活的對象,就會被放到年老代。因此年老代存放的都是生命周期較長的對象。
3 持久代
用于存放靜態文件,如java類,方法等。
持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=<N>進行設置。
什么情況下觸發垃圾回收?
由于對象進行了分代處理,因此垃圾回收區域、時間也不一樣。
GC(Garbage Collection)有兩種類型:Scavenge GC 和 Full GC
1 Scavenge GC
一般情況下,當新對象生成,并且在Eden區申請空間失敗時,就會觸發Scavenge GC。對Eden區進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區。然后整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。
因為大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,所以Scavenge GC 會頻繁進行。
一般這里需要使用速度快,效率高的算法,使Eden區盡快空閑出來。
2 Full GC
對整個Heap進行整理,包括年輕代,年老代和持久代。Full GC因為需要對整個進行回收,所以比Scavenge GC要慢,因此應該盡可能減少Full GC的次數。在對jvm調優的過程中,很大一部分工作就是對Full GC的調節。導致Full GC是因為如下方法:
選擇合適的垃圾收集算法
1 串行收集器
用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高。但是,無法使用多處理器的優勢,所以只適合單處理器機器。
也可以用在小數據量(100M)情況下的多處理器機器上。用-XX:+UseSerialGC打開
2 并行收集器
對年輕代進行并行垃圾回收,因此可以減少垃圾回收時間。一般在多線程多處理器上使用。使用-XX:+UseParallelGC打開。
并行收集器在J2SE5.0更新上引入,在java SE6.0中進行了增強,可以對年老代進行收集。如果年老代不使用并發收集的話,默認是使用單線程進行垃圾回收,因此會制約擴展能力。使用-XX:+UseParallelOldGC打開。
設置:
可以保證大部分工作都并發進行(應用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應時間要求比較高的大中規模應用。
使用-XX:+UseConcMarkSweepGC打開。
并發收集器主要減少年老代的暫停時間,它在應用不停止的情況下使用獨立的垃圾回收線程,跟蹤可達對象。在每個年老代垃圾回收周期中,在收集出氣并發收起會對整個應用進行簡短的暫停,在收集中還會再暫停一次。第二次暫停時間比第一次稍長,在此過程中多個線程同時進行垃圾回收工作。
并發收集器使用處理器換來短暫的停頓時間。
在一個N個處理器的系統上,并發收集部分使用K/N個可用處理器進行回收,一般情況下1<=K<=N/4。即K小于N的四分之一。
在只有一個處理器的主機上使用并發收集器,設置為incremental mode模式也可以獲得較短的停頓時間。
浮動垃圾(Floating Garbage):
由于在應用運行的同時進行垃圾回收,所以有些垃圾可能在垃圾回收進行完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收周期時才能回收掉。所有,并發收集器一般需要預留20%的空間用于浮動垃圾。
并發模式失敗(Concurrent Mode Failure):
并發收集器在應用運行時進行收集,所以需要保證heap在垃圾回收的這段時間有足夠的空間供程序使用,否則,垃圾回收還未完成,heap空間先滿了。這種情況下就會發生并發模式失敗,此時整個應用會暫停,進行垃圾回收。
為了保證有足夠內存供并發收集器使用,可以設置-XX:CMSInitiatingOccupancyFraction=<N>指定剩余多少heap時開始執行并發收集。
1 串行serial收集器,適用于數據量比較小(100M)的應用,或者單處理器下并且對響應時間無要求的應用。
缺點:只能用于小型應用
2 并行parallel收集器,適用于對吞吐量有高要求,多cpu,對應用相應時間無要求的大中型應用。如后臺處理、科學計算。
缺點:垃圾收集過程中應用響應時間可能加長
3 并發concurrent收集器:適用于對響應時間有高要求,多cpu的大中型應用。如web服務器、應用服務器、電信交換、集成開發環境。
P26……
數據類型:基本類型和引用類型
基本類型的變量保存原始值,它代表的值就是數值本身。byte,short,int,long,char,float,double,Boolean,returnAddress
引用類型代表某個對象的引用,存放引用值的地址。類類型,接口類型,數組
棧stack 和 堆heap:
stack是運行時單位,每個線程都會有線程棧與之對應。里面存儲的是當前線程相關信息。包括局部變量,程序運行狀態,方法返回值等
heap是存儲數據的地方,所有線程共享。存放對象信息。
1 從軟件設計的角度看,stack代表處理邏輯,heap代表數據。
2 heap的數據被多個線程共享,則多個線程可以訪問同一對象。heap的數據可以供所有stack訪問,節省了空間
3 stack因為運行時的需要,保存系統運行的上下文,需要進行地址段的劃分。并且只能向上增長,因此限制了stack的存儲能力。而heap中的數據可以根據需要動態增長,相應stack中只需要記錄heap中的一個地址即可。
4 面向對象就是stack和heap的完美結構。對象的屬性就是數據,存放在heap中。而對象的方法就是運行邏輯,放在stack中。
在java中,main函數就是stack的起點,也是程序的起點。
heap中存放的是對象實例,stack中是基本數據類型和heap中對象的引用。一個對象的大小是不可以估計的,甚至動態變化的。但是在stack中,一個對象只對應了一個4byte的引用。這就是stack和heap分離的好處。
因為基本數據類型占用的空間是1到8個字節,需要空間比較小,而且不會出現動態增長的情況,因此stack中存儲就夠了。
java中參數傳遞時傳值還是傳引用?
程序永遠在stack中運行,因而參數傳遞的只是基本類型或者對象的引用。不會傳遞對象本身。
簡單說,java在方法調用傳遞參數時,都是進行傳值調用。
當傳遞引用的時候,程序會將引用查找到heap中的那個對象,這個時候進行修改,修改的是真實的對象,而不是引用本身!!!
所以傳遞引用也是傳遞的最終值。
另外,因為傳遞基本類型是傳遞了基本值,所以修改的也是另一個copy,而無法修改原值。只有傳遞的是對象引用時才能修改原對象。
stack是程序運行最根本的東西。程序運行可以沒有heap,但必須有stack。
java中,stack的大小是通過-Xss來設置的,當stack中數據比較多時,需要適當調大這個值,否則會出現java.long.StackOverflowError異常
Java對象的大小
一個空object的大小是8byte,這個大小只是保存heap中一個沒有任何屬性的對象大大小。如:
Object o = new Object();
它所占的空間為4byte + 8byte。4byte為stack中保存對象引用需要的空間,8byte是heap中對象的信息。
因為所有java非基本類型對象都是集成自Object,所以不論什么樣的java對象,大小都必須大于8byte
1
2 Class NewObject{
3 int count;
4 boolean flag;
5 Object o;
6 }
其大小為:對象大小(8) + int型(4) + boolean(1) + 對象引用(4) = 17byte2 Class NewObject{
3 int count;
4 boolean flag;
5 Object o;
6 }
但是因為java在內存中對對象進行分配時都是以8的倍數來分配,因此會為NewObject對象實例分配 24byte。
需要注意基本類型的包裝類型的大小。因為包裝類型已經成為對象了,因此要把包裝類型當對象來看待。如Integer,Float,Double等
一個包裝類型最少占用16byte(8的倍數),它是使用基本類型的N倍,因此盡量少用包裝類。
對象引用分為:強引用,軟引用,弱引用和虛引用。
1 強引用StrongReference:我們一般聲明對象時jvm生成的引用,強引用環境下,垃圾回收需要嚴格判斷。如果被強引用則不會被回收
2 軟引用SoftReference:一般作為緩存來使用。在垃圾回收的時候,jvm會根據當前系統的剩余內存來決定是否對軟引用進行回收。如果jvm發生outOfMemory時,肯定是沒有軟引用存在的。
3 弱引用WeakReference:與軟引用類似,都是作為緩存來使用。不同的是在垃圾回收的時候,弱引用一定會被回收。因此其生命周期只存在一個垃圾回收周期內。
4 虛引用PhantomReference:形同虛設,隨時會被垃圾回收。其主要功能是與引用隊列(ReferenceQueue)聯合使用。當垃圾回收發現一個對象有虛引用時,就會在回收內存之前,將虛引用添加到與之關聯的引用隊列中。程序可以通過判斷引用隊列中是否有虛引用來了解引用對象是否將要被回收。從而決定是否采取行動。
系統一般使用強引用。軟引用和弱引用一般是在內存大小比較受限的情況下使用。常用在桌面引用系統中。
垃圾回收基本算法
1 引用計數 Reference Counting
古老的回收算法。對象多一個引用,就增加一個計數,刪除一個引用則減少一個計數。垃圾回收時,只收集計數為0的對象。
該算法最致命的是無法處理循環引用的問題。
2 標記-清除 Mark-Sweep
此算法分為兩個階段。第一階段從引用根節點開始標記所有被引用的對象,第二階段遍歷整個heap,把未標記的對象清除。
此算法需要暫停整個應用。同時產生內存碎片。
3 復制 Copying
此算法把空間劃分為2個相等的區域。每次只使用其中一個區域。垃圾回收時,遍歷當前使用區域,把正在使用中的對象復制到另一個區域中。此算法每次只處理正在使用中的對象,因此復制成本比較小,同時復制過去以后還能進行相應的內存整理,不會出現碎片。
此算法的缺點是需要兩倍的內存空間。
4 標記-整理 Mark-Compact
此算法結合了 Mark_Sweep 和 Copying 兩個算法的優點。
第一階段從根節點開始標記所有被引用對象,第二階段遍歷整個heap,清除未標記對象并且把存活對象壓縮到heap的其中一塊,按照順序擺放。
此算法避免了碎片問題,和Copying算法的空間問題。
5 增量收集 Incremental Collecting
實時垃圾回收算法,在應用進行的同時進行垃圾回收,jdk5 沒有使用此算法
6 分代收集 Generational Collecting
基于對對象生命周期分析后得出的垃圾回收算法。把對象分為年青代、年老代、持久代,對不同周期的對象采用不同的算法(上面算法的一個)進行回收。
7 串行收集
使用單線程處理所有垃圾回收工作,因為無序多線程交互,更容易實現,而且效率很高。但是無法使用多處理器的優勢,所以只適合單處理器的機器。
8 并行收集
使用多線程處理垃圾回收工作速度快,效率高。理論上cpu數目越多,越能體現并行收集的優勢
9 并發收集
前面兩個在進行垃圾回收的時候,需要暫停整個運行環境。因此系統會有明顯的暫停,暫停時間因為heap越大而越長。
垃圾回收面臨的問題
1 如何區分垃圾?
因為引用計數無法解決循環引用。所有后來的垃圾回收算法都是從程序運行的根節點出發,遍歷整個對象引用,查找存活的對象。因為stack是真正進行程序執行的地方,所以要知道哪些對象正在被使用,需要從java stack開始。如果有多個線程,必須對這些線程對應的所有stack進行檢查。
除了stack外,還有系統運行時的寄存器,也是存儲程序運行時數據的地方。這樣以stack和寄存器中的引用為起點,來找到heap中的對象,又從這些對象中找到對heap中其他對象的引用,這樣逐步擴展。最終以null引用或基本類型結束,這樣就形成了一棵以java stack中引用所對應的對象為根節點的一棵對象數。如果stack中有多個引用,則最終形成多棵對象樹。這些對象樹上的對象,都是當前系統運行所需要的對象,不能被回收。而其他剩余對象,視為無法被引用的對象,可以被回收。
因此垃圾回收的起點是一些根對象(java stack,static 變量,寄存器……)。最簡單的java stack就是main函數。這種回收方式,就是Mark-Sweep。
2 如何處理碎片?
因為不同java對象存活時間不同,因此程序運行一段時間后,會出現零散的內存碎片。碎片最直接的問題就是導致無法分配大塊的內存空間,以及程序運行效率降低。Copying和Mark-Compact都可以解決碎片問題
3 如何解決同時存在的對象創建和對象回收問題
垃圾回收線程是回收內存的,程序運行是消耗內存的,一個回收內存,一個分配內存,兩者是毛段的。因此,在現有的垃圾回收方式中,在垃圾回收前,一般都需要暫停整個應用(暫停內存分配),然后進行垃圾回收,回收完成后再繼續應用。
這樣的弊端是,當heap空間持續增大時,垃圾回收的時間也將相應增長,相應的程序暫停時間也增長。一些對時間要求很高的應用,比如最大暫停時間要求是幾百ms,那么當heap空間大于幾個G時,就可能超時。這種情況下,垃圾回收會成為系統運行的一個瓶頸。為了解決這個矛盾,有了并發垃圾回收算法。使用這個算法,垃圾回收線程與程序運行線程同時運行。沒有暫停,算法復雜性會大大增加,系統處理能力也相應降低。同時碎片問題將會比較難解決。
分代垃圾回收詳述:
1 為什么要分代?
基于這樣一個事實:不同對象的生命周期是不一樣的。因此不同生命周期的對象可以采取不同的收集方式,以便提高回收效率。
在java運行過程中會產生大量對象。其中有些對象是與業務信息相關的。比如http請求中的session對象、線程、socket連接,這些跟業務直接掛鉤,因此生命周期比較長。但是程序運行過程中生成的臨時變量,生命周期會比較短。比如String對象等。
2 如何分代?
jvm中共劃為三個代:年輕代(Young Generation)、年老代(Old Generation)、持久代(Permanent Generation)
持久代主要存放Java類信息,與垃圾回收要收集的java對象關系不大。年青代和年老代是對垃圾收集影響最大的。
年輕代:
1 所有新生成的對象首先都是放在“年輕代"里的。年輕代的目標就是盡可能快速的收集那些生命周期短的對象。
年輕代分為三個區:1個Eden區,2個Survivor(幸存,殘余)區
大部分對象在Eden區生成,當Eden區滿時,還存活的對象將被復制到Survivor1區,當Survivor1區滿時,此區的存活對象將被復制到Survivor2區,此時,Eden區滿時還存活的對象將復制到Survivor2中,Survivor1區會被清空。當Survivor2區也滿時(包含從1中復制過來的對象和從Eden區過來的對象),從Survivor1區復制過來的并且還存活的對象,將被復制到"年老代"的年老區(Tenured Space)。Survivor2區中新增加的從Eden區過來的還存活的對象,將復制到Survivor1中,Survivor2被清空。之后,Eden區滿時還存活的對象就會復制到Survivor1中。重復這樣的循環。
兩個Survivor區是對稱的,沒有先后順序。所以同一個區中可能同時存在從Eden復制過來的對象和另一個Survivor區復制過來的對象。
而復制到年老代的Tenured區的只有從第一個Survivor區過來的對象,因為Tenured區存放的是從第一個Survivor區過來,依舊存活的對象。
兩個Survivor區中總有一個是空的。同時根據需要,可以配置多個Survivor區,延長對象在年輕代中的存在時間,減少被放到年老代的可能。
2 年老代
在年輕代中經歷了N次垃圾回收后仍然存活的對象,就會被放到年老代。因此年老代存放的都是生命周期較長的對象。
3 持久代
用于存放靜態文件,如java類,方法等。
持久代對垃圾回收沒有顯著影響,但是有些應用可能動態生成或者調用一些class,例如Hibernate等,在這種時候需要設置一個比較大的持久代空間來存放這些運行過程中新增的類。持久代大小通過-XX:MaxPermSize=<N>進行設置。
什么情況下觸發垃圾回收?
由于對象進行了分代處理,因此垃圾回收區域、時間也不一樣。
GC(Garbage Collection)有兩種類型:Scavenge GC 和 Full GC
1 Scavenge GC
一般情況下,當新對象生成,并且在Eden區申請空間失敗時,就會觸發Scavenge GC。對Eden區進行GC,清除非存活對象,并且把尚且存活的對象移動到Survivor區。然后整理Survivor的兩個區。這種方式的GC是對年輕代的Eden區進行,不會影響到年老代。
因為大部分對象都是從Eden區開始的,同時Eden區不會分配的很大,所以Scavenge GC 會頻繁進行。
一般這里需要使用速度快,效率高的算法,使Eden區盡快空閑出來。
2 Full GC
對整個Heap進行整理,包括年輕代,年老代和持久代。Full GC因為需要對整個進行回收,所以比Scavenge GC要慢,因此應該盡可能減少Full GC的次數。在對jvm調優的過程中,很大一部分工作就是對Full GC的調節。導致Full GC是因為如下方法:
- 年老代的Tenured區被寫滿
- 持久代被寫滿
- System.gc()被顯式調用
- 上一次GC之后Heap的各區域分配策略動態變化
選擇合適的垃圾收集算法
1 串行收集器
用單線程處理所有垃圾回收工作,因為無需多線程交互,所以效率比較高。但是,無法使用多處理器的優勢,所以只適合單處理器機器。
也可以用在小數據量(100M)情況下的多處理器機器上。用-XX:+UseSerialGC打開
2 并行收集器
對年輕代進行并行垃圾回收,因此可以減少垃圾回收時間。一般在多線程多處理器上使用。使用-XX:+UseParallelGC打開。
并行收集器在J2SE5.0更新上引入,在java SE6.0中進行了增強,可以對年老代進行收集。如果年老代不使用并發收集的話,默認是使用單線程進行垃圾回收,因此會制約擴展能力。使用-XX:+UseParallelOldGC打開。
設置:
- 并行垃圾回收的線程數,使用-XX:ParallelGCThreads=<N>。此值可以設置與機器處理器數量相等。
- 最大垃圾回收暫停,指定垃圾回收時的最長暫停時間,通過-XX:MaxGCPauseMillis=<N>指定。N為毫秒,如果指定了此值,heap大小和垃圾回收相關參數會進行調整以達到指定值。設定此值會減少應用吞吐量。
- 吞吐量,為垃圾回收時間與非垃圾回收時間的比值,通過-XX:GCTimeRatio=<N>來設定,公式為1/(1+N)。例如,N=19時,表示5%的時間用于垃圾回收。默認值是99,即用1%的時間用于垃圾回收。
可以保證大部分工作都并發進行(應用不停止),垃圾回收只暫停很少的時間,此收集器適合對響應時間要求比較高的大中規模應用。
使用-XX:+UseConcMarkSweepGC打開。
并發收集器主要減少年老代的暫停時間,它在應用不停止的情況下使用獨立的垃圾回收線程,跟蹤可達對象。在每個年老代垃圾回收周期中,在收集出氣并發收起會對整個應用進行簡短的暫停,在收集中還會再暫停一次。第二次暫停時間比第一次稍長,在此過程中多個線程同時進行垃圾回收工作。
并發收集器使用處理器換來短暫的停頓時間。
在一個N個處理器的系統上,并發收集部分使用K/N個可用處理器進行回收,一般情況下1<=K<=N/4。即K小于N的四分之一。
在只有一個處理器的主機上使用并發收集器,設置為incremental mode模式也可以獲得較短的停頓時間。
浮動垃圾(Floating Garbage):
由于在應用運行的同時進行垃圾回收,所以有些垃圾可能在垃圾回收進行完成時產生,這樣就造成了“Floating Garbage”,這些垃圾需要在下次垃圾回收周期時才能回收掉。所有,并發收集器一般需要預留20%的空間用于浮動垃圾。
并發模式失敗(Concurrent Mode Failure):
并發收集器在應用運行時進行收集,所以需要保證heap在垃圾回收的這段時間有足夠的空間供程序使用,否則,垃圾回收還未完成,heap空間先滿了。這種情況下就會發生并發模式失敗,此時整個應用會暫停,進行垃圾回收。
為了保證有足夠內存供并發收集器使用,可以設置-XX:CMSInitiatingOccupancyFraction=<N>指定剩余多少heap時開始執行并發收集。
1 串行serial收集器,適用于數據量比較小(100M)的應用,或者單處理器下并且對響應時間無要求的應用。
缺點:只能用于小型應用
2 并行parallel收集器,適用于對吞吐量有高要求,多cpu,對應用相應時間無要求的大中型應用。如后臺處理、科學計算。
缺點:垃圾收集過程中應用響應時間可能加長
3 并發concurrent收集器:適用于對響應時間有高要求,多cpu的大中型應用。如web服務器、應用服務器、電信交換、集成開發環境。
P26……