From:深入理解Java虛擬機
- 目錄
BiBi - JVM -0- 開篇
BiBi - JVM -1- Java內存區(qū)域
BiBi - JVM -2- 對象
BiBi - JVM -3- 垃圾收集算法
BiBi - JVM -4- HotSpot JVM
BiBi - JVM -5- 垃圾回收器
BiBi - JVM -6- 回收策略
BiBi - JVM -7- Java類文件結構
BiBi - JVM -8- 類加載機制
BiBi - JVM -9- 類加載器
BiBi - JVM -10- 虛擬機字節(jié)碼
BiBi - JVM -11- 編譯期優(yōu)化
BiBi - JVM -12- 運行期優(yōu)化
BiBi - JVM -13- 并發(fā)
程序計數器歉眷、虛擬機棧、本地方法棧這3部分區(qū)域隨線程而生而滅互亮,每一個棧楨中分配多少內存基本是在編譯期間就可知的慢宗,他們的分配和回收具有確定性坪蚁,不需要考慮回收的問題。而Java堆和方法區(qū)不一樣镜沽,一個方法中的多個分支需要的內存是不一樣的敏晤,需要程序處于運行期間才能知道要創(chuàng)建哪些對象,這部分內存的分配和回收是動態(tài)的缅茉。
- GC需要完成的3個件事情:
1)哪些內存需要回收嘴脾?
2)什么時候回收?
3)如何回收宾舅?
1. 判斷對象“存活”的方法
-
引用計數算法【不好】
給對象添加一個引用計數器统阿,每當有一個地方引用它時彩倚,計數器值就加1筹我;當引用失效時,計數器值就減1帆离;任何時刻計數器為0的對象就是沒有被引用蔬蕊,死亡的對象。
問題:難以解決對象之間互相循環(huán)引用
例子:對象a哥谷、b已經不能再被訪問了岸夯,但是他們因為互相引用這對方,導致他們的引用計數都不為0们妥。所以猜扮,主流的Java虛擬機沒有采用這種方法進行內存管理。
public static void main(String[] args) {
A a = new A();
B b = new B();
a.obj = b;
b.obj = a;
a = null;
b = null;
System.gc();
}
-
可達性分析算法【主流程序語言采用的方法】
通過一系列【GC Roots】對象作為起始點监婶,從這些節(jié)點向下搜索旅赢,所走過的路徑稱為【引用鏈】,當一個對象到GC Roots沒有任何引用鏈相連時惑惶,則此對象不可用煮盼。
- Java中可以作為GC Roots的對象包括:
1)虛擬機棧中引用的對象
2)本地方法棧中JNI引用的對象
3)方法區(qū)中類靜態(tài)屬性引用的對象
4)方法區(qū)中常量引用的對象
2. finalize()方法
宣告一個對象真正死亡,至少要經歷兩次標記過程:
第一次:對象進行可達性分析后發(fā)現沒有與GC Root相連的引用鏈带污,進行第一次標記僵控,并且還要進行一次篩選,篩選得條件是該對象是否有必要執(zhí)行finalize()方法鱼冀。當對象沒有覆蓋finalize()方法或者finalize()方法已經被虛擬機調用過了报破,虛擬機將這兩種情況視為【沒有必要執(zhí)行】悠就。
第二次:對象有必要執(zhí)行finalize()方法,將這個對象放置在F-Queue隊列中泛烙,并由一個低優(yōu)先級的Finalizer線程去執(zhí)行它理卑。finalize()方法是對象逃離死亡命運的最后機會,GC會對F-Queue隊列中的對象進行第二次小規(guī)模的標記蔽氨。
注意:任何一個對象的finalize()方法只會被系統(tǒng)自動調用一次藐唠,所以在finalize()方法中逃脫的對象,只能夠逃脫一次鹉究。
F-Queue隊列的執(zhí)行宇立,無法保證各個對象的調用順序。所以自赔,最好不要在finalize()中做任何事情妈嘹。
3. 回收方法區(qū)
永久代的垃圾回收主要分兩部分:【廢棄的常量】和【無用的類】∩芊粒回收廢棄的常量跟Java堆中對象的回收類似润脸。
- 無用類的判斷條件:
1)Java堆中不存在該類的任何實例
2)加載該類的ClassLoader已經被回收
3)該類對應的java.lang.Class對象沒有在任何地方被引用,無法通過反射訪問該類
在大量使用反射他去、動態(tài)代理毙驯、CGLib等ByteCode框架、動態(tài)生成JSP以及OSGi這類頻繁自定義ClassLoader的場景灾测,需要虛擬機具備【類卸載的功能】爆价,以避免永久代溢出。
4. 垃圾收集算法
-
1)標記 - 清除算法
分兩個階段媳搪,先標記出需要回收的對象铭段,在標記完成后再統(tǒng)一回收所有標記的對象。
缺陷:標記和清除兩個過程效率不高秦爆;標記清除后會產生大量的不連續(xù)的內存碎片序愚,當程序運行過程中需要分配大對象時,由于無法找到連續(xù)的內存空間等限,而不得不提前觸發(fā)另一次垃圾回收動作爸吮。
-
2)復制算法【JVM新生代使用的算法】
將內存按容量分為大小相等的兩塊,每次只使用其中一塊精刷。當這塊內存用完拗胜,就將還存活的對象復制到另外一塊上面,然后再把已使用過的這塊內存空間一次全部清理掉怒允。
優(yōu)點:實現簡單埂软、效率高、沒有鎖片。
缺陷:內存空閑一半勘畔,浪費所灸。
因為【新生代】中的對象98%都是“朝生夕死”【存活率不高】,適合用復制算法來回收炫七,但并不一定按照1:1來劃分內存空間爬立。
HotSpot劃分比例為Eden:Survivor1:Survivor2 = 8:1:1,每次使用Eden和其中一個Survivor万哪,當回收時侠驯,將Eden和Survivor1中存活著的對象一次性復制到Survivor2中,最后清理掉Eden和Survivor1空間奕巍。這樣新生代只浪費了【10%】的內存空間吟策。
當新生代中回收的空間大于10%,即Survivor2空間不夠用時的止,多的對象將直接通過【分配擔保機制】進入到【老年代】檩坚。
-
3)標記 - 整理【JVM老年代使用的算法】
與標記-清除算法一樣,只是不是直接對可回收對象進行清理诅福,而是讓所有存活的對象都向一端移動匾委,然后直接清理掉邊界以外的內存。
老年代中對象【存活率高】【沒有額外的空間進行擔泵ト螅】赂乐,所以必須使用:標記-清理/整理算法,不能使用復制算法進行擔保旺芽。