java垃圾回收机制优化_JVM性能优化--Java的垃圾回收机制
一、Java內存結構
1、Java堆(Java Heap)
java堆是java虛擬機所管理的內存中最大的一塊,是被所有線程共享的一塊內存區域,在虛擬機啟動時創建。此內存區域的唯一目的就是存放對象實例,這一點在Java虛擬機規范中的描述是:所有的對象實例以及數組都要在堆上分配。
java堆是垃圾收集器管理的主要區域,因此也被成為“GC堆”(Garbage Collected Heap)。從內存回收角度來看java堆可分為:新生代和老生代。從內存分配的角度看,線程共享的Java堆中可能劃分出多個線程私有的分配緩沖區(Thread Local Allocation Buffer,TLAB)。無論怎么劃分,都與存放內容無關,無論哪個區域,存儲的都是對象實例,進一步的劃分都是為了更好的回收內存,或者更快的分配內存。
根據Java虛擬機規范的規定,java堆可以處于物理上不連續的內存空間中。當前主流的虛擬機都是可擴展的(通過 -Xmx 和 -Xms 控制)。如果堆中沒有內存完成實例分配,并且堆也無法再擴展時,將會拋出OutOfMemoryError異常。
2、Java虛擬機棧(Java?Virtual?Machine?Stacks)
java虛擬機也是線程私有的,它的生命周期和線程相同。虛擬機棧描述的是Java方法執行的內存模型:每個方法在執行的同時都會創建一個棧幀(Stack Frame)用于存儲局部變量表、操作數棧、動態鏈接、方法出口等信息。
咱們常說的堆內存、棧內存中,棧內存指的就是虛擬機棧。局部變量表存放了編譯期可知的各種基本數據類型(8個基本數據類型)、對象引用(地址指針)、returnAddress類型。
局部變量表所需的內存空間在編譯期間完成分配。在運行期間不會改變局部變量表的大小。
這個區域規定了兩種異常狀態:如果線程請求的棧深度大于虛擬機所允許的深度,則拋出StackOverflowError異常;如果虛擬機棧可以動態擴展,在擴展是無法申請到足夠的內存,就會拋出OutOfMemoryError異常。
3、本地方法棧(Native Method Stack)
本地方法棧與虛擬機棧所發揮作用非常相似,它們之間的區別不過是虛擬機棧為虛擬機執行Java方法(也就是字節碼)服務,而本地方法棧則為虛擬機使用到的native方法服務。本地方法棧也是拋出兩個異常。
4、方法區(Method Area)
方法區與java堆一樣,是各個線程共享的內存區域,它用于存儲已被虛擬機加載的類信息、常量、靜態變量、即時編譯器編譯后的代碼等數據。它有個別命叫Non-Heap(非堆)。當方法區無法滿足內存分配需求時,拋出OutOfMemoryError異常。
5、直接內存(Direct?Memory)
直接內存不是虛擬機運行時數據區的一部分,也不是java虛擬機規范中定義的內存區域。但這部分區域也唄頻繁使用,而且也可能導致OutOfMemoryError異常
在JDK1.4中新加入的NIO(New Input/Output)類,引入了一種基于通道(Channel)與緩沖區(Buffer)的I/O方式,它可以使用Native函數庫直接分配堆外內存,然后通過一個存儲在java堆中的DirectByteBuffer對象作為這塊內存的引用進行操作。
6、運行時常量池(Runtime Constant Pool)
運行時常量池是方法區的一部分。Class文件中除了有類的版本、字段、方法、接口等描述信息外,還有一項信息是常量池,用于存放編譯期生成的各種字面量和符號引用,這部分內容將在加載后進入方法區的運行時常量池中存放。
## 7、程序計數器(Program Counter Register)
程序計數器是一塊較小的內存空間,它可以看作是當前線程所執行的字節碼的行號指示器。
由于Java虛擬機的多線程是通過線程輪流切換并分配處理器執行時間的方式來實現的,一個處理器都只會執行一條線程中的指令。因此,為了線程切換后能恢復到正確的執行位置,每條線程都有一個獨立的程序計數器,各個線程之間計數器互不影響,獨立存儲。稱之為“線程私有”的內存。程序計數器內存區域是虛擬機中唯一沒有規定OutOfMemoryError情況的區域。
8、執行引擎
虛擬機核心的組件就是執行引擎,它負責執行虛擬機的字節碼,一般戶先進行編譯成機器碼后執行。
9、垃圾收集系統
垃圾收集系統是Java的核心,也是不可少的,Java有一套自己進行垃圾清理的機制,開發人員無需手工清理
二、垃圾回收機制算法分析
1、什么是垃圾回收機制
不定時去堆內存中清理不可達對象。不可達的對象并不會馬上就會直接回收, 垃圾收集器在一個Java程序中的執行是自動的,不能強制執行,即使程序員能明確地判斷出有一塊內存已經無用了,是應該回收的,程序員也不能強制垃圾收集器回收該內存塊。程序員唯一能做的就是通過調用System.gc 方法來"建議"執行垃圾收集器,但其是否可以執行,什么時候執行卻都是不可知的。這也是垃圾收集器的最主要的缺點。
public class Test {
public static void main(String[] args) {
Test test = new Test();
test = null;
System.gc(); // 手動回收垃圾
}
@Override
protected void finalize() throws Throwable {
// gc回收垃圾之前調用
System.out.println("垃圾回收機制...");
}
}
2、finalize方法作用
Java技術使用finalize()方法在垃圾收集器將對象從內存中清除出去前,做必要的清理工作。這個方法是由垃圾收集器在確定這個對象沒有被引用時對這個對象調用的。它是在Object類中定義的,因此所有的類都繼承了它。子類覆蓋finalize()方法以整理系統資源或者執行其他清理工作。finalize()方法是在垃圾收集器刪除對象之前對這個對象調用的。
3、新生代與老年代
Java 中的堆是 JVM 所管理的最大的一塊內存空間,主要用于存放各種類的實例對象。
在 Java 中,堆被劃分成兩個不同的區域:新生代 ( Young )、老年代 ( Old )。新生代 ( Young ) 又被劃分為三個區域:Eden、From Survivor、To Survivor。
這樣劃分的目的是為了使 JVM 能夠更好的管理堆內存中的對象,包括內存的分配以及回收。
堆的內存模型大致為:
默認的,新生代 ( Young ) 與老年代 ( Old ) 的比例的值為?1:2?( 該值可以通過參數 –XX:NewRatio 來指定 ),即:新生代 ( Young ) = 1/3 的堆空間大小。老年代 ( Old ) = 2/3 的堆空間大小。其中,新生代 ( Young ) 被細分為 Eden 和 兩個 Survivor 區域,這兩個 Survivor 區域分別被命名為 from 和 to,以示區分。
默認的,Eden : from : to = 8 : 1 : 1?( 可以通過參數 –XX:SurvivorRatio 來設定 ),即: Eden = 8/10 的新生代空間大小,from = to = 1/10 的新生代空間大小。
根據垃圾回收機制的不同,Java堆有可能擁有不同的結構,最為常見的就是將整個Java堆分為
新生代和老年代。其中新生帶存放新生的對象或者年齡不大的對象,老年代則存放老年對象。
新生代分為den區、s0區、s1區,s0和s1也被稱為from和to區域,他們是兩塊大小相等并且可以互相角色的空間。
絕大多數情況下,對象首先分配在eden區,在新生代回收后,如果對象還存活,則進入s0或s1區,之后每經過一次
新生代回收,如果對象存活則它的年齡就加1,對象達到一定的年齡后,則進入老年代。
三、如何判斷對象是否存活
1、引用計數法
概念
引用計數法就是如果一個對象沒有被任何引用指向,則可視之為垃圾。這種方法的缺點就是不能檢測到環的存在。
首先需要聲明,至少主流的Java虛擬機里面都沒有選用引用計數算法來管理內存。
什么是引用計數算法:給對象中添加一個引用計數器,每當有一個地方引用它時,計數器值加1;當引用失效時,計數器值減1.任何時刻計數器值為0的對象就是不可能再被使用的。那為什么主流的Java虛擬機里面都沒有選用這種算法呢?其中最主要的原因是它很難解決對象之間相互循環引用的問題。
2、根搜索算法
概念
根搜索算法的基本思路就是通過一系列名為”GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。
這個算法的基本思想是通過一系列稱為“GC Roots”的對象作為起始點,從這些節點向下搜索,搜索所走過的路徑稱為引用鏈,當一個對象到GC Roots沒有任何引用鏈(即GC Roots到對象不可達)時,則證明此對象是不可用的。
那么問題又來了,如何選取GCRoots對象呢?在Java語言中,可以作為GCRoots的對象包括下面幾種:
(1). 虛擬機棧(棧幀中的局部變量區,也叫做局部變量表)中引用的對象。
(2). 方法區中的類靜態屬性引用的對象。
(3). 方法區中常量引用的對象。
(4). 本地方法棧中JNI(Native方法)引用的對象。
下面給出一個GCRoots的例子,如下圖,為GCRoots的引用鏈。
user3和user5沒有引用鏈
根搜索算法的基本思路就是通過一系列名為”GC Roots”的對象作為起始點,從這些節點開始向下搜索,搜索所走過的路徑稱為引用鏈(Reference Chain),當一個對象到GC Roots沒有任何引用鏈相連時,則證明此對象是不可用的。
四、垃圾回收機制策略
1、標記清除算法
概念
該算法有兩個階段。
標記階段:找到所有可訪問的對象,做個標記
清除階段:遍歷堆,把未被標記的對象回收
應用場景
該算法一般應用于老年代,因為老年代的對象生命周期比較長。
優缺點
標記清除算法的優點和缺點
1.優點
是可以解決循環引用的問題
必要時才回收(內存不足時)
2.缺點:
回收時,應用需要掛起,也就是stop the world。
標記和清除的效率不高,尤其是要掃描的對象比較多的時候
會造成內存碎片(會導致明明有內存空間,但是由于不連續,申請稍微大一些的對象無法做到),
2、復制算法
概念
如果jvm使用了coping算法,一開始就會將可用內存分為兩塊,from域和to域, 每次只是使用from域,to域則空閑著。當from域內存不夠了,開始執行GC操作,這個時候,會把from域存活的對象拷貝到to域,然后直接把from域進行內存清理。
應用場景
coping算法一般是使用在新生代中,因為新生代中的對象一般都是朝生夕死的,存活對象的數量并不多,這樣使用coping算法進行拷貝時效率比較高。jvm將Heap 內存劃分為新生代與老年代,又將新生代劃分為Eden(伊甸園) 與2塊Survivor Space(幸存者區) ,然后在Eden –>Survivor Space 以及From Survivor Space 與To Survivor Space 之間實行Copying 算法。 不過jvm在應用coping算法時,并不是把內存按照1:1來劃分的,這樣太浪費內存空間了。一般的jvm都是8:1。也即是說,Eden區:From區:To區域的比例是
始終有90%的空間是可以用來創建對象的,而剩下的10%用來存放回收后存活的對象。
1、當Eden區滿的時候,會觸發第一次young gc,把還活著的對象拷貝到Survivor From區;當Eden區再次觸發young gc的時候,會掃描Eden區和From區域,對兩個區域進行垃圾回收,經過這次回收后還存活的對象,則直接復制到To區域,并將Eden和From區域清空。
2、當后續Eden又發生young gc的時候,會對Eden和To區域進行垃圾回收,存活的對象復制到From區域,并將Eden和To區域清空。
3、可見部分對象會在From和To區域中復制來復制去,如此交換15次(由JVM參數MaxTenuringThreshold決定,這個參數默認是15),最終如果還是存活,就存入到老年代
注意: 萬一存活對象數量比較多,那么To域的內存可能不夠存放,這個時候會借助老年代的空間。
優缺點
優點:在存活對象不多的情況下,性能高,能解決內存碎片和java垃圾回收算法之-標記清除 中導致的引用更新問題。
缺點: 會造成一部分的內存浪費。不過可以根據實際情況,將內存塊大小比例適當調整;如果存活對象的數量比較大,coping的性能會變得很差。
3、標記壓縮算法
標記清除算法和標記壓縮算法非常相同,但是標記壓縮算法在標記清除算法之上解決內存碎片化
概念
壓縮算法簡單介紹
任意順序 : 即不考慮原先對象的排列順序,也不考慮對象之間的引用關系,隨意移動對象;
線性順序 : 考慮對象的引用關系,例如a對象引用了b對象,則盡可能將a和b移動到一塊;
滑動順序 : 按照對象原來在堆中的順序滑動到堆的一端。
優缺點
優點:解決內存碎片問題,
缺點:壓縮階段,由于移動了可用對象,需要去更新引用。
4、Minor GC和Full GC區別
概念:
新生代?GC(Minor?GC):指發生在新生代的垃圾收集動作,因為?Java?對象大多都具
備朝生夕滅的特性,所以?Minor?GC?非常頻繁,一般回收速度也比較快。
老年代?GC(Major?GC??/?Full?GC):指發生在老年代的?GC,出現了?Major?GC,經常
會伴隨至少一次的?Minor?GC(但非絕對的,在?ParallelScavenge?收集器的收集策略里
就有直接進行?Major?GC?的策略選擇過程)?。MajorGC?的速度一般會比?Minor?GC?慢?10
倍以上。
Minor?GC觸發機制:
當年輕代滿時就會觸發Minor?GC,這里的年輕代滿指的是Eden代滿,Survivor滿不會引發GCFull?GC觸發機制:
當年老代滿時會引發Full?GC,Full?GC將會同時回收年輕代、年老代,當永久代滿時也會引發Full?GC,會導致Class、Method元信息的卸載其中
Minor?GC如下圖所示
虛擬機給每個對象定義了一個對象年齡(Age)計數器。如果對象在?Eden?出生并經過第一次?Minor?GC?后仍然存活,并且能被?Survivor?容納的話,將被移動到?Survivor?空間中,并將對象年齡設為?1。對象在?Survivor?區中每熬過一次?Minor?GC,年齡就增加?1?歲,當它的年齡增加到一定程度(默認為?15?歲)時,就會被晉升到老年代中。對象晉升老年代的年齡閾值,可以通過參數?-XX:MaxTenuringThreshold?(閾值)來設置。
JVM的永久代中會發生垃圾回收么?
垃圾回收不會發生在永久代,如果永久代滿了或者是超過了臨界值,會觸發完全垃圾回收(Full GC)。如果你仔細查看垃圾收集器的輸出信息,就會發現永久代也是被回收的。這就是為什么正確的永久代大小對避免Full GC是非常重要的原因。請參考下Java8:從永久代到元數據區
(注:Java8中已經移除了永久代,新加了一個叫做元數據區的native內存區)
5、分代算法
概述
這種算法,根據對象的存活周期的不同將內存劃分成幾塊,新生代和老年代,這樣就可以根據各個年代的特點采用最適當的收集算法。可以用抓重點的思路來理解這個算法。
新生代對象朝生夕死,對象數量多,只要重點掃描這個區域,那么就可以大大提高垃圾收集的效率。另外老年代對象存儲久,無需經常掃描老年代,避免掃描導致的開銷。
新生代
在新生代,每次垃圾收集器都發現有大批對象死去,只有少量存活,采用復制算法,只需要付出少量存活對象的復制成本就可以完成收集;
老年代
而老年代中因為對象存活率高、沒有額外空間對它進行分配擔保,就必須“標記-清除-壓縮”算法進行回收。參看java垃圾回收算法之-標記_清除壓縮
新創建的對象被分配在新生代,如果對象經過幾次回收后仍然存活,那么就把這個對象劃分到老年代。
老年代區存放Young區Survivor滿后觸發minor GC后仍然存活的對象,當Eden區滿后會將存活的對象放入Survivor區域,如果Survivor區存不下這些對象,GC收集器就會將這些對象直接存放到Old區中,如果Survivor區中的對象足夠老,也直接存放到Old區中。如果Old區滿了,將會觸發Full GC回收整個堆內存。
個人博客 蝸牛
總結
以上是生活随笔為你收集整理的java垃圾回收机制优化_JVM性能优化--Java的垃圾回收机制的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java properties 保存_J
- 下一篇: java导出highcharts_Hig