Prefácio
A empresa possui um sistema que utiliza o coletor de lixo CMS. A memória heap inicial da JVM não é igual à memória heap máxima. Porém, através do monitoramento das informações, verifica-se que após um FullGC, o espaço restante da memória física do servidor não aumentou. De acordo com meu entendimento anterior após FullGC Parte da memória liberada pelo processo JVM é retornada à memória física. Vamos comparar e verificar o mecanismo de retorno da memória física de CMS e G1 por meio de alguns experimentos.
Código de teste
public class MemoryRecycleTest {
static volatile List<OOMobject> list = new ArrayList<>();
public static void main(String[] args) {
//指定要生产的对象大小为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();
//阻止程序退出
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void addObject(List<OOMobject> 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("");
}
}
JDK8 CMS
Parâmetros JVM :
-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC
O que o console imprime :
第1次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:6 MB
JVM内存的空闲空间为:1202 MB
JVM总内存空间为:1208 MB
JVM总内存最大堆空间为:1979 MB
第2次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:3 MB
JVM内存的空闲空间为:1097 MB
JVM总内存空间为:1100 MB
JVM总内存最大堆空间为:1979 MB
第3次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:3 MB
JVM内存的空闲空间为:706 MB
JVM总内存空间为:709 MB
JVM总内存最大堆空间为:1979 MB
第4次生产512大小的对象
清理list.... 回收jvm内存....
JVM内存已用的空间为:3 MB
JVM内存的空闲空间为:120 MB
JVM总内存空间为:123 MB
JVM总内存最大堆空间为:1979 MB
Memória heap monitorada pelo VisualVM :
Pode ser visto a partir da situação da memória heap na figura que sob a configuração de JDK8 + CMS, JVM não retorna a memória para o sistema operacional imediatamente, mas a retorna gradualmente conforme o número de FullGC aumenta e, eventualmente, retornará tudo isso
JDK8 G1
Parâmetros JVM :
-Xms128M -Xmx2048M -XX:+UseG1GC
Memória heap monitorada pelo VisualVM :
Na configuração de JDK8 + G1, JVM retorna toda a memória física após cada FullGC
JDK11 CMS
Parâmetros JVM :
-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC
Memória heap monitorada pelo VisualVM :
Na configuração do JDK11 + CMS, a situação é a mesma do JDK8 + CMS (JVM não retorna a memória para o sistema operacional imediatamente, mas a retorna gradualmente conforme o número de FullGC aumenta e, eventualmente, retornará tudo)
JDK11 fornece um parâmetro JVM ShrinkHeapInSteps
. Com este parâmetro, a memória pode ser devolvida ao sistema operacional gradualmente após o GC. No JDK11, este parâmetro é habilitado por padrão. Você pode desativar este parâmetro para ver como a memória heap muda:
-Xms128M -Xmx2048M -XX:+UseConcMarkSweepGC -XX:-ShrinkHeapInSteps
Memória heap monitorada pelo VisualVM :
Na configuração do ShrinkHeapInSteps
JDK11 + CMS, após desligar os parâmetros, o JVM irá retornar toda a memória física após cada FullGC
JDK11 G1
Como o JDK11 usa o coletor de lixo G1 por padrão, apenas a memória heap inicial e a memória heap máxima são definidas aqui
Parâmetros JVM :
-Xms128M -Xmx2048M
Memória heap monitorada pelo VisualVM :
1) JDK11 ShrinkHeapInSteps
é habilitado por padrão por padrão, mas aqui, as mudanças na memória heap não são reduzidas gradualmente. Portanto, ShrinkHeapInSteps
é inválido no coletor G1 . Se desligarmos manualmente os ShrinkHeapInSteps
parâmetros, descobrimos que as mudanças na memória heap são semelhantes às anteriores
2) G1 sob JDK11 e G1 sob JDK8 têm respostas diferentes à memória. Do ponto de vista das mudanças na memória heap, G1 no JDK11 é mais inclinado a usar a memória tanto quanto possível e não apressar para reciclá-la . G1 no JDK8 tende a recuperar a memória tanto quanto possível. Pela figura, o tamanho real da memória heap de G1 em JDK8 é basicamente a metade de G1 em JDK11
resumo
Se o código permanecer o mesmo, mas as configurações de Xms e Xmx nos parâmetros JVM forem as mesmas, independentemente de haver FullGC, o tamanho da memória heap não será alterado e a memória não será liberada para o sistema operacional
Como devolver a memória ao sistema operacional após GC:
- Se ele pode ser retornado depende se Xms e Xmx são iguais
- Quando retornar, depende principalmente da versão do JDK e do tipo de coletor de lixo
Somente no FullGC a memória heap pode ser reduzida e retornada ao sistema operacional. YGC não pode fazer com que a JVM retorne ativamente a memória para o sistema operacional
Tente manter Xms e Xmx consistentes, de modo a reduzir a perda de desempenho causada pelo ajuste de memória heap e também reduzir o risco de falta de memória causado pelo ajuste de memória heap
referência:
https://segmentfault.com/a/1190000019856974
https://www.cnblogs.com/androidsuperman/p/11743103.html
http://blog.dutycode.com/archives/jvmjvm%E7%9A%84xms%E5%8F%82%E6%95%B0%E5%92%8Clinuxtop%E5%91%BD%E4%BB%A4% E7% 9A% 84res% E5% 85% B3% E7% B3% BB