https://blog.csdn.net/jisuanjiguoba/article/details/80156781
圖解JVM GC過程--很好
http://www.itdecent.cn/p/314272e6d35b
java內(nèi)存結(jié)構(gòu)圖

Java堆
堆內(nèi)存用于存放由new創(chuàng)建的對象和數(shù)組。在堆中分配的內(nèi)存,由java虛擬機自動垃圾回收器來管理。在堆中產(chǎn)生了一個數(shù)組或者對象后,還可以在棧中定義一個特殊的變量,這個變量的取值等于數(shù)組或者對象在堆內(nèi)存中的首地址,在棧中的這個特殊的變量就變成了數(shù)組或者對象的引用變量,以后就可以在程序中使用棧內(nèi)存中的引用變量來訪問堆中的數(shù)組或者對象,引用變量相當于為數(shù)組或者對象起的一個別名,或者代號。根據(jù)垃圾回收機制的不同,Java堆有可能擁有不同的結(jié)構(gòu),最為常見的就是將整個Java堆分為新生代和老年代。其中新聲帶存放新生的對象或者年齡不大的對象,老年代則存放老年對象。新生代分為den區(qū)、s0區(qū)、s1區(qū),s0和s1也被稱為from和to區(qū)域,他們是兩塊大小相等并且可以互相角色的空間。絕大多數(shù)情況下,對象首先分配在eden區(qū),在新生代回收后,如果對象還存活,則進入s0或s1區(qū),之后每經(jīng)過一次新生代回收,如果對象存活則它的年齡就加1,對象達到一定的年齡后,則進入老年代。
Java棧
Java棧是一塊線程私有的空間,一個棧,一般由三部分組成:局部變量表、操作數(shù)據(jù)棧和幀數(shù)據(jù)區(qū)局部變量表:用于報錯函數(shù)的參數(shù)及局部變量
操作數(shù)據(jù)棧:主要保存計算過程的中間結(jié)果,同時作為計算過程中的變量臨時的存儲空間。
幀數(shù)據(jù)區(qū):除了局部變量表和操作數(shù)據(jù)棧以外,棧還需要一些數(shù)據(jù)來支持常量池的解析,這里幀數(shù)據(jù)區(qū)保存著訪問常量池的指針,方便程序訪問常量池,另外當函數(shù)返回或出現(xiàn)異常時虛擬機必須有一個異常處理表,方便發(fā)送異常的時候找到異常的代碼,因此異常處理表也是幀數(shù)據(jù)區(qū)的一部分。
Java方法區(qū)
Java方法區(qū)和堆一樣,方法區(qū)是一塊所有線程共享的內(nèi)存區(qū)域,他保存系統(tǒng)的類信息。比如類的字段、方法、常量池等。方法區(qū)的大小決定系統(tǒng)可以保存多少個類。如果系統(tǒng)定義太多的類,導致方法區(qū)溢出。虛擬機同樣會拋出內(nèi)存溢出的錯誤。方法區(qū)可以理解為永久區(qū)。
堆區(qū):
提供所有類實例和數(shù)組對象存儲區(qū)域
jvm只有一個堆區(qū)(heap)被所有線程共享,堆中不存放基本類型和對象引用,只存放對象本身
棧區(qū):
每個線程包含一個棧區(qū),棧中只保存基礎(chǔ)數(shù)據(jù)類型的對象和自定義對象的引用(不是對象),對象都存放在堆區(qū)中
每個棧中的數(shù)據(jù)(原始類型和對象引用)都是私有的,其他棧不能訪問。
方法區(qū):
又叫靜態(tài)區(qū),跟堆一樣,被所有的線程共享。方法區(qū)包含所有的class和static變量。
方法區(qū)中包含的都是在整個程序中永遠唯一的元素,如class,static變量。
運行時常量池都分配在 Java 虛擬機的方法區(qū)之中
堆的參數(shù)配置
-XX:+PrintGC?????每次觸發(fā)GC的時候打印相關(guān)日志
-XX:+UseSerialGC???? ?串行回收
-XX:+PrintGCDetails? 更詳細的GC日志
-Xms??????????????堆初始值
-Xmx??????????????堆最大可用值
-Xmn??????????????新生代堆最大可用值
-XX:SurvivorRatio??? ?用來設(shè)置新生代中eden空間和from/to空間的比例.
JVM參數(shù)調(diào)優(yōu)
我們希望達到一些目標:
GC的時間足夠的小
GC的次數(shù)足夠的少
發(fā)生Full GC的周期足夠的長
?(1)針對JVM堆的設(shè)置,一般可以通過-Xms -Xmx限定其最小、最大值,堆內(nèi)存初始值與最大值保持一致,減少垃圾回收次數(shù)。
(2)年輕代和年老代將根據(jù)默認的比例分配堆內(nèi)存,設(shè)置新生代和老年代的回收比例,新生代:老年代=1:3或者1:4。
(3)線程堆棧的設(shè)置:每個線程默認會開啟1M的堆棧,用于存放棧幀、調(diào)用參數(shù)、局部變量等,對大多數(shù)應(yīng)用而言這個默認值太大了,一般256K就足用。理論上,在內(nèi)存不變的情況下,減少每個線程的堆棧,可以產(chǎn)生更多的線程,但這實際上還受限于操作系統(tǒng)。
(4) 盡可能將對象預(yù)留在新生代,減少老年代的GC次數(shù)。除了可以設(shè)置新生代的絕對大小(-Xmn),可以使用(-XX:NewRatio)設(shè)置新生代和老年代的比例:-XX:NewRatio=老年代/新生代
(5)垃圾收集器的選用
jdk1.5以前是串行收集器適用于小數(shù)據(jù)量,目前吞吐量優(yōu)先選用并行收集器,響應(yīng)時間優(yōu)先選用并發(fā)收集器,還有一種G1回收器,有取代前面幾種回收期的趨勢
內(nèi)存溢出和內(nèi)存泄漏區(qū)別
1、內(nèi)存溢出:(Out Of Memory---OOM)
系統(tǒng)已經(jīng)不能再分配出你所需要的空間,比如你需要100M的空間,系統(tǒng)只剩90M了,這就叫內(nèi)存溢出
2、內(nèi)存泄漏:? (Memory Leak)----》強引用所指向的對象不會被回收,可能導致內(nèi)存泄漏,虛擬機寧愿拋出OOM也不會去回收他指向的對象
意思就是你用資源的時候為他開辟了一段空間,當你用完時忘記釋放資源了,這時內(nèi)存還被占用著,這就是內(nèi)存泄漏。一次沒關(guān)系,但是內(nèi)存泄漏次數(shù)多了就會導致內(nèi)存溢出
手動GC回收
public class JVMDemo05 {
????? public static void main(String[] args) {
?????????? JVMDemo05jvmDemo05 = new JVMDemo05();
?????????? //jvmDemo05 = null;
?????????? System.gc();}
????? protected void finalize() throws Throwable {
?????? System.out.println("gc在回收對象...");}}
finalize作用:Java技術(shù)使用finalize()方法在垃圾收集器將對象從內(nèi)存中清除出去前,做必要的清理工作。GC回收之前調(diào)用。
?
垃圾回收機制算法
1、引用計數(shù)法
1.1概述
給對象中添加一個引用計數(shù)器,每當有一個地方引用它時,計數(shù)器值就加1;當引用失效時,計數(shù)器值就減1;任何時刻計數(shù)器都為0的對象就是不再被使用的,垃圾收集器將回收該對象使用的內(nèi)存。
1.2優(yōu)缺點
優(yōu)點:引用計數(shù)收集器可以很快的執(zhí)行,交織在程序運行中。對程序需要不被長時間打斷的實時環(huán)境比較有利。
缺點:無法檢測出循環(huán)引用。如父對象有一個對子對象的引用,子對象反過來引用父對象。這樣,他們的引用計數(shù)永遠不可能為0.而且每次加減非常浪費內(nèi)存。
2、復制算法(空間換時間)
S0和s1將可用內(nèi)存按容量分成大小相等的兩塊,每次只使用其中一塊,當這塊內(nèi)存使用完了,就將還存活的對象復制到另一塊內(nèi)存上去,然后把使用過的內(nèi)存空間一次清理掉。這樣使得每次都是對其中一塊內(nèi)存進行回收,內(nèi)存分配時不用考慮內(nèi)存碎片等復雜情況,只需要移動堆頂指針,按順序分配內(nèi)存即可,實現(xiàn)簡單,運行高效。
復制算法的缺點顯而易見,可使用的內(nèi)存降為原來一半。
復制算法用于在新生代垃圾回收
3、標記清除算法
標記-清除(Mark-Sweep)算法顧名思義,主要就是兩個動作,一個是標記,另一個就是清除。
標記就是根據(jù)特定的算法(如:引用計數(shù)算法,可達性分析算法等)標出內(nèi)存中哪些對象可以回收,哪些對象還要繼續(xù)用。標記指示回收,那就直接收掉;標記指示對象還能用,那就原地不動留下。
缺點
1.? 標記與清除沒有連續(xù)性效率低;
2.? 清除之后內(nèi)存會產(chǎn)生大量碎片;
4、標記-壓縮算法(時間換空間)
標記壓縮法在標記清除基礎(chǔ)之上做了優(yōu)化,把存活的對象壓縮到內(nèi)存一端,而后進行垃圾清理。(java中老年代使用的就是標記壓縮法)
5、分代收集算法
根據(jù)內(nèi)存中對象的存活周期不同,將內(nèi)存劃分為幾塊,java的虛擬機中一般把內(nèi)存劃分為新生代和年老代,當新創(chuàng)建對象時一般在新生代中分配內(nèi)存空間,當新生代垃圾收集器回收幾次之后仍然存活的對象會被移動到年老代內(nèi)存中,當大對象在新生代中無法找到足夠的連續(xù)內(nèi)存時也直接在年老代中創(chuàng)建。
對于新生代和老年代來說,新生代回收頻率很高,但是每次回收耗時很短,而老年代回收頻率較低,但是耗時會相對較長,所以應(yīng)該盡量減少老年代的GC.
垃圾回收時的停頓現(xiàn)象
停頓的目的是為了終止所有的應(yīng)用線程,只有這樣的系統(tǒng)才不會有新垃圾的產(chǎn)生。同時停頓保證了系統(tǒng)狀態(tài)在某一個瞬間的一致性,也有利于更好的標記垃圾對象。
什么是Java垃圾回收器
Java垃圾回收器是Java虛擬機的三個重要模塊(另外兩個是解釋器和多線程機制)之一,為應(yīng)用程序提供內(nèi)存的自動分配、自動回收功能,這兩個操作都發(fā)生在Java堆上。某一個時點,一個對象如果有一個以上的引用指向它,那么該對象就為活著的,否則死亡,視為垃圾,可被垃圾回收器回收再利用。垃圾回收操作需要消耗CPU、線程、時間等資源,所以容易理解的是垃圾回收操作不是實時的發(fā)生,當內(nèi)存消耗完或者是達到某一個指標時,觸發(fā)垃圾回收操作。有一個對象死亡的例外,java.lang.Thread類型的對象即使沒有引用,只要線程還在運行,就不會被回收。
1、串行回收器(Serial Collector)
單線程執(zhí)行回收操作,回收期間暫停所有應(yīng)用線程的執(zhí)行,client模式下的默認回收器,通過-XX:+UseSerialGC命令行可選項強制指定。參數(shù)可以設(shè)置使用新生代串行和老年代串行回收器
年輕代的回收算法(MinorCollection)
把Eden區(qū)的存活對象移到To區(qū),To區(qū)裝不下直接移到年老代,把From區(qū)的移到To區(qū),To區(qū)裝不下直接移到年老代,F(xiàn)rom區(qū)里面年齡很大的升級到年老代。回收結(jié)束之后,Eden和From區(qū)都為空,此時把From和To的功能互換,F(xiàn)rom變To,To變From,每一輪回收之前To都是空的。設(shè)計的選型為復制。
年老代的回收算法(FullCollection)
年老代的回收分為三個步驟,標記(Mark)、清除(Sweep)、合并(Compact)。標記階段把所有存活的對象標記出來,清除階段釋放所有死亡的對象,合并階段 把所有活著的對象合并到老年代的前面,把空閑的片段都留到后面。設(shè)計的選型為合并,減少內(nèi)存的碎片。
2、并行回收器(ParNew回收器)
并行回收器在串行回收器基礎(chǔ)上做了改進,他可以使用多個線程同時進行垃圾回收,對于計算能力強的計算機而言,可以有效的縮短垃圾回收所需的尖際時間。
并行回收器是一個工作在新生代的垃圾收集器,他只是簡單的將串行回收器多線程快他的回收策略和算法和串行回收器一樣。使用XX:+UseParNewGC?新生代ParNew回收器,老年代則使用串行回收器
并行回收器工作時的線程數(shù)量可以使用XX:ParaleiGCThreads參數(shù)指定,一般最好和計算機的CPU相當,避免過多的栽程影響性能。
3、并CMS(并發(fā)GC)收集器
CMS(Concurrent Mark Sweep)收集器是一種以獲取最短回收停頓時間為目標的收集器。CMS收集器是基于“標記-清除”算法實現(xiàn)的,整個收集過程大致分為4個步驟:
①.初始標記(CMS initial mark)
②.并發(fā)標記(CMS concurrenr mark)
③.重新標記(CMS remark)
④.并發(fā)清除(CMS concurrent sweep)
4、G1回收器
G1回收器(Garbage-First)實在]dk1.7中提出的垃圾回收器,從長期目標來看是為了取代CMS回收器,G1回收器擁有獨特的垃圾回收策略,G1屬于分代垃圾回收器,區(qū)分新生代和老年代,依然有eden和from/to區(qū),它并不要求整個eden區(qū)或者新生代、老年代的空間都連續(xù),它使用了分區(qū)算法。