เอาต์พุต -1 กลายเป็นสแลชในลูป


54

น่าแปลกที่รหัสต่อไปนี้ส่งออก:

/
-1

รหัส:

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

}

ฉันพยายามหลายครั้งเพื่อกำหนดว่าจะเกิดเหตุการณ์นี้กี่ครั้ง แต่โชคไม่ดีในที่สุดมันก็ไม่แน่นอนและฉันพบว่าบางครั้งผลลัพธ์ของ -2 กลายเป็นช่วงเวลา นอกจากนี้ฉันยังพยายามลบ while loop และ output -1 โดยไม่มีปัญหา ใครสามารถบอกฉันได้ว่าทำไม


ข้อมูลรุ่น JDK:

HopSpot 64-Bit 1.8.0.171
IDEA 2019.1.1

2
ความคิดเห็นไม่ได้มีไว้สำหรับการอภิปรายเพิ่มเติม การสนทนานี้ได้รับการย้ายไปแชท
Samuel Liew

คำตอบ:


36

นี้สามารถทำซ้ำได้อย่างน่าเชื่อถือ (หรือไม่ทำซ้ำขึ้นอยู่กับสิ่งที่คุณต้องการ) ด้วยopenjdk version "1.8.0_222"(ใช้ในการวิเคราะห์ของฉัน), OpenJDK12.0.1 (ตาม Oleksandr Pyrohov) และ OpenJDK 13 (ตามคาร์ลอส Heuberger)

ฉันรันโค้ดด้วย -XX:+PrintCompilationเวลาเพียงพอที่จะรับทั้งพฤติกรรมและนี่คือความแตกต่าง

การใช้งาน Buggy (แสดงผลลัพธ์):

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

เรียกใช้ที่ถูกต้อง (ไม่มีจอแสดงผล):

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

เราสามารถสังเกตเห็นความแตกต่างที่สำคัญอย่างหนึ่ง ด้วยการดำเนินการที่ถูกต้องเรารวบรวมtest()สองครั้ง ครั้งแรกในการเริ่มต้นและอีกครั้งหลังจากนั้น (น่าจะเป็นเพราะ JIT สังเกตเห็นวิธีการที่ร้อน) ในการดำเนินการรถมีการtest()รวบรวม (หรือ decompiled) 5ครั้ง

นอกจากนี้การรันด้วย-XX:-TieredCompilation(ซึ่งแปลหรือใช้C2) หรือด้วย-Xbatch(ซึ่งบังคับให้คอมไพล์รันในเธรดหลักแทนที่จะเป็นพารัลลี่) ผลลัพธ์ถูกรับประกันและด้วยการวนซ้ำ 30000 พิมพ์สิ่งต่าง ๆ มากมายดังนั้นC2คอมไพเลอร์จึงดูเหมือนว่า เป็นผู้กระทำผิด สิ่งนี้ได้รับการยืนยันจากการรันด้วย-XX:TieredStopAtLevel=1ซึ่งปิดใช้C2และไม่สร้างเอาต์พุต (การหยุดที่ระดับ 4 แสดงข้อผิดพลาดอีกครั้ง)

ในการดำเนินการที่ถูกต้องวิธีการจะถูกรวบรวมครั้งแรกด้วย ระดับ 3จากนั้นตามด้วยระดับ 4

ในการดำเนินการแบบบั๊กการรวบรวมก่อนหน้านี้จะถูกแยกออก ( made non entrant) และจะถูกรวบรวมอีกครั้งในระดับ 3 (ซึ่งก็คือC1ดูลิงค์ก่อนหน้า)

ดังนั้นมันจึงเป็นข้อบกพร่องอย่างแน่นอน C2ถึงแม้ว่าฉันจะไม่แน่ใจจริง ๆ ว่าการกลับไปสู่การรวบรวมระดับ 3 มีผลกับมันหรือไม่

คุณสามารถสร้างรหัสแอสเซมบลีด้วยบรรทัดต่อไปนี้เพื่อเจาะลึกลงไปในรูกระต่าย (ดูที่นี่เพื่อเปิดใช้งานการพิมพ์แอสเซมบลี)

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

ณ จุดนี้ฉันเริ่มหมดทักษะพฤติกรรมรถเริ่มแสดงเมื่อรุ่นที่รวบรวมก่อนหน้านี้ถูกทิ้ง แต่สิ่งที่ทักษะการชุมนุมเล็ก ๆ ที่ฉันได้มาจากยุค 90 ดังนั้นฉันจะให้ใครฉลาดกว่าฉันใช้มัน จากที่นี่.

อาจเป็นไปได้ว่ามีรายงานข้อผิดพลาดเกี่ยวกับเรื่องนี้อยู่แล้วเนื่องจากมีการนำเสนอรหัสต่อ OP โดยคนอื่นและเนื่องจากรหัสC2ทั้งหมดไม่มีข้อบกพร่องไม่ได้โดยไม่มีข้อบกพร่อง ฉันหวังว่าการวิเคราะห์นี้จะให้ข้อมูลกับคนอื่น ๆ เหมือนที่ฉันได้รับ

ในฐานะที่เป็นที่เคารพ apangin ชี้ให้เห็นในความคิดเห็นนี้เป็นข้อผิดพลาดที่ผ่านมา จำเป็นอย่างยิ่งต่อผู้ที่สนใจและเป็นประโยชน์ :)


ผมยังคิดว่ามันเป็นC2- ได้ดูรหัสประกอบสร้างขึ้น (และพยายามที่จะเข้าใจมัน) โดยใช้ JitWatch - C1สร้างโค้ดยังคงคล้ายกับ bytecode, C2มีความแตกต่างกันโดยสิ้นเชิง (ฉันจะไม่ได้พบกับการเริ่มต้นของi8)
user85421-ห้าม

คำตอบของคุณดีมากฉันพยายามปิดการใช้งาน c2 ผลลัพธ์ถูกต้อง อย่างไรก็ตามโดยทั่วไปพารามิเตอร์เหล่านี้ส่วนใหญ่จะเป็นค่าเริ่มต้นในโครงการแม้ว่าโครงการจริงจะไม่มีรหัสข้างต้น แต่มีแนวโน้มว่าจะมีรหัสที่คล้ายกันหากโครงการใช้รหัสที่คล้ายกันมันแย่มากจริงๆ
okali

1
@Eugene นี่เป็นสิ่งที่ค่อนข้างยุ่งยากฉันแน่ใจว่ามันจะเป็นอะไรที่คล้ายกับ eclipse compiler bug หรือสิ่งที่คล้ายกัน ... และฉันก็ไม่สามารถทำซ้ำได้ในตอนแรก ..
Kayaman

1
@ Kayay เห็นด้วย การวิเคราะห์ที่คุณทำนั้นดีมากมันน่าจะเพียงพอสำหรับ apangin ในการอธิบายและแก้ไขปัญหานี้ ช่างเป็นตอนเช้าที่เหลือเชื่อบนรถไฟ!
ยูจีน

7
ฉันสังเกตเห็นหัวข้อนี้โดยบังเอิญเท่านั้น เพื่อให้แน่ใจว่าฉันเห็นคำถามให้ใช้ @mentions หรือเพิ่มแท็ก #jvm การวิเคราะห์ที่ดี BTW แท้จริงนี่คือข้อผิดพลาด C2 คอมไพเลอร์คงมีเพียงไม่กี่วันที่ผ่านมา - JDK-8231988
apangin

4

นี่เป็นเรื่องที่ค่อนข้างแปลกเพราะรหัสนั้นไม่ควรส่งออกเพราะ ...

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

... มักจะส่งผลให้iเป็น-1 (8 - 3 = 5; 5 - 3 = 2; 2 - 3 = -1) สิ่งที่น่าแปลกกว่าคือมันไม่เคยส่งออกในโหมดดีบักของ IDE ของฉัน

ที่น่าสนใจคือช่วงเวลาที่ฉันเพิ่มเช็คก่อนการแปลงเป็น a Stringจากนั้นก็ไม่มีปัญหา ...

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

เพียงสองจุดของการฝึกการเขียนโปรแกรมที่ดี ...

  1. ค่อนข้างใช้ String.valueOf()
  2. มาตรฐานการเข้ารหัสบางอย่างระบุว่าตัวอักษร String ควรเป็นเป้าหมายของ.equals()แทนที่จะเป็นอาร์กิวเมนต์จึงลด NullPointerExceptions

วิธีเดียวที่ฉันได้รับสิ่งนี้ไม่ให้เกิดขึ้นคือการใช้ 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);
  }
}

... โดยพื้นฐานแล้วมันดูเหมือนว่า Java ต้องการเวลาเล็กน้อยในการสูดลมหายใจ :)

แก้ไข: นี้อาจจะเป็นเรื่องบังเอิญอย่างสมบูรณ์ แต่มีไม่ดูเหมือนจะมีการติดต่อระหว่างค่าที่พิมพ์ออกมาและบางตาราง ASCII

  • i= -1, อักขระที่แสดงคือ/(ค่าทศนิยม ASCII เท่ากับ 47)
  • i= -2, อักขระที่แสดงคือ.(ค่าทศนิยม ASCII เท่ากับ 46)
  • i= -3, อักขระที่แสดงคือ-(ค่าทศนิยม ASCII เท่ากับ 45)
  • i= -4, อักขระที่แสดงคือ,(ค่าทศนิยม ASCII เท่ากับ 44)
  • i= -5, อักขระที่แสดงคือ+(ค่าทศนิยม ASCII เท่ากับ 43)
  • i= -6, อักขระที่แสดงคือ*(ค่าทศนิยม ASCII เท่ากับ 42)
  • i= -7, อักขระที่แสดงคือ)(ค่าทศนิยม ASCII เท่ากับ 41)
  • i= -8, อักขระที่แสดงคือ((ค่าทศนิยม ASCII เท่ากับ 40)
  • i= -9, อักขระที่แสดงคือ'(ค่าทศนิยม ASCII เท่ากับ 39)

สิ่งที่น่าสนใจจริงๆก็คือตัวละครที่ ASCII ทศนิยม 48 คือค่า0และ 48 - 1 = 47 (ตัวอักษร/) ฯลฯ


1
ค่าตัวเลขของอักขระ "/" คือ "-1" ??? สิ่งนี้มาจากไหน ( (int)'/' == 47; (char)-1ไม่ได้กำหนด0xFFFFคือ <ไม่ใช่ตัวอักษร> ใน Unicode)
user85421- ถูกแบน

1
ถ่าน c = '/'; int a = Character.getNumericValue (c); System.out.println (ก);
Ambro-r

วิธีการที่ไม่getNumericValue()เกี่ยวข้องกับรหัสที่กำหนด ??? และมันจะแปลง-1เป็น'/'??? ทำไมไม่ไป'-', getNumericValue('-')นอกจากนี้ยัง-1??? (BTW กลับมาเป็นจำนวนมาก-1)
85421- ถูกแบน

@CarlosHeuberger ฉันทำงานgetNumericValue()บนvalue( /) เพื่อรับค่าตัวอักษร คุณอยู่ที่ถูกต้อง 100% ว่า ASCII ค่าทศนิยมของ/ที่ควรจะเป็น 47 (มันเป็นสิ่งที่ผมยังคาดหวังว่า) แต่getNumericValue()กำลังจะกลับ -1 System.out.println(Character.getNumericValue(value.toCharArray()[0]));ที่จุดที่เป็นฉันได้เพิ่ม ฉันเห็นความสับสนที่คุณอ้างถึงและอัปเดตโพสต์แล้ว
Ambro-r

1

ไม่ทราบว่าทำไม Java จะให้ผลผลิตสุ่มดังกล่าว แต่ที่เป็นปัญหาในการเรียงต่อกันของคุณที่ล้มเหลวสำหรับค่าขนาดใหญ่ของiภายในforวง

หากคุณแทนที่String value = i + "";บรรทัดด้วยString value = String.valueOf(i) ;รหัสของคุณทำงานตามที่คาดไว้

การต่อข้อมูลที่ใช้+เพื่อแปลง int เป็น string นั้นเป็นภาษาดั้งเดิมและอาจเป็นรถ (แปลกที่เราพบตอนนี้อาจ) และก่อให้เกิดปัญหาดังกล่าว

หมายเหตุ: ฉันลดค่า i ภายในสำหรับวนซ้ำเป็น 10,000 และฉันไม่ประสบปัญหากับการ+ต่อข้อมูล

ปัญหานี้จะต้องรายงานต่อผู้มีส่วนได้เสียของ Java & พวกเขาสามารถให้ความเห็นในเรื่องเดียวกัน

แก้ไขฉันอัปเดตค่าของ i ในการวนซ้ำเป็น 3 ล้านและเห็นข้อผิดพลาดชุดใหม่ดังนี้:

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)

Java เวอร์ชันของฉันคือ 8


1
ฉันไม่คิดว่าการต่อสตริงเป็นภาษาพื้นเมือง - มันใช้StringConcatFactory(OpenJDK 13) หรือStringBuilder(Java 8)
user85421- แบน

@CarlosHeuberger เป็นไปได้เช่นกัน ฉันคิดว่ามันมาจาก java 9 ถ้ามันต้องเป็นStringConcatFactory คลาส แต่เท่าที่ฉันรู้ java จนถึง java 8 java don; t สนับสนุนผู้ประกอบการมากไป
Vinay Prajapati

@Vayay ลองสิ่งนี้ด้วยและใช่มันใช้งานได้ แต่เมื่อคุณเพิ่มลูปจาก 30,000 เพื่อบอกว่า 3000000 คุณเริ่มมีปัญหาเดียวกัน
Ambro-r

@ Ambro-r ฉันลองด้วยค่าที่แนะนำของคุณและฉันได้รับException in thread "main" java.lang.ArrayIndexOutOfBoundsException: -1ข้อผิดพลาด แปลก.
Vinay Prajapati

3
i + ""ถูกคอมไพล์เหมือนกับnew StringBuilder().append(i).append("").toString()ใน Java 8 และการใช้นั้นในที่สุดก็สร้างผลลัพธ์เช่นกัน
user85421-Banned
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.