公司有一個資產統計系統,使用頻率很低,可是要求在使用時查詢速度快,所以想到作一些緩存放在內存中,在長時間沒有使用,持久化到磁盤中,並對垃圾進行回收,歸還物理內存給操做系統,從而節省寶貴資源給其它業務系統。當我作好緩存時,卻發現了一個棘手的問題,經過程序釋放資源並通知GC回收資源後,堆內存的已用內存減小了,空閒內存增長了,但是進程佔用系統內存卻沒有減小。查閱了不少資料,也嘗試過不少次,都沒有完美解決問題。直到後來看到一段評論談及G1垃圾回收器,才恍然大悟。java
接下來,經過一個小demo給你們演示一下兩種垃圾回收器對物理內存歸還的區別。若是有什麼不對的地方,但願你們可以在評論裏面指正。緩存
-Xms128M -Xmx2048M
複製代碼
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
public class MemoryRecycleTest {
@Test
public void testMemoryRecycle() throws InterruptedException {
List list = new ArrayList();
//指定要生產的對象大小爲512m
int count = 512;
//新建一條線程,負責生產對象
new Thread(() -> {
try {
for (int i = 1; i <= 10; i++) {
System.out.println(String.format("第%s次生產%s大小的對象", i, count));
addObject(list, count);
//休眠40秒
Thread.sleep(i * 10000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
//新建一條線程,負責清理list,回收jvm內存
new Thread(() -> {
for (;;) {
//當list內存到達512m,就通知gc回收堆
if (list.size() >= count) {
System.out.println("清理list.... 回收jvm內存....");
list.clear();
//通知gc回收
System.gc();
//打印堆內存信息
printJvmMemoryInfo();
}
}
}).start();
//阻止程序退出
Thread.currentThread().join();
}
public void addObject(List list, int count) {
for (int i = 0; i < count; i++) {
OOMobject ooMobject = new OOMobject();
//向list添加一個1m的對象
list.add(ooMobject);
try {
//休眠100毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static class OOMobject{
//生成1m的對象
private byte[] bytes=new byte[1024*1024];
}
public static void printJvmMemoryInfo() {
// 虛擬機級內存狀況查詢
long vmFree = 0;
long vmUse = 0;
long vmTotal = 0;
long vmMax = 0;
int byteToMb = 1024 * 1024;
Runtime rt = Runtime.getRuntime();
vmTotal = rt.totalMemory() / byteToMb;
vmFree = rt.freeMemory() / byteToMb;
vmMax = rt.maxMemory() / byteToMb;
vmUse = vmTotal - vmFree;
System.out.println("");
System.out.println("JVM內存已用的空間爲:" + vmUse + " MB");
System.out.println("JVM內存的空閒空間爲:" + vmFree + " MB");
System.out.println("JVM總內存空間爲:" + vmTotal + " MB");
System.out.println("JVM總內存最大堆空間爲:" + vmMax + " MB");
System.out.println("");
}
}
複製代碼
-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC
複製代碼
第1次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:6 MB
JVM內存的空閒空間爲:936 MB
JVM總內存空間爲:942 MB
JVM總內存最大堆空間爲:1990 MB
第2次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:1025 MB
JVM總內存空間爲:1029 MB
JVM總內存最大堆空間爲:1990 MB
第3次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:680 MB
JVM總內存空間爲:684 MB
JVM總內存最大堆空間爲:1990 MB
第4次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:119 MB
JVM總內存空間爲:123 MB
JVM總內存最大堆空間爲:1990 MB
第5次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:119 MB
JVM總內存空間爲:123 MB
JVM總內存最大堆空間爲:1990 MB
第6次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:119 MB
JVM總內存空間爲:123 MB
JVM總內存最大堆空間爲:1990 MB
第7次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:119 MB
JVM總內存空間爲:123 MB
JVM總內存最大堆空間爲:1990 MB
第8次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:119 MB
JVM總內存空間爲:123 MB
JVM總內存最大堆空間爲:1990 MB
第9次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:119 MB
JVM總內存空間爲:123 MB
JVM總內存最大堆空間爲:1990 MB
複製代碼
C:\Users>jmap -heap 4716
Attaching to process ID 4716, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12
using parallel threads in the new generation.
using thread-local object allocation.
Concurrent Mark-Sweep GC
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2122317824 (2024.0MB)
NewSize = 44695552 (42.625MB)
MaxNewSize = 348913664 (332.75MB)
OldSize = 89522176 (85.375MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 0 (0.0MB)
Heap Usage:
New Generation (Eden + 1 Survivor Space):
capacity = 280887296 (267.875MB)
used = 1629392 (1.5539093017578125MB)
free = 279257904 (266.3210906982422MB)
0.5800874668251284% used
Eden Space:
capacity = 249692160 (238.125MB)
used = 1629392 (1.5539093017578125MB)
free = 248062768 (236.5710906982422MB)
0.6525603366961942% used
From Space:
capacity = 31195136 (29.75MB)
used = 0 (0.0MB)
free = 31195136 (29.75MB)
0.0% used
To Space:
capacity = 31195136 (29.75MB)
used = 0 (0.0MB)
free = 31195136 (29.75MB)
0.0% used
concurrent mark-sweep generation:
capacity = 624041984 (595.1328125MB)
used = 4169296 (3.9761505126953125MB)
free = 619872688 (591.1566619873047MB)
0.6681114583470076% used
6718 interned Strings occupying 574968 bytes.
複製代碼
經過統計圖和控制檯日誌,能夠看到在運行43秒左右前,使用內存呈直線平滑上升,開闢的內存呈階梯狀上升。當使用內存到達525m時,程序發起了System.gc(),此時垃圾被回收了,所以使用內存回到了10m,但是jvm開闢出來的內存空間卻沒有歸還給操做系統,致使程序一直霸佔着960m左右的內存資源。第二次生產對象時,能夠看到在運行53秒至1分44秒時,再也不開闢新空間,而是重複利用已開闢的內存繼續建立對象,當執行第二次System.gc()時,jvm又開闢了一小部份內存,這一次程序霸佔了1050m內存資源。第三次生產對象時,能夠看到在運行2分05秒至2分55秒時,再也不開闢新空間,而是重複利用已開闢的內存繼續建立對象,當執行到第三次System.gc()時,jvm歸還了一部份內存給操做系統,此時依然霸佔着700m內存。........循環執行10次......從總的狀況,能夠看出,隨着System.gc()次數逐漸增長和時間間隔逐漸拉大,從繼續開闢內存變成了慢慢歸還內存給了操做系統,直到後面將物理內存所有歸還給操做系統。bash
-Xms128M -Xmx2048M -XX:+UseG1GC
複製代碼
第1次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:5 MB
JVM內存的空閒空間爲:123 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
第2次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:124 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
第3次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:124 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
第4次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:124 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
第5次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:124 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
第6次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:124 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
第7次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:124 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
第8次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:124 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
第9次生產512大小的對象
清理list.... 回收jvm內存....
JVM內存已用的空間爲:4 MB
JVM內存的空閒空間爲:124 MB
JVM總內存空間爲:128 MB
JVM總內存最大堆空間爲:2024 MB
複製代碼
C:\Users>jmap -heap 18112
Attaching to process ID 18112, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 25.161-b12
using thread-local object allocation.
Garbage-First (G1) GC with 4 thread(s)
Heap Configuration:
MinHeapFreeRatio = 40
MaxHeapFreeRatio = 70
MaxHeapSize = 2122317824 (2024.0MB)
NewSize = 1363144 (1.2999954223632812MB)
MaxNewSize = 1272971264 (1214.0MB)
OldSize = 5452592 (5.1999969482421875MB)
NewRatio = 2
SurvivorRatio = 8
MetaspaceSize = 21807104 (20.796875MB)
CompressedClassSpaceSize = 1073741824 (1024.0MB)
MaxMetaspaceSize = 17592186044415 MB
G1HeapRegionSize = 1048576 (1.0MB)
Heap Usage:
G1 Heap:
regions = 2024
capacity = 2122317824 (2024.0MB)
used = 8336616 (7.950416564941406MB)
free = 2113981208 (2016.0495834350586MB)
0.39280714253663074% used
G1 Young Generation:
Eden Space:
regions = 2
capacity = 83886080 (80.0MB)
used = 2097152 (2.0MB)
free = 81788928 (78.0MB)
2.5% used
Survivor Space:
regions = 0
capacity = 0 (0.0MB)
used = 0 (0.0MB)
free = 0 (0.0MB)
0.0% used
G1 Old Generation:
regions = 11
capacity = 50331648 (48.0MB)
used = 6239464 (5.950416564941406MB)
free = 44092184 (42.049583435058594MB)
12.396701176961264% used
6706 interned Strings occupying 573840 bytes.
複製代碼
經過統計圖和控制檯日誌,能夠看到在運行41秒左右前,使用內存呈直線平滑上升,開闢的內存也是呈直線平滑上升。當使用內存到達530m時,程序發起了System.gc(),垃圾被回收,所以使用內存回到了10m。此時會發現神奇的現象出來了,jvm以前開闢出來的剩餘內存空間所有歸還給了操做系統,內存回到了咱們指定的初始jvm堆大小128m。經過屢次執行生產對象對比發現,jvm都是在每一次調用System.gc()後所有歸還物理內存,不作任何保留。達到了我指望的效果!app
CMS垃圾回收器,在內存開闢後,會隨着System.gc()執行次數逐漸增多和回收頻率逐漸拉長,從繼續開闢內存到慢慢歸還物理內存給操做系統,直到出現一次所有歸還,就會在每次調用System.gc()都歸還全部剩餘的物理內存給操做系統;G1偏偏相反,G1是在JVM每次回收垃圾後,主動歸還物理內存給操做系統,不作任何保留,大大下降了內存佔用。jvm
另外,查看java堆棧實時狀況,推薦使用JProfiler和VisualVM。若是是本地推薦JProfiler,由於功能強大,不過遠程配置麻煩;若是是連遠程java進程,推薦VisualVM,功可以用,鏈接遠程只需配置一些jvm參數。性能
JDK 12將有G1收集器,將內存返回到操做系統(不調用System.gc)「應用程序空閒時」測試
jdk9 增長了這個jvm參數:
-XX:+ShrinkHeapInSteps
使Java堆漸進地縮小到目標大小,該選項默認開啓,通過屢次GC後堆縮小到目標大小;若是關閉該選項,那麼GC後Java堆將當即縮小到目標大小。若是但願最小化Java堆大小,能夠關閉改選項,並配合如下選項:
-XX:MaxHeapFreeRatio=10 -XX:MinHeapFreeRatio=5
這樣將保持Java堆空間較小,並減小程序的動態佔用空間,這對嵌入式應用很是有用,但對於通常應用,可能下降性能。
複製代碼
www.imooc.com/wenda/detai… developer.ibm.com/cn/blog/201… gameinstitute.qq.com/community/d… www.zhihu.com/question/30… www.zhihu.com/question/29…ui