關注「Java後端技術全棧」java
回覆「面試」獲取全套面試資料面試
廢話少說,直接開整:算法
JDK
、JRE
、JVM
的關係JDK
中包含JRE
,也包括JDK
,而JRE
也包括JDK
。後端
範圍關係:JDK
>JRE
>JVM
。緩存
具體見下圖:數據結構
.java
文件與.class
文件的關係這二者的關係須要兩張圖才能說明白:jvm
JVM經過類加載機制,把class文件裝載進JVM中,而後JVM解析class文件的內容,因而就有了類加載過中的連接、初始化等。源碼分析
一張圖來講明:編碼
直接上圖:spa
若是在棧幀中有一個變量,類型爲引用類型,好比:
package com.tian.my_code.test; public class JvmCodeDemo { public Object testGC(){ int op1 = 10; int op2 = 3; Object obj = new Object(); Object result=obj; return result; } }
這時候就是典型的棧中元素obj指向堆中的Object對象,result的指向和obj的指向爲同一個對象。
使用命令
javac -g:vars JvmCodeDemo.java
進行編譯,而後再使用
javap -v JvmCodeDemo.class >log.txt
而後打開log.txt
文件
方法區中會存放靜態變量,常量等數據。
若是是下面這種狀況,就是典型的方法區中元素指向堆中的對象。
方法區中會包含類的信息,對象保存再堆中,建立一個對象的前提是有對應的類信息,這個類信息就在方法區中。
Minor GC:發生在年輕代的 GC。
Major GC:發生在老年代的 GC。
Full GC:新生代+老年代,好比 方法區引發年輕代和老年代的回收。
對於這二者,最重要的是要明白爲何須要Survivor區?只有Eden不行嗎?
若是沒有Survivor,Eden區每進行一次Minor GC ,而且沒有年齡限制的話, 存活的對象就會被送到老年代。這樣一來,老年代很快被填滿,觸發Major GC(由於Major GC通常伴隨着Minor GC,也能夠看作觸發了Full GC)。老年代的內存空間遠大於新生代,進行一次Full GC消耗的時間比Minor GC長得多。
執行時間長有什麼壞處?
頻發的Full GC消耗的時間很長,會影響大型程序的執行和響應速度。
可能你會說,那就對老年代的空間進行增長或者較少咯。
假如增長老年代空間,更多存活對象才能填滿老年代。雖然下降Full GC頻率,可是隨着老年代空間加大,一旦發生Full GC,執行所須要的時間更長。
假如減小老年代空間,雖然Full GC所需時間減小,可是老年代很快被存活對象填滿,Full GC頻率增長。
因此Survivor的存在乎義,就是減小被送到老年代的對象,進而減小Full GC的發生,Survivor的預篩選保證,只有經歷16 次Minor GC還能在新生代中存活的對象,纔會被送到老年代。
給對象添加一個引用計數器,每當一個地方引用它object時技術加1,引用失去之後就減1,計數爲0說明再也不引用
public class A { public B b; } public class B { public C c; } public class C { public A a; } public class Test{ private void test(){ A a = new A(); B b = new B(); C c = new C(); a.b=b; b.c=c; c.a=a; } }
當一個對象到GC Roots
沒有引用鏈相連,即就是GC Roots
到這個對象不可達時,證實對象不可用。
GC Roots
種類:
❝Java 線程中,當前全部正在被調用的方法的引用類型參數、局部變量、臨時值等。也就是與咱們棧幀相關的各類引用。全部當前被加載的 Java 類。Java 類的引用類型靜態變量。運行時常量池裏的引用類型常量(String 或 Class 類型)。JVM 內部數據結構的一些引用,好比 sun.jvm.hotspot.memory.Universe 類。用於同步的監控對象,好比調用了對象的 wait() 方法。
❞
User user=new User()
;咱們開發中使用最多的對象引用方式。特色:咱們日常典型編碼Object obj = new Object()
中的obj就是強引用。
經過關鍵字new建立的對象所關聯的引用就是強引用。
當JVM
內存空間不足,JVM
寧願拋出OutOfMemoryError
運行時錯誤(OOM
),使程序異常終止,也不會靠隨意回收具備強引用的「存活」對象來解決內存不足的問題。
對於一個普通的對象,若是沒有其餘的引用關係,只要超過了引用的做用域或者顯式地將相應(強)引用賦值爲 null,就是能夠被垃圾收集的了,具體回收時機仍是要看垃圾收集策略。
SoftReference<Object> object=new SoftReference<Object>(new Object())
;特色:軟引用經過SoftReference
類實現。軟引用的生命週期比強引用短一些。只有當 JVM 認爲內存不足時,纔會去試圖回收軟引用指向的對象:即JVM 會確保在拋出 OutOfMemoryError 以前,清理軟引用指向的對象。軟引用能夠和一個引用隊列(ReferenceQueue)聯合使用,若是軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。後續,咱們能夠調用ReferenceQueue的poll()方法來檢查是否有它所關心的對象被回收。若是隊列爲空,將返回一個null,不然該方法返回隊列中前面的一個Reference對象。
應用場景:軟引用一般用來實現內存敏感的緩存。若是還有空閒內存,就能夠暫時保留緩存,當內存不足時清理掉,這樣就保證了使用緩存的同時,不會耗盡內存
WeakReference<Object> object=new WeakReference<Object> (new Object()
;ThreadLocal
中有使用.弱引用經過WeakReference
類實現。弱引用的生命週期比軟引用短。在垃圾回收器線程掃描它所管轄的內存區域的過程當中,一旦發現了具備弱引用的對象,無論當前內存空間足夠與否,都會回收它的內存。因爲垃圾回收器是一個優先級很低的線程,所以不必定會很快回收弱引用的對象。
弱引用能夠和一個引用隊列(ReferenceQueue
)聯合使用,若是弱引用所引用的對象被垃圾回收,Java虛擬機就會把這個弱引用加入到與之關聯的引用隊列中。應用場景:弱應用一樣可用於內存敏感的緩存。
ReferenceQueue 、PhantomReference
。第一步:就是找出活躍的對象。咱們反覆強調 GC 過程是逆向的, 根據 GC Roots 遍歷全部的可達對象,這個過程,就叫做標記。
第二部:除了上面標記出來的對象之外,其他的都清楚掉。
新生代使用,新生代分中Eden:S0:S1
= 8:1:1,其中後面的1:1就是用來複制的。
當其中一塊內存使用完了,就將還存活的對象複製到另一塊上面,而後把已經使用過的內存空間一次 清除掉。
通常對象分配都是進入新生代的eden區,若是Minor GC
還存活則進入S0
區,S0
和S1
不斷對象進行復制。對象存活年齡最大默認是15,大對象進來可能由於新生代不存在連續空間,因此會直接接入老年代。任何使用都有新生代的10%是空着的。
它的主要思路,就是移動全部存活的對象,且按照內存地址順序依次排列,而後將末端內存地址之後的內存所有回收。 可是須要注意,這只是一個理想狀態。對象的引用關係通常都是很是複雜的,咱們這裏不對具體的算法進行描述。咱們只須要了解,從效率上來講,通常整理算法是要低於複製算法的。這個算法是規避了內存碎片和內存浪費。
讓全部存活的對象都向一端移動,而後直接清理掉端邊界之外的內存。
從上面的三個算法來看,其實沒有絕對最好的回收算法,只有最適合的算法。
「新生代收集器」:Serial、ParNew
、Parallel Scavenge
「老年代收集器」:CMS
、Serial Old、Parallel Old
「整堆收集器」:G1
,ZGC
(由於不涉年代不在圖中)
推薦閱讀