Surpreendentemente, o seguinte código gera:
/
-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);
}
}
}
Tentei várias vezes determinar quantas vezes isso ocorreria, mas, infelizmente, era incerto, e descobri que a saída de -2 às vezes se transformava em um período. Além disso, também tentei remover o loop while e a saída -1 sem problemas. Quem pode me dizer o porquê?
Informações da versão do JDK:
HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1
Respostas:
Isso pode ser reproduzido de maneira confiável (ou não, dependendo do que você deseja) com
openjdk version "1.8.0_222"
(usado em minha análise), OpenJDK12.0.1
(de acordo com Oleksandr Pyrohov) e OpenJDK 13 (de acordo com Carlos Heuberger).Corri o código com
-XX:+PrintCompilation
tempo suficiente para obter os dois comportamentos e aqui estão as diferenças.Implementação de buggy (exibe saída):
Execução correta (sem exibição):
Podemos notar uma diferença significativa. Com a execução correta, compilamos
test()
duas vezes. Uma vez no início e mais uma vez depois (presumivelmente porque o JIT percebe o quão quente é o método). No buggy, a execuçãotest()
é compilada (ou descompilada) 5 vezes.Além disso, executando com
-XX:-TieredCompilation
(que interpreta ou usaC2
) ou com-Xbatch
(que força a compilação a executar no encadeamento principal, em vez de paralelamente), a saída é garantida e, com 30000 iterações, imprime muitas coisas, portanto oC2
compilador parece ser o culpado. Isso é confirmado com a execução de-XX:TieredStopAtLevel=1
, que desativaC2
e não produz saída (parar no nível 4 mostra o bug novamente).Na execução correta, o método é compilado primeiro com a compilação do Nível 3 e depois com o Nível 4.
Na execução do buggy, as compilações anteriores são descartadas (
made non entrant
) e são compiladas novamente no nível 3 (ou sejaC1
, consulte o link anterior).Definitivamente, é um bug
C2
, embora eu não tenha certeza absoluta de que o fato de voltar à compilação do nível 3 a afeta (e por que está voltando ao nível 3, ainda há muitas incertezas).Você pode gerar o código de montagem com a seguinte linha para ir ainda mais fundo na toca do coelho (veja também isso para ativar a impressão de montagem).
Neste ponto, estou começando a ficar sem habilidades, o comportamento do buggy começa a ser exibido quando as versões compiladas anteriores são descartadas, mas que poucas habilidades de montagem eu tenho desde os anos 90, então deixarei alguém mais esperto do que eu usá-lo daqui.
É provável que já exista um relatório de bug sobre isso, uma vez que o código foi apresentado ao OP por outra pessoa e, como todo o código C2, não há erros . Espero que essa análise tenha sido tão informativa para os outros quanto para mim.
Como o venerável apangin apontou nos comentários, este é um bug recente . Muito obrigado a todas as pessoas interessadas e prestativas :)
fonte
C2
- olhei o código do assembler gerado (e tentei entendê-lo) usando oC1
código gerado pelo JitWatch - ainda se assemelha ao bytecode,C2
é totalmente diferente (eu nem consegui encontrar a inicializaçãoi
com 8)Isso é honestamente bem estranho, pois esse código tecnicamente nunca deve ser exibido porque ...
... sempre deve resultar em
i
ser-1
(8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1). O que é ainda mais estranho é que ele nunca sai no modo de depuração do meu IDE.Curiosamente, no momento em que adiciono um cheque antes da conversão para a
String
, então não há problema ...Apenas dois pontos de boas práticas de codificação ...
String.valueOf()
.equals()
, e não o argumento, minimizando assim NullPointerExceptions.A única maneira de conseguir que isso não acontecesse era usando
String.format()
... basicamente parece que o Java precisa de um pouco de tempo para recuperar o fôlego :)
EDIT: Isso pode ser completamente coincidência, mas parece haver alguma correspondência entre o valor que está sendo impresso e a tabela ASCII .
i
=-1
, o caractere exibido é/
(valor decimal ASCII de 47)i
=-2
, o caractere exibido é.
(valor decimal ASCII de 46)i
=-3
, o caractere exibido é-
(valor decimal ASCII de 45)i
=-4
, o caractere exibido é,
(valor decimal ASCII de 44)i
=-5
, o caractere exibido é+
(valor decimal ASCII de 43)i
=-6
, o caractere exibido é*
(valor decimal ASCII de 42)i
=-7
, o caractere exibido é)
(valor decimal ASCII de 41)i
=-8
, o caractere exibido é(
(valor decimal ASCII de 40)i
=-9
, o caractere exibido é'
(valor decimal ASCII de 39)O que é realmente interessante é que o caractere no decimal ASCII 48 é o valor
0
e 48 - 1 = 47 (caractere/
), etc ...fonte
(int)'/' == 47
;(char)-1
é indefinido0xFFFF
<não é um caractere> em Unicode) #getNumericValue()
relaciona com o código fornecido ?? e como ele se converte-1
em'/'
??? Por que não'-'
,getNumericValue('-')
também é-1
??? (BTW muitos métodos retornam-1
)getNumericValue()
emvalue
(/
) para obter o valor do personagem. Você está 100% correto que o valor decimal ASCII/
deve ser 47 (era o que eu também esperava), masgetNumericValue()
estava retornando -1 nesse ponto, como eu havia adicionadoSystem.out.println(Character.getNumericValue(value.toCharArray()[0]));
. Eu posso ver a confusão a que você está se referindo e atualizou a postagem.Não sei por que o Java está fornecendo uma saída aleatória, mas o problema está na sua concatenação que falha em valores maiores
i
dentro dofor
loop.Se você substituir a
String value = i + "";
linha peloString value = String.valueOf(i) ;
seu código, funcionará conforme o esperado.A concatenação usada
+
para converter o int em string é nativa e pode ser incorreta (estranhamente, estamos fundando agora, provavelmente) e causando esse problema.Nota: Reduzi o valor de i inside for loop para 10000 e não tive problemas com
+
concatenação.Esse problema deve ser relatado aos interessados em Java e eles podem dar sua opinião sobre o mesmo.
Editar Atualizei o valor de i in for loop para 3 milhões e vi um novo conjunto de erros como abaixo:
Minha versão do Java é 8.
fonte
StringConcatFactory
(OpenJDK 13) ouStringBuilder
(Java 8)StringConcatFactory
classe. mas, tanto quanto eu sei java até java 8 java don; operador t apoio sobrecargaException in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
erro. Estranho.i + ""
é compilado exatamente comonew StringBuilder().append(i).append("").toString()
no Java 8, e usá-lo também produz a saída