文章首發於公衆號《程序員果果》
一、簡介
Epsilon(A No-Op Garbage Collector)垃圾回收器控制內存分配,但是不執行任何垃圾回收工作。一旦java的堆被耗盡,jvm就直接關閉。設計的目的是提供一個完全消極的GC實現,分配有限的內存分配,最大限度降低消費內存佔用量和內存吞吐時的延遲時間。一個好的實現是隔離代碼變化,不影響其他GC,最小限度的改變其他的JVM代碼。
二、使用場景
- Performance testing,什麼都不執行的GC非常適合用於差異性分析。no-op GC可以用於過濾掉GC誘發的新能損耗,比如GC線程的調度,GC屏障的消耗,GC週期的不合適觸發,內存位置變化等。此外有些延遲者不是由於GC引起的,比如scheduling hiccups, compiler transition hiccups,所以去除GC引發的延遲有助於統計這些延遲。
- Memory pressure testing, 在測試java代碼時,確定分配內存的閾值有助於設置內存壓力常量值。這時no-op就很有用,它可以簡單地接受一個分配的內存分配上限,當內存超限時就失敗。例如:測試需要分配小於1G的內存,就使用-Xmx1g參數來配置no-op GC,然後當內存耗盡的時候就直接crash。
- VM interface testing, 以VM開發視角,有一個簡單的GC實現,有助於理解VM-GC的最小接口實現。它也用於證明VM-GC接口的健全性。
- Extremely short lived jobs, 一個短聲明週期的工作可能會依賴快速退出來釋放資源,這個時候接收GC週期來清理heap其實是在浪費時間,因爲heap會在退出時清理。並且GC週期可能會佔用一會時間,因爲它依賴heap上的數據量。
- Last-drop latency improvements, 對那些極端延遲敏感的應用,開發者十分清楚內存佔用,或者是幾乎沒有垃圾回收的應用,此時耗時較長的GC週期將會是一件壞事。
- Last-drop throughput improvements, 即便對那些無需內存分配的工作,選擇一個GC意味着選擇了一系列的GC屏障,所有的OpenJDK GC都是分代的,所以他們至少會有一個寫屏障。避免這些屏障可以帶來一點點的吞吐量提升。
三、案例
使用G1垃圾收集器
代碼:
public class TestEpsilon {
public static void main(String[] args) {
System.out.println("程序開始");
boolean flag = true;
List<Garbage> list = new ArrayList<>();
long count = 0;
while (flag) {
list.add(new Garbage(list.size() + 1));
if (list.size() == 1000000 && count == 0) {
list.clear();
count++;
}
}
System.out.println("程序結束");
}
}
class Garbage {
private int number;
public Garbage(int number) {
this.number = number;
}
/**
* GC在清除對象時,會調用finalize()方法
*/
@Override
public void finalize() {
System.out.println(this + " : " + number + " is dying");
}
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
啓動參數:
-Xms100m -Xmx100m
運行程序後,結果如下:
程序開始
...
com.gf.demo8.Garbage@15ddf76b : 305097 is dying
com.gf.demo8.Garbage@35e52705 : 305224 is dying
com.gf.demo8.Garbage@32c14bc1 : 305362 is dying
com.gf.demo8.Garbage@7521660a : 305705 is dying
com.gf.demo8.Garbage@f3da16a : 305948 is dying
com.gf.demo8.Garbage@13fc7287 : 306089 is dying
at java.base/java.lang.ref.Finalizer.register(Finalizer.java:66)
at java.base/java.lang.Object.<init>(Object.java:50)
at com.gf.demo8.Garbage.<init>(TestEpsilon.java:28)
at com.gf.demo8.TestEpsilon.main(TestEpsilon.java:14)
...
會發現G1一直回收對象,直到內存不夠用。
使用Epsilon垃圾收集器
啓動參數:
UnlockExperimentalVMOptions:解鎖隱藏的虛擬機參數。
-XX:+UnlockExperimentalVMOptions -XX:+UseEpsilonGC -Xms100m -Xmx100m
運行程序後,結果如下:
程序開始
Terminating due to java.lang.OutOfMemoryError: Java heap space
會發現很快就內存溢出了,因爲Epsilon不會去回收對象。