Ao atualizar nosso projeto do Java 17 para o Java 21, notamos um aumento no consumo de memória. Depois de despejar o heap e analisar as diferenças, descobri que há milhares de strings vazias armazenadas na memória.
Consegui reproduzir o problema com o seguinte código:
import java.lang.management.ManagementFactory;
import java.text.DecimalFormat;
public class DecimalFormating {
static DecimalFormat decimalFormat = new DecimalFormat("#.##");
static DecimalFormat decimalFormat2 = new DecimalFormat();
public static void main(String[] args) {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
String pid = ManagementFactory.getRuntimeMXBean().getName().split("@")[0];
Process p = Runtime.getRuntime().exec("D:\\JAVA\\jdk-17.0.2\\bin\\jmap.exe -dump:format=b,file=heapdump_string_decimal_17.hprof " + pid);
p.waitFor();
} catch (Exception e) {
e.printStackTrace();
}
}));
}
}
O código a seguir é direto, pois define duas instâncias de DecimalFormat, que por sua vez definem múltiplas strings vazias, como visto aqui e aqui . Ele então despeja o heap em um arquivo.
Compilei e executei o código com Java 17.0.2 e Java 21.0.6, e aqui está a aparência da memória:
- No Java 17, você pode ver que todas as strings que deveriam estar vazias apontam para o mesmo endereço de memória, o que é um comportamento esperado devido ao String Constant Pool:
- No Java 21, cada string tem um endereço de memória diferente, resultando na string vazia sendo definida seis vezes e consumindo seis vezes mais memória do que no Java anterior:
Esse comportamento é normal? Não consigo encontrar nenhuma menção a esse tipo de mudança nas notas de lançamento do Java entre as versões 18 e 21.