一、 GC回收日誌打印基本設置
啓動設置:
-verbose:gc //開啓gc日誌
-XX:+PrintGCDetails //打印gc詳情
-XX:+PrintGCDateStamps //打印gc時間戳
-XX:+PrintHeapAtGC //在進行GC的前後打印出堆的信息
-Xloggc:gc.log //日誌輸出名稱
-Xms3M //初始內存
-Xmx4M //最大可用內存
日誌各個字段含義簡述:
2019-03-28 日期
10:02:40.628+0800: 執行回收時間
0.080: 啓動JVM到該次垃圾回收時間
GC/Full GC :垃圾回收類型
Allocation Failure : 垃圾回收的原因,此時爲Eden區空間不足
ParNew/CMS/Metaspace : 垃圾回收區域 新生代/老年代/元空間
[PSYoungGen: 510K->504K(1024K)] 清理前佔用 -> 清理後佔用(新生代內存總大小)
PS代表新生代的是使用的是Parallel Scavenge收集器垃圾收集器
[ParOldGen] Par代表老年代使用的是Parallel old垃圾回收器
0.0006474 secs 表示回收的時間
510K->536K(3584K) 整個堆內存清理前佔用 ——> 清理後佔用(對內存總大小)
[Metaspace: 3304K->3304K(1056768K)] 這部分表示的是方法區的內存大小變化
[Times: user=0.00 sys=0.00, real=0.00 secs]
user 代表的是用戶狀態耗時0.00,
sys 系統狀態耗時0.00,
real CPU實際的執行時間爲0.00
Ergonomics:HotSpot自動選擇和調優引發的FullGC
二、瞭解其概念及其區別
我們在實際開發中,往往會執行一些方法加載一些參數值到內存中,後期隨着業務的發展、方法的執行等等大量的數據存在內存中,結果導致內存佔用過多而產生的溢出異常,因此我們期望自己的放到內存中的對象也具有生命週期(如:內存不足時,jvm會自動回收掉某些對象從而避免OOM的錯誤)這個時候就需要用到軟引用和弱引用
從JDK 1.2之後,Java 對引用的概念進行了擴充,將引用分爲了:強引用(Strong Reference)、軟引用(Soft Reference)、弱引用(Weak Reference)、虛引用(Phantom Reference)4 種,這 4 種引用的強度依次減弱。
強引用:
Java中默認聲明的就是強引用,如:Object object=new Object();只要強引用還存在,JVM必定不會回收掉被引用的對象,即使在內存不足的情況下,JVM寧願拋出OOM異常,也不會回收這種對象;
如下代碼:
public class StronglyReference {
public static void main(String[] args) {
Object object=new Object();
System.gc();
System.out.println("GC回收後:"+object);
try {
byte[] buff2 = new byte[1024 * 1024 * 4];
} catch (OutOfMemoryError e) {
//爲了程序繼續執行下去,捕獲異常,如果不捕獲會拋出異常且回收
System.out.println("創建對象發生OOM異常,哥,你寧願發生異常也不回收掉哎,何必呢!");
}
//將引用設置爲null,jvm在適當的時候會回收該對象
object=null;
System.out.println("設置爲空後:"+object);
}
}
結果產出:
GC回收後:java.lang.Object@12a3a380
創建對象發生OOM異常,哥,你寧願發生異常也不回收掉哎,何必呢!
設置爲空後:null
經過上面的demo實例,然後分析GC日誌,我們得出下面的問題和答案;
問:那麼在使用強引用的時候,JVM是麼時候會觸發回收?
答:
- 線程結束時回收;
- 當 softReference 設置爲空的時候,會被回收;
軟引用(SoftReference):
官方解釋:軟引用是用來描述一些非必需但仍有用的對象。在內存足夠的時候,軟引用對象不會被回收,只有在內存不足時,系統則會回收軟引用對象,如果回收了軟引用對象之後仍然沒有足夠的內存,纔會拋出內存溢出異常。在 JDK1.2 之後,用java.lang.ref.SoftReference
類來表示軟引用。
查看一下事例:
public class SoftReferences {
public static void main(String[] args) {
List<SoftReference> list=new ArrayList<SoftReference>();
for (int i=0;i<10;i++){
byte[] softBuff = new byte[1024 * 1024 * 1];
SoftReference sr = new SoftReference(softBuff);
list.add(sr);
}
for (SoftReference sr:list) {
System.out.println(sr.get());
}
}
}
返回結果:
null
null
null
null
null
null
null
null
null
[B@12a3a380
在GC日誌中我們可以看到一個關鍵字出現:Ergonomics
(解釋上面有說明)
由此我們可以得出一個結論與解釋一致
(還有一種情況,當軟引用設置爲空時也會被回收)
事例代碼:
public static void main(String[] args) {
byte[] softBuff1 = new byte[1024 * 1024 * 2];
SoftReference sr1 = new SoftReference(softBuff1);
sr1=null;
System.out.println(sr1.get());
}
result:
Exception in thread "main" java.lang.NullPointerException
由此可見,設置爲空時,對象也被回收了;
弱引用(WeakReference):
這個其實挺簡單的:只要JVM進行垃圾回收,無論內存是否充足,都會回收弱引用關聯的對象,在 java 中,用 java.lang.ref.WeakReference
類來表示
事例:
public class WeakReferences {
public static void main(String[] args) {
try {
WeakReference w1=new WeakReference(new Object());
System.out.println(w1.get());
System.gc();
System.out.println(w1.get());
} catch (NullPointerException e) {
System.out.println("W1 會拋出空指針異常,表示被回收");
}
}
}
result:
java.lang.Object@12a3a380
null
虛引用(PhantomReference):
官方解釋:如果一個對象僅持有虛引用,那麼它就和沒有任何引用一樣,它隨時可能會被回收,在 JDK1.2 之後,用PhantomReference 類來表示;我們看下源碼
源碼:
public class PhantomReference<T> extends Reference<T> {
public T get() {
return null;
}
public PhantomReference(T referent, ReferenceQueue<? super T> q) {
super(referent, q);
}
}
通過源碼我們可以清晰的瞭解到,它只有一個構造函數和一個 get()
方法,而且 get()
方法是固定的返回的null,這樣玩們就知道了,我們永遠無法單獨的通過虛引用來獲取對象,虛引用也必須要和ReferenceQueue
引用隊列一起使用。這是是它和軟引用、弱引用最直觀的局別;
引用隊列(ReferenceQueue)
官方解釋:引用隊列可以與軟引用、弱引用以及虛引用一起配合使用,當垃圾回收器準備回收一個對象時,如果發現它還有引用,那麼就會在回收對象之前,把這個引用加入到與之關聯的引用隊列中去。程序可以通過判斷引用隊列中是否已經加入了引用,來判斷被引用的對象是否將要被垃圾回收,這樣就可以在對象被回收之前採取一些必要的措施。
與軟引用、弱引用不同,虛引用必須和引用隊列一起使用(上面有提到過)。
public class PhantomReferences {
public static void main(String[] args) throws InterruptedException {
ReferenceQueue queue=new ReferenceQueue();
Object phantomObject=new Object();
PhantomReference phantomReference=new PhantomReference (phantomObject,queue);
System.out.println(phantomReference.get());
phantomObject=null;
System.gc();
System.out.println(phantomReference.get());
Thread.sleep(200);
System.out.println(queue.poll());
}
}
result:
null
null
java.lang.ref.PhantomReference@12a3a380
問:爲什麼會有對象輸出呢?
答:我們已經創建引用隊列,並且在引用隊列裏面創建了對象phantomObject,當GC回收的時候發現虛引用,會將這個對象丟到隊列裏面,而現在插入隊列的時候發現已經有了這個對象,會失敗,然後會回收掉這個對象並且返回,所以會有返回對象