Cordas compactas em Java 9
1. Visão geral
Strings em Java são internamente representados porchar[] contendo os caracteres deString. E cadachar é composto de 2 bytes porqueJava internally uses UTF-16.
Por exemplo, se aString contiver uma palavra no idioma inglês, os 8 bits iniciais serão todos 0 para cadachar, pois um caractere ASCII pode ser representado usando um único byte.
Muitos caracteres requerem 16 bits para representá-los, mas estatisticamente a maioria requer apenas 8 bits - representação de caracteres LATIN-1. Portanto, existe um escopo para melhorar o consumo e o desempenho da memória.
O que também é importante é queStrings normalmente ocupa uma grande proporção do espaço de heap da JVM. E, devido à forma como são armazenados pela JVM, na maioria dos casos,a String instance can take up double espaçoit actually needs.
Neste artigo, discutiremos a opção String compactada, introduzida no JDK6 e a nova String compacta, introduzida recentemente com o JDK9. Ambos foram projetados para otimizar o consumo de memória de Strings na JMV.
2. String compactado - Java 6
A atualização 21 Performance Release do JDK 6 introduziu uma nova opção de VM:
-XX:+UseCompressedStrings
Quando esta opção está habilitada,Strings são armazenados comobyte[], ao invés dechar[] –, economizando muita memória. No entanto, essa opção acabou sendo removida no JDK 7, principalmente por ter algumas conseqüências não desejadas no desempenho.
3. String compacto - Java 9
Java 9 trouxe o conceito de compactStrings back.
Isso significa quewhenever we create a String if all the characters of the String can be represented using a byte — LATIN-1 representation, a byte array will be used internamente, de forma que um byte é fornecido para um caractere.
Em outros casos, se algum caractere precisar de mais de 8 bits para representá-lo, todos os caracteres serão armazenados usando dois bytes para cada representação UTF-16.
Então, basicamente, sempre que possível, ele usará apenas um byte para cada caractere.
Agora, a questão é - como todas as operaçõesString funcionarão? Como vai distinguir entre as representações LATIN-1 e UTF-16?
Bem, para resolver esse problema, outra alteração é feita na implementação interna doString. Temos um campo finalcoder, que preserva essa informação.
3.1. Implementação deString em Java 9
Até agora, oString foi armazenado como umchar[]:
private final char[] value;
De agora em diante, será umbyte[]:
private final byte[] value;
A variávelcoder:
private final byte coder;
Onde ocoder pode ser:
static final byte LATIN1 = 0;
static final byte UTF16 = 1;
A maioria das operaçõesString agora verifica o codificador e despacha para a implementação específica:
public int indexOf(int ch, int fromIndex) {
return isLatin1()
? StringLatin1.indexOf(value, ch, fromIndex)
: StringUTF16.indexOf(value, ch, fromIndex);
}
private boolean isLatin1() {
return COMPACT_STRINGS && coder == LATIN1;
}
Com todas as informações de que a JVM precisa prontas e disponíveis, a opçãoCompactString VM é habilitada por padrão. Para desativá-lo, podemos usar:
+XX:-CompactStrings
3.2. Comocoder funciona
Na implementação da classe Java 9String, o comprimento é calculado como:
public int length() {
return value.length >> coder;
}
SeString contiver apenas LATIN-1, o valor decoder será 0, então o comprimento deString será o mesmo que o comprimento da matriz de bytes.
Em outros casos, seString estiver na representação UTF-16, o valor decoder será 1 e, portanto, o comprimento terá metade do tamanho da matriz de bytes real.
Observe que todas as alterações feitas para CompactString, estão na implementação interna da classeString e são totalmente transparentes para desenvolvedores que usamString.
4. Strings compacto vs. ComprimidoStrings
No caso do JDK 6Strings, compactado, um grande problema enfrentado foi que o construtorString aceitou apenaschar[] como argumento. Além disso, muitas operaçõesString dependiam da representaçãochar[] e não de uma matriz de bytes. Devido a isso, muita descompactação teve que ser feita, o que afetou o desempenho.
Considerando que, no caso de CompactString,, manter o campo extra “codificador” também pode aumentar o overhead. Para mitigar o custo decodere o desempacotamento debytes parachars (no caso de representação UTF-16), alguns dos métodos sãointrinsifiede o código ASM gerado pelo compilador JIT também foi melhorado.
Essa mudança resultou em alguns resultados contra-intuitivos. O LATIN-1indexOf(String) chama um método intrínseco, enquanto oindexOf(char) não. No caso de UTF-16, ambos os métodos chamam um método intrínseco. Esse problema afeta apenas o LATIN-1Stringe será corrigido em versões futuras.
Assim, CompactStrings são melhores do que CompactadoStrings em termos de desempenho.
Para descobrir quanta memória é salva usando o CompactStrings,, vários dumps de heap do aplicativo Java foram analisados. E, embora os resultados dependessem fortemente de aplicativos específicos, as melhorias gerais eram quase sempre consideráveis.
4.1. Diferença no desempenho
Vamos ver um exemplo muito simples da diferença de desempenho entre habilitar e desabilitar CompactStrings:
long startTime = System.currentTimeMillis();
List strings = IntStream.rangeClosed(1, 10_000_000)
.mapToObj(Integer::toString)
.collect(toList());
long totalTime = System.currentTimeMillis() - startTime;
System.out.println(
"Generated " + strings.size() + " strings in " + totalTime + " ms.");
startTime = System.currentTimeMillis();
String appended = (String) strings.stream()
.limit(100_000)
.reduce("", (l, r) -> l.toString() + r.toString());
totalTime = System.currentTimeMillis() - startTime;
System.out.println("Created string of length " + appended.length()
+ " in " + totalTime + " ms.");
Aqui, estamos criando 10 milhões deStrings e, em seguida, acrescentando-os de uma maneira ingênua. Quando executamos esse código (o Compact Strings está ativado por padrão), obtemos a saída:
Generated 10000000 strings in 854 ms.
Created string of length 488895 in 5130 ms.
Da mesma forma, se o executarmos desativando o Compact Strings usando a opção:-XX:-CompactStrings, a saída será:
Generated 10000000 strings in 936 ms.
Created string of length 488895 in 9727 ms.
Claramente, este é um teste de nível superficial e não pode ser altamente representativo - é apenas um instantâneo do que a nova opção pode fazer para melhorar o desempenho neste cenário específico.
5. Conclusão
Neste tutorial, vimos as tentativas de otimizar o desempenho e o consumo de memória na JVM - armazenandoStrings de maneira eficiente em termos de memória.
Como sempre, todo o código está disponívelover on Github.