Đầu ra -1 trở thành dấu gạch chéo trong vòng lặp


54

Đáng ngạc nhiên, các mã đầu ra sau đây:

/
-1

Mật mã:

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);
        }
    }

}

Tôi đã cố gắng nhiều lần để xác định điều này sẽ xảy ra bao nhiêu lần, nhưng thật không may, cuối cùng nó không chắc chắn và tôi thấy rằng đầu ra của -2 đôi khi biến thành một khoảng thời gian. Ngoài ra, tôi cũng đã cố gắng loại bỏ vòng lặp while và đầu ra -1 mà không gặp vấn đề gì. Ai có thể cho tôi biết tại sao?


Thông tin phiên bản JDK:

HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1

2
Bình luận không dành cho thảo luận mở rộng; cuộc trò chuyện này đã được chuyển sang trò chuyện .
Samuel Liew

Câu trả lời:


36

Điều này có thể được sao chép một cách đáng tin cậy (hoặc không được sao chép, tùy thuộc vào những gì bạn muốn) với openjdk version "1.8.0_222"(được sử dụng trong phân tích của tôi), OpenJDK 12.0.1(theo Oleksandr Pyrohov) và OpenJDK 13 (theo Carlos Heuberger).

Tôi đã chạy mã với -XX:+PrintCompilationđủ thời gian để có được cả hai hành vi và đây là sự khác biệt.

Triển khai lỗi (hiển thị đầu ra):

 --- 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

Chạy đúng (không hiển thị):

 --- 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)

Chúng ta có thể nhận thấy một sự khác biệt đáng kể. Với việc thực hiện chính xác, chúng tôi biên dịch test()hai lần. Một lần vào đầu và một lần nữa sau đó (có lẽ vì JIT thông báo phương pháp này nóng đến mức nào). Trong thực thi lỗi test()được biên dịch (hoặc dịch ngược) 5 lần.

Ngoài ra, chạy với -XX:-TieredCompilation(mà là phiên dịch hoặc sử dụng C2) hoặc với -Xbatch(buộc phải biên dịch chạy trong luồng chính, thay vì song song), đầu ra được đảm bảo và với 30000 lần lặp in ra rất nhiều thứ, vì vậy C2trình biên dịch dường như là thủ phạm Điều này được xác nhận bằng cách chạy với -XX:TieredStopAtLevel=1, điều này vô hiệu hóa C2và không tạo ra đầu ra (dừng ở mức 4 sẽ hiển thị lại lỗi).

Trong thực thi chính xác, phương pháp đầu tiên được biên dịch với biên dịch cấp 3 , sau đó với cấp độ 4.

Trong quá trình thực thi lỗi, các phần tổng hợp trước đó bị loại bỏ ( made non entrant) và nó lại được biên dịch ở Cấp độ 3 (nghĩa là C1xem liên kết trước đó).

Vì vậy, nó chắc chắn là một lỗi trong C2, mặc dù tôi không chắc chắn liệu thực tế rằng việc quay lại cấp độ 3 có ảnh hưởng đến nó hay không (và tại sao nó lại quay trở lại cấp 3, vẫn còn nhiều điều không chắc chắn).

Bạn có thể tạo mã lắp ráp với dòng sau để đi sâu hơn vào lỗ thỏ (cũng xem phần này để cho phép in lắp ráp).

java -XX:+PrintCompilation -Xbatch -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly LoopOutPut > broken.asm

Tại thời điểm này, tôi bắt đầu cạn kiệt các kỹ năng, hành vi lỗi bắt đầu xuất hiện khi các phiên bản được biên dịch trước đó bị loại bỏ, nhưng tôi có những kỹ năng lắp ráp nhỏ nào từ những năm 90, vì vậy tôi sẽ để ai đó thông minh hơn tôi. từ đây.

Có khả năng đã có báo cáo lỗi về điều này, vì mã được người khác trình bày cho OP và vì tất cả mã C2 không có lỗi . Tôi hy vọng phân tích này đã được cung cấp thông tin cho những người khác như nó đã được với tôi.

Như apangin đáng kính đã chỉ ra trong các ý kiến, đây là một lỗi gần đây . Rất nhiều nghĩa vụ cho tất cả những người quan tâm và hữu ích :)


Tôi cũng nghĩ rằng C2- đã xem xét mã trình biên dịch được tạo (và đã cố hiểu nó) bằng cách sử dụng JitWatch - C1mã được tạo vẫn giống mã byte, C2hoàn toàn khác (tôi thậm chí không thể tìm thấy khởi tạo ivới 8)
user85421-B cấm

Câu trả lời của bạn rất tốt, tôi đã thử, vô hiệu hóa c2, kết quả là chính xác. Tuy nhiên, nói chung, hầu hết các tham số này là mặc định trong dự án, mặc dù dự án thực tế sẽ không có mã ở trên, nhưng nó có khả năng có mã tương tự, nếu dự án sử dụng mã tương tự, nó thực sự khủng khiếp
okali

1
@Eugene đây là một vấn đề khá khó khăn, tôi chắc chắn rằng nó sẽ giống như lỗi trình biên dịch nhật thực hoặc tương tự ... và tôi không thể sao chép nó lúc đầu ..
Kayaman

1
@Kayaman đồng ý. Các phân tích bạn thực hiện là rất tốt, nó nên là đủ để apangin giải thích và khắc phục điều này. Thật là một buổi sáng tuyệt vời trên tàu!
Eugene

7
Tôi nhận thấy chủ đề này chỉ vô tình. Để đảm bảo tôi thấy câu hỏi, hãy sử dụng @mentions hoặc thêm thẻ #jvm. Phân tích tốt, BTW. Đây thực sự là một lỗi trình biên dịch C2, chỉ được sửa vài ngày trước - JDK-8231988 .
apangin

4

Điều này thực sự khá kỳ lạ, vì mã đó về mặt kỹ thuật sẽ không bao giờ xuất ra vì ...

int i = 8;
while ((i -= 3) > 0);

... Luôn luôn dẫn đến kết quả i-1(8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1). Điều thậm chí kỳ lạ hơn là nó không bao giờ xuất ra trong chế độ gỡ lỗi của IDE của tôi.

Thật thú vị, thời điểm tôi thêm một kiểm tra trước khi chuyển đổi thành a String, thì không vấn đề gì ...

public void test() {
  int i = 8;
  while ((i -= 3) > 0);
  if(i != -1) { System.out.println("Not -1"); }
  String value = String.valueOf(i);
  if (!"-1".equalsIgnoreCase(value)) {
    System.out.println(value);
    System.out.println(i);
  }
}

Chỉ cần hai điểm thực hành mã hóa tốt ...

  1. Thay vì sử dụng String.valueOf()
  2. Một số tiêu chuẩn mã hóa xác định rằng chuỗi ký tự chuỗi phải là mục tiêu của .equals(), chứ không phải là đối số, do đó giảm thiểu NullPulumExceptions.

Cách duy nhất tôi có được điều này không xảy ra là sử dụng String.format()

public void test() {
  int i = 8;
  while ((i -= 3) > 0);
  String value = String.format("%d", i);
  if (!"-1".equalsIgnoreCase(value)) {
    System.out.println(value);
    System.out.println(i);
  }
}

... về cơ bản, có vẻ như Java cần một chút thời gian để bắt nhịp :)

EDIT: Điều này có thể hoàn toàn ngẫu nhiên, nhưng dường như có một số tương ứng giữa giá trị được in ra và Bảng ASCII .

  • i= -1, ký tự được hiển thị là /(giá trị thập phân ASCII là 47)
  • i= -2, ký tự được hiển thị là .(giá trị thập phân ASCII là 46)
  • i= -3, ký tự được hiển thị là -(giá trị thập phân ASCII là 45)
  • i= -4, ký tự được hiển thị là ,(giá trị thập phân ASCII là 44)
  • i= -5, ký tự được hiển thị là +(giá trị thập phân ASCII là 43)
  • i= -6, ký tự được hiển thị là *(giá trị thập phân ASCII là 42)
  • i= -7, ký tự được hiển thị là )(giá trị thập phân ASCII là 41)
  • i= -8, ký tự được hiển thị là ((giá trị thập phân ASCII là 40)
  • i= -9, ký tự được hiển thị là '(giá trị thập phân ASCII là 39)

Điều thực sự thú vị là ký tự ở ASCII thập phân 48 là giá trị 0và 48 - 1 = 47 (ký tự /), v.v ...


1
Giá trị bằng số của ký tự "/" là "-1" ??? Trường hợp nào này đến từ đâu? ( (int)'/' == 47; (char)-1không xác định 0xFFFFlà <không phải là ký tự> trong Unicode)
user85421-Cấm

1
char c = '/'; int a = Character.getNumericValue (c); System.out.println (a);
Ambro-r

Làm thế nào không getNumericValue()liên quan đến mã đã cho ??? và làm thế nào để chuyển đổi -1thành '/'??? Tại sao không '-', getNumericValue('-')cũng được -1??? (BTW rất nhiều phương thức trả về -1)
user85421-Cấm

@CarlosHeuberger, tôi đã chạy getNumericValue()trên value( /) để lấy giá trị ký tự. Bạn đúng 100% rằng giá trị thập phân ASCII /phải là 47 (đó là điều tôi cũng mong đợi), nhưng getNumericValue()đã trả về -1 tại thời điểm đó như tôi đã thêm System.out.println(Character.getNumericValue(value.toCharArray()[0]));. Tôi có thể thấy sự nhầm lẫn mà bạn đang đề cập và đã cập nhật bài viết.
Ambro-r

1

Không biết tại sao Java là cho sản lượng ngẫu nhiên như vậy nhưng vấn đề là trong nối của bạn mà không cho các giá trị lớn hơn ibên trong forvòng lặp.

Nếu bạn thay thế String value = i + "";dòng với String value = String.valueOf(i) ;mã của bạn hoạt động như mong đợi.

Sự kết hợp sử dụng +để chuyển đổi int thành chuỗi là nguồn gốc và có thể có lỗi (Điều kỳ lạ là chúng tôi hiện đang tìm thấy nó) và gây ra vấn đề như vậy.

Lưu ý: Tôi đã giảm giá trị của i bên trong vòng lặp xuống 10000 và tôi không gặp phải vấn đề gì với việc +ghép nối.

Vấn đề này phải được báo cáo cho các bên liên quan Java và họ có thể đưa ra ý kiến ​​tương tự.

Chỉnh sửa Tôi đã cập nhật giá trị của i trong vòng lặp lên 3 triệu và thấy một loạt lỗi mới như dưới đây:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1
    at java.lang.Integer.getChars(Integer.java:463)
    at java.lang.Integer.toString(Integer.java:402)
    at java.lang.String.valueOf(String.java:3099)
    at solving.LoopOutPut.test(LoopOutPut.java:16)
    at solving.LoopOutPut.main(LoopOutPut.java:8)

Phiên bản Java của tôi là 8.


1
Tôi không nghĩ rằng nối chuỗi là nguồn gốc - nó chỉ sử dụng StringConcatFactory(OpenJDK 13) hoặc StringBuilder(Java 8)
user85421-B cấm

@CarlosHeuberger Có thể quá. Tôi nghĩ đó là từ java 9 nếu nó phải là StringConcatFactory lớp. nhưng theo như tôi biết java cho đến java 8 java không hỗ trợ quá tải toán tử
Vinay Prajapati

@Vinay, cũng đã thử điều này và có nó hoạt động, nhưng thời điểm bạn tăng vòng lặp từ 30000 lên 3000000 bạn bắt đầu gặp vấn đề tương tự.
Ambro-r

@ Ambro-r Tôi đã thử với giá trị đề xuất của bạn và tôi gặp Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1lỗi. Lạ thật.
Vinay Prajapati

3
i + ""được biên dịch chính xác như new StringBuilder().append(i).append("").toString()trong Java 8 và sử dụng cuối cùng cũng tạo ra đầu ra
user85421-B cấm
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.