Surpreendentemente, as seguintes saídas de código:
/
-1
O código:
public class LoopOutPut {
public static void main(String[] args) {
LoopOutPut loopOutPut = new LoopOutPut();
for (int i = 0; i < 30000; i++) {
loopOutPut.test();
}
}
public void test() {
int i = 8;
while ((i -= 3) > 0) ;
String value = i + "";
if (!value.equals("-1")) {
System.out.println(value);
System.out.println(i);
}
}
}
Eu tentei muitas vezes para determinar quantas vezes isso iria ocorrer, mas, infelizmente, acabou por ser incerto, e eu achei que a saída de -2 por vezes transformado em um período. Além disso, também tentou remover o while loop e de saída -1, sem quaisquer problemas. Quem pode me dizer por quê?
JDK informações sobre a versão:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
Isso pode ser reproduzida de forma confiável (ou não reproduzidos, dependendo do que você quiser) com openjdk version "1.8.0_222"
(usado na minha análise), OpenJDK 12.0.1
(de acordo com Oleksandr Pyrohov) e OpenJDK 13 (de acordo com Carlos Heuberger).
Eu corri o código com -XX:+PrintCompilation
tempos suficientes para obter ambos os comportamentos e aqui estão as diferenças.
implementação Buggy (exibe saída):
--- Previous lines are identical in both
54 17 3 java.lang.AbstractStringBuilder::<init> (12 bytes)
54 23 3 LoopOutPut::test (57 bytes)
54 18 3 java.lang.String::<init> (82 bytes)
55 21 3 java.lang.AbstractStringBuilder::append (62 bytes)
55 26 4 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
55 20 3 java.lang.StringBuilder::<init> (7 bytes)
56 19 3 java.lang.StringBuilder::toString (17 bytes)
56 25 3 java.lang.Integer::getChars (131 bytes)
56 22 3 java.lang.StringBuilder::append (8 bytes)
56 27 4 java.lang.String::equals (81 bytes)
56 10 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) made not entrant
56 28 4 java.lang.AbstractStringBuilder::append (50 bytes)
56 29 4 java.lang.String::getChars (62 bytes)
56 24 3 java.lang.Integer::stringSize (21 bytes)
58 14 3 java.lang.String::getChars (62 bytes) made not entrant
58 33 4 LoopOutPut::test (57 bytes)
59 13 3 java.lang.AbstractStringBuilder::append (50 bytes) made not entrant
59 34 4 java.lang.Integer::getChars (131 bytes)
60 3 3 java.lang.String::equals (81 bytes) made not entrant
60 30 4 java.util.Arrays::copyOfRange (63 bytes)
61 25 3 java.lang.Integer::getChars (131 bytes) made not entrant
61 32 4 java.lang.String::<init> (82 bytes)
61 16 3 java.util.Arrays::copyOfRange (63 bytes) made not entrant
61 31 4 java.lang.AbstractStringBuilder::append (62 bytes)
61 23 3 LoopOutPut::test (57 bytes) made not entrant
61 33 4 LoopOutPut::test (57 bytes) made not entrant
62 35 3 LoopOutPut::test (57 bytes)
63 36 4 java.lang.StringBuilder::append (8 bytes)
63 18 3 java.lang.String::<init> (82 bytes) made not entrant
63 38 4 java.lang.StringBuilder::append (8 bytes)
64 21 3 java.lang.AbstractStringBuilder::append (62 bytes) made not entrant
prazo correto (sem exibição):
--- Previous lines identical in both
55 23 3 LoopOutPut::test (57 bytes)
55 17 3 java.lang.AbstractStringBuilder::<init> (12 bytes)
56 18 3 java.lang.String::<init> (82 bytes)
56 20 3 java.lang.StringBuilder::<init> (7 bytes)
56 21 3 java.lang.AbstractStringBuilder::append (62 bytes)
56 26 4 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes)
56 19 3 java.lang.StringBuilder::toString (17 bytes)
57 22 3 java.lang.StringBuilder::append (8 bytes)
57 24 3 java.lang.Integer::stringSize (21 bytes)
57 25 3 java.lang.Integer::getChars (131 bytes)
57 27 4 java.lang.String::equals (81 bytes)
57 28 4 java.lang.AbstractStringBuilder::append (50 bytes)
57 10 3 java.lang.AbstractStringBuilder::ensureCapacityInternal (27 bytes) made not entrant
57 29 4 java.util.Arrays::copyOfRange (63 bytes)
60 16 3 java.util.Arrays::copyOfRange (63 bytes) made not entrant
60 13 3 java.lang.AbstractStringBuilder::append (50 bytes) made not entrant
60 33 4 LoopOutPut::test (57 bytes)
60 34 4 java.lang.Integer::getChars (131 bytes)
61 3 3 java.lang.String::equals (81 bytes) made not entrant
61 32 4 java.lang.String::<init> (82 bytes)
62 25 3 java.lang.Integer::getChars (131 bytes) made not entrant
62 30 4 java.lang.AbstractStringBuilder::append (62 bytes)
63 18 3 java.lang.String::<init> (82 bytes) made not entrant
63 31 4 java.lang.String::getChars (62 bytes)
Podemos notar uma diferença significativa. Com a execução correta que compilar test()
duas vezes. Uma vez no início, e mais uma vez depois (presumivelmente porque os avisos JIT quão quente o método é). Na execução de buggy test()
é compilado (ou compilado) 5 vezes.
Além disso, correndo com -XX:-TieredCompilation
(que tanto interpreta, ou usos C2
) ou com -Xbatch
(o que obriga a compilação para executar no thread principal, em vez de em paralelo), a saída é garantida e com 30000 iterações imprime um monte de coisas, de modo que o C2
compilador parece para ser o culpado. Isto é confirmado por correr com -XX:TieredStopAtLevel=1
, que desativa C2
e não produz saída (parando no nível 4 mostra o bug novamente).
Na execução correta, o método é primeiro compilado com Nível 3 compilação, em seguida, depois com Nível 4.
Na execução de buggy, as compilações anteriores são discared ( made non entrant
) e é novamente compilado no Nível 3 (que é C1
, veja o link anterior).
Por isso definitivamente é um bug no C2
, embora eu não estou absolutamente certo de que o fato de que ele vai voltar para o Nível 3 compilação afeta (e por que é que vai voltar para o nível 3, tantas incertezas ainda).
Você pode gerar o código de montagem com a seguinte linha para ir ainda mais fundo no buraco do coelho (ver também este para ativar a impressão de montagem).
java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm
Neste momento eu estou começando a ficar sem habilidades, o comportamento de buggy começa a apresentar quando as versões anteriores compilados são descartados, mas o pouco montagem habilidades que eu tenho são da década de 90, então eu vou deixar alguém mais esperto que eu levá-la daqui.
É provável que já existe um relatório de bug sobre isso, já que o código foi apresentado ao OP por outra pessoa, e como todo o código C2 não é sem erros . Espero que esta análise tem sido o mais informativo para os outros como tem sido para mim.
Como o apangin venerável apontou nos comentários, este é um bug recente . Muito obrigado a todas as pessoas interessadas e votos :)