原博客鏈接:https://www.cnblogs.com/wabi87547568/p/5282892.html
1.JVM的組成
JVM定義了控制Java代碼解釋執(zhí)行和具體實現(xiàn)的五種規(guī)格斋扰,因此把JVM分成了6個部分:JVM解釋器、指令系統(tǒng)、寄存器杂靶、棧、存儲區(qū)和碎片回收區(qū)酱鸭。
◆JVM解釋器:即這個虛擬機處理字段碼的CPU吗垮。
◆JVM指令系統(tǒng):該系統(tǒng)與計算機很相似,一條指令由操作碼和操作數(shù)兩部分組成凹髓。操作碼為8位二進制數(shù)烁登,主要是為了說明一條指令的功能,操作數(shù)可以根據(jù)需要而定蔚舀,JVM最多有256種不同的操作指令饵沧。目前已使用了160多種操作碼锨络。
◆寄存器:JVM有自己的虛擬寄存器,這樣就可以快速地與JVM的解釋器進行數(shù)據(jù)交換狼牺。為了功能的需要羡儿,JVM設置了4個常用的32位寄存器:pc(程序計數(shù)器)、optop(操作數(shù)棧頂指針)是钥、frame(當前執(zhí)行環(huán)境指針)和vars(指向當前執(zhí)行環(huán)境中第一個局部變量的指針)失受。
◆JVM棧:指令執(zhí)行時數(shù)據(jù)和信息存儲的場所和控制中心,它提供給JVM解釋器運算所需要的信息咏瑟。當JVM得到一個Java字節(jié)碼應用程序后拂到,便為該代碼中一個類的每一個方法創(chuàng)建一個棧框架码泞,以保存該方法的狀態(tài)信息兄旬。每個棧框架包括以下三類信息:局部變量余寥、執(zhí)行環(huán)境领铐、操作數(shù)棧。
局部變量用于存儲一個類的方法中所用到的局部變量宋舷。vars寄存器指向該變量表中的第一個局部變量绪撵。
執(zhí)行環(huán)境用于保存解釋器對Java字節(jié)碼進行解釋過程中所需的信息。它們是:上次調用的方法祝蝠、局部變量指針和操作數(shù)棧的棧頂和棧底指針音诈。執(zhí)行環(huán)境是一個執(zhí)行一個方法的控制中心。例如:如果解釋器要執(zhí)行iadd(整數(shù)加法)绎狭,首先要從frame寄存器中找到當前執(zhí)行環(huán)境细溅,而后便從執(zhí)行環(huán)境中找到操作數(shù)棧,從棧頂彈出兩個整數(shù)進行加法運算儡嘶,最后將結果壓入棧頂喇聊。
操作數(shù)棧用于存儲運算所需操作數(shù)及運算的結果。
◆存儲區(qū):JVM有兩類存儲區(qū):常量緩沖池和方法區(qū)蹦狂。常量緩沖池用于存儲類名稱誓篱、方法和字段名稱以及串常量。方法區(qū)則用于存儲Java方法的字節(jié)碼凯楔。
◆碎片回收區(qū):JVM碎片回收是指將使用過的Java類的具體實例從內存進行回收窜骄,這就使得開發(fā)人員免去了自己編程控制內存的麻煩和危險。隨著JVM的不斷升級啼辣,其碎片回收的技術和算法也更加合理啊研。JVM 1.4.1版后產(chǎn)生了一種叫分代收集技術,簡單來說就是利用對象在程序中生存的時間劃分成代,以此為標準進行碎片回收党远。
2.JAVA的垃圾回收機制 GC通過確定對象是否被活動對象引用來確定是否收集該對象削解。
2.1 觸發(fā)GC(Garbage Collector)的條件
1)GC在優(yōu)先級最低的線程中運行,一般在應用程序空閑即沒有應用線程在運行時被調用沟娱。但下面的條件例外氛驮。
2)Java堆內存不足時,GC會被調用济似。當應用線程在運行矫废,并在運行過程中創(chuàng)建新對象,若這時內存空間不足砰蠢,JVM就會強制調用GC線程蓖扑。若GC一次之后仍不能滿足內存分配,JVM會再進行兩次GC台舱,若仍無法滿足要求律杠,則JVM將報“out of memory”的錯誤,Java應用將停止竞惋。
2.2 兩個重要方法
2.2.1 System.gc()方法
使用System.gc()可以不管JVM使用的是哪一種垃圾回收的算法柜去,都可以請求Java的垃圾回收。在命令行中有一個參數(shù)-verbosegc可以查看Java使用的堆內存的情況拆宛,它的格式如下:java -verbosegc classfile 由于這種方法會影響系統(tǒng)性能嗓奢,不推薦使用,所以不詳訴浑厚。
2.2.2 finalize()方法
在JVM垃圾回收器收集一個對象之前股耽,一般要求程序調用適當?shù)姆椒ㄡ尫刨Y源,但在沒有明確釋放資源的情況下瞻颂,Java提供了缺省機制來終止該對象心釋放資源豺谈,這個方法就是finalize()郑象。它的原型為:protected void finalize() throws Throwable 在finalize()方法返回之后贡这,對象消失,垃圾收集開始執(zhí)行厂榛。原型中的throws Throwable表示它可以拋出任何類型的異常盖矫。
之所以要使用finalize(),是存在著垃圾回收器不能處理的特殊情況击奶。例如:1)由于在分配內存的時候可能采用了類似 C語言的做法辈双,而非JAVA的通常new做法。這種情況主要發(fā)生在native method中柜砾,比如native method調用了C/C++方法malloc()函數(shù)系列來分配存儲空間湃望,但是除非調用free()函數(shù),否則這些內存空間將不會得到釋放,那么這個時候就可能造成內存泄漏证芭。但是由于free()方法是在C/C++中的函數(shù)瞳浦,所以finalize()中可以用本地方法來調用它。以釋放這些“特殊”的內存空間废士。2)又或者打開的文件資源叫潦,這些資源不屬于垃圾回收器的回收范圍。
2.3 減少GC開銷的措施
1)不要顯式調用System.gc()官硝。此函數(shù)建議JVM進行主GC,雖然只是建議而非一定,但很多情況下它會觸發(fā)主GC,從而增加主GC的頻率,也即增加了間歇性停頓的次數(shù)矗蕊。大大的影響系統(tǒng)性能。
2)盡量減少臨時對象的使用氢架。臨時對象在跳出函數(shù)調用后,會成為垃圾,少用臨時變量就相當于減少了垃圾的產(chǎn)生,從而延長了出現(xiàn)上述第二個觸發(fā)條件出現(xiàn)的時間,減少了主GC的機會傻咖。
3)對象不用時最好顯式置為Null。一般而言,為Null的對象都會被作為垃圾處理,所以將不用的對象顯式地設為Null,有利于GC收集器判定垃圾,從而提高了GC的效率岖研。
4)盡量使用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)生中間對象缎玫。
5)能用基本類型如Int,Long,就不用Integer,Long對象硬纤。基本類型變量占用的內存資源比相應對象占用的少得多,如果沒有必要,最好使用基本變量赃磨。
6)盡量少用靜態(tài)對象變量筝家。靜態(tài)變量屬于全局變量,不會被GC回收,它們會一直占用內存。
7)分散對象創(chuàng)建或刪除的時間邻辉。集中在短時間內大量創(chuàng)建新對象,特別是大對象,會導致突然需要大量內存,JVM在面臨這種情況時,只能進行主GC,以回收內存或整合內存碎片,從而增加主GC的頻率溪王。集中刪除對象,道理也是一樣的。它使得突然出現(xiàn)了大量的垃圾對象,空閑空間必然減少,從而大大增加了下一次創(chuàng)建新對象時強制主GC的機會值骇。
2.4 對象在JVM堆區(qū)的狀態(tài)
1)可觸及狀態(tài):程序中還有變量引用莹菱,那么此對象為可觸及狀態(tài)。
2)可復活狀態(tài):當程序中已經(jīng)沒有變量引用這個對象吱瘩,那么此對象由可觸及狀態(tài)轉為可復活狀態(tài)监徘。CG線程將在一定的時間準備調用此對象的finalize方法(finalize方法繼承或重寫子Object),finalize方法內的代碼有可能將對象轉為可觸及狀態(tài)胁艰,否則對象轉化為不可觸及狀態(tài)涩澡。
3)不可觸及狀態(tài):只有當對象處于不可觸及狀態(tài)時,GC線程才能回收此對象的內存票摇。
[圖片上傳失敗...(image-125b4-1539142222224)]
Jvm堆區(qū)對象狀態(tài)轉換圖
2.5 常用垃圾收集器
1) 標記-清除收集器 Mark-Sweep
2) 復制收集器 Copying
3) 標記-壓縮收集器 Mark-Compact
4) 分代收集器 Generational
2.6 垃圾收集算法介紹
2.6.1 tracing算法
基于tracing算法的垃圾收集也稱為標記和清除(mark-and-sweep)垃圾收集器.
這是最基礎的垃圾回收算法拘鞋,之所以說它是最基礎的是因為它最容易實現(xiàn),思想也是最簡單的矢门。標記-清除算法分為兩個階段:標記階段和清除階段盆色。標記階段的任務是標記出所有需要被回收的對象灰蛙,清除階段就是回收被標記的對象所占用的空間。具體過程如下圖所示:
從圖中可以很容易看出標記-清除算法實現(xiàn)起來比較容易隔躲,但是有一個比較嚴重的問題就是容易產(chǎn)生內存碎片缕允,碎片太多可能會導致后續(xù)過程中需要為大對象分配空間時無法找到足夠的空間而提前觸發(fā)新的一次垃圾收集動作。
2.6.2 Copying算法
為了解決Mark-Sweep算法的缺陷蹭越,Copying算法就被提了出來障本。它將可用內存按容量劃分為大小相等的兩塊,每次只使用其中的一塊响鹃。當這一塊的內存用完了驾霜,就將還存活著的對象復制到另外一塊上面买置,然后再把已使用的內存空間一次清理掉粪糙,這樣一來就不容易出現(xiàn)內存碎片的問題。具體過程如下圖所示:
這種算法雖然實現(xiàn)簡單忿项,運行高效且不容易產(chǎn)生內存碎片,但是卻對內存空間的使用做出了高昂的代價,因為能夠使用的內存縮減到原來的一半惨好。很顯然日川,Copying算法的效率跟存活對象的數(shù)目多少有很大的關系龄句,如果存活對象很多,那么Copying算法的效率將會大大降低繁调。
2.6.3 compacting算法
為了解決Copying算法的缺陷奕翔,充分利用內存空間派继,提出了Mark-Compact算法绅络。該算法標記階段和Mark-Sweep一樣恩急,但是在完成標記之后匾荆,它不是直接清理可回收對象简卧,而是將存活對象都向一端移動铜涉,然后清理掉端邊界以外的內存盖彭。具體過程如下圖所示:
2.6.4 Generation算法
分代收集算法是目前大部分JVM的垃圾收集器采用的算法幻林。它的核心思想是根據(jù)對象存活的生命周期將內存劃分為若干個不同的區(qū)域沪饺。一般情況下將堆區(qū)劃分為老年代(Tenured Generation)和新生代(Young Generation)肝谭,老年代的特點是每次垃圾收集時只有少量對象需要被回收鼠次,而新生代的特點是每次垃圾回收時都有大量的對象需要被回收,那么就可以根據(jù)不同代的特點采取最適合的收集算法赦役。
目前大部分垃圾收集器對于新生代都采取Copying算法赢赊,因為新生代中每次垃圾回收都要回收大部分對象叭披,也就是說需要復制的操作次數(shù)較少锋边,但是實際中并不是按照1:1的比例來劃分新生代的空間的皱坛,一般來說是將新生代劃分為一塊較大的Eden空間和兩塊較小的Survivor空間,每次使用Eden空間和其中的一塊Survivor空間豆巨,當進行回收時剩辟,將Eden和Survivor中還存活的對象復制到另一塊Survivor空間中,然后清理掉Eden和剛才使用過的Survivor空間往扔。
而由于老年代的特點是每次回收都只回收少量對象贩猎,一般使用的是Mark-Compact算法。
新年代:新創(chuàng)建的對象都存放在這里萍膛。因為大多數(shù)對象很快變得不可達吭服,所以大多數(shù)對象在年輕代中創(chuàng)建,然后消失蝗罗。當對象從這塊內存區(qū)域消失時艇棕,我們說發(fā)生了一次“minor GC”。
老年代:沒有變得不可達串塑,存活下來的年輕代對象被復制到這里沼琉。這塊內存區(qū)域一般大于年輕代。因為它更大的規(guī)模桩匪,GC發(fā)生的次數(shù)比在年輕代的少打瘪。對象從老年代消失時,我們說“major GC”(或“full GC”)發(fā)生了傻昙。
上圖中的永久代(permanent generation)也稱為“方法區(qū)(method area)”闺骚,他存儲class對象和字符串常量。所以這塊內存區(qū)域絕對不是永久的存放從老年代存活下來的對象的妆档。在這塊內存中有可能發(fā)生垃圾回收僻爽。發(fā)生在這里垃圾回收也被稱為major GC。
對于分代算法过吻,我推薦一篇博客給大家:http://blog.jobbole.com/80499/