2 Replies Latest reply: Jun 11, 2012 12:38 AM by suhoo RSS

    ある件数以上で発生するjdk6のループ処理の不具合について

    929851
      <環境>
      OS:Linux
      Javaバージョン:
      java version "1.6.0_22-rev"
      Java(TM) Platform, Standard Edition for Business (build 1.6.0_22-rev-b05)
      Java HotSpot(TM) 64-Bit Server VM (build 17.1-b04, mixed mode)
      -----
       上記環境で、javaで作成したバッチを実行していたのですが、以下のループ処理(ロジック①)で処理見込み件数より少ない件数でループが抜けてしまうという現象が発生しました。
      ※ 処理データ件数が3,000~4,000以上で現象発生。

      === ロジック① ===
      // "_rs"は、DBから取得したデータが格納されているResultSetオブジェクト
      // "row"は、int型変数で 1 が格納されている。
      // "count"は、int型変数で 2147483647 が格納されている。
      // "clmcnt"は、int型変数で 6 が格納されている。
      int n = 0; // 現在の行番号
      int cnt = 0; // 出力した行数
      while (_rs.next()) {
           if (++n < row) continue;
           if (++cnt > count) break;
           for (int i = 0; i < clmcnt; i++) {
                任意処理
           }
           任意処理
      }

      実際の動きの見る為に、上記ループ処理の一部を加工した以下のループ処理(ロジック②)で確認したところ、処理見込み件数 4018件全て処理されるはずが、3374件しか処理されずにループから抜けており、また後程記載するデバッグログを見ると、if文の条件判定式の結果が false にも関わらず、trueの時の処理が実行されていることが分かりました。

      === ロジック② ===
      // "_rs"は、DBから取得したデータが格納されているResultSetオブジェクト(データ件数:4018件)
      // "row"は、int型変数で 1 が格納されている。
      // "count"は、int型変数で 2147483647 が格納されている。
      // "clmcnt"は、int型変数で 6 が格納されている。
      int n = 0; // 現在の行番号
      int cnt = 0; // 出力した行数
      while (_rs.next()) {
           if (++n < row) continue;
           // デバッグログとして、以下の内容を出力
           // "判定結果=[" + (++cnt > count) + "], cnt=[" + cnt + "], count=[" + count + "], clmcnt=[" + clmcnt + "]"
           boolean resflag = (cnt > count);
           // デバッグログとして、以下の内容を出力
           // "resflag=[" + resflag + "]"
           if (resflag) {
                // デバッグログとして、以下の内容を出力
                // "[LAST]判定結果=[" + (cnt > count) + "], cnt=[" + cnt + "], count=[" + count + "], resflag=[" + resflag + "], clmcnt=[" + clmcnt + "]"
                break;
           }
           for (int i = 0; i < clmcnt; i++) {
                任意処理
           }
           任意処理
      }

      === ロジック②で出力されたデバッグログの抜粋 ===
         :
      [DEBUG] 判定結果=[false], cnt=[3373], count=[2147483647], clmcnt=[6]
      [DEBUG] resflag=[false]
      [DEBUG] 判定結果=[false], cnt=[3374], count=[2147483647], clmcnt=[6]
      [DEBUG] resflag=[false]
      [DEBUG] [LAST]判定結果=[false], cnt=[3374], count=[2147483647], resflag=[false], clmcnt=[6]

      上記に関する内容をWebで調べたところ、jdk7のループ最適化不具合に関する記事の中で、jdk6環境でも-XX:+OptimizeStringConcatか-XX:+AggressiveOptsパラメータを指定すると不具合が発生する旨の記述を見つけたのですが、バッチを実行する際の記述に該当するパラメータは特に指定しておりません。

      そこで、このケースは該当パラメータを指定していなくても同じ現象に相当するものなのでしょうか。
      もし、同じ現象に相当する場合、jdk6でバグが改修されたバージョンは、幾つになりますでしょうか。
        • 1. Re: ある件数以上で発生するjdk6のループ処理の不具合について
          suhoo
          回答にはなりませんが、この現象は jdkではなく Oracleの本体側にあるように思えます。
          PL/SQLで、以下の処理を行なったところ、同様に途中でcontinueがbreakしているようです。
          continueを使わず、以降の処理をif文で分岐させることで対応しました。

          17:01:56 SQL> set serveroutput on
          17:02:09 SQL> create global temporary table testconti
          2 ( ikey number not null )
          3 on commit preserve rows ;

          表が作成されました。

          18:04:55 SQL> begin
          2 for i in 1..30000 loop
          3 insert into testconti ( ikey ) values ( i ) ;
          4 end loop ;
          5 end ;
          6 /

          PL/SQLプロシージャが正常に完了しました。

          18:21:00 SQL> commit ;

          コミットが完了しました。

          18:21:19 SQL> select count(*) from testconti ;

          COUNT(*)
          ----------
          30000

          18:21:33 SQL> declare
          2 icnt int ;
          3 begin
          4 icnt := 0 ;
          5 for irec in (select ikey from testconti) loop
          6 icnt := icnt + 1 ;
          7 if mod( icnt,10 ) = 0 then
          8 continue ;
          9 end if ;
          10 end loop ;
          11 dbms_output.put_line( 'loop end icnt = ' || icnt ) ;
          12 end ;
          13 /
          loop end icnt = 3000

          PL/SQLプロシージャが正常に完了しました。
          • 2. Re: ある件数以上で発生するjdk6のループ処理の不具合について
            suhoo
            自己フォローですが、前回の暗黙カーソルだけでなく、
            明示カーソルを使ったFORループでも同様の不具合がありました。

            そこで、明示カーソルでさらにOPENとFETCHを記述する方式を試してみたところ、
            今度は正常に動作しました。
            PL/SQLであればこの方法を採るのが最良ですが、jdkとなると、対応方法は
            「continueを使わない」ということになるのでしょうか。

            14:18:37 SQL> set serveroutput on
            14:18:46 SQL> select count(*) from testconti ;

            COUNT(*)
            ----------
            30000

            14:19:06 SQL> declare
            2 icnt int ;
            3 cursor ctest is select ikey from testconti;
            4 irec ctest%rowtype ;
            5 BEGIN
            6 icnt := 0 ;
            7 for irec in ctest loop
            8 icnt := icnt + 1 ;
            9 if mod( icnt,7 ) = 0 then
            10 continue ;
            11 end if ;
            12 if mod( icnt,100 ) = 0 then
            13 dbms_output.put_line( 'mod 100 icnt = ' || icnt ) ;
            14 end if ;
            15 end loop ;
            16 dbms_output.put_line( 'loop end icnt = ' || icnt ) ;
            17 end ;
            18 /
            mod 100 icnt = 100
            mod 100 icnt = 200
            mod 100 icnt = 300
            mod 100 icnt = 400
            mod 100 icnt = 500
            mod 100 icnt = 600
            mod 100 icnt = 800
            mod 100 icnt = 900
            mod 100 icnt = 1000
            mod 100 icnt = 1100
            mod 100 icnt = 1200
            mod 100 icnt = 1300
            mod 100 icnt = 1500
            mod 100 icnt = 1600
            mod 100 icnt = 1700
            mod 100 icnt = 1800
            mod 100 icnt = 1900
            mod 100 icnt = 2000
            loop end icnt = 2100

            PL/SQLプロシージャが正常に完了しました。

            14:21:37 SQL> declare
            2 icnt int ;
            3 cursor ctest is select ikey from testconti;
            4 irec ctest%rowtype ;
            5 BEGIN
            6 icnt := 0 ;
            7 open ctest ;
            8 loop
            9 fetch ctest into irec ;
            10 exit when ctest%notfound ;
            11 icnt := icnt + 1 ;
            12 if mod( icnt,7 ) = 0 then
            13 continue ;
            14 end if ;
            15 if mod( icnt,100 ) = 0 then
            16 dbms_output.put_line( 'mod 100 icnt = ' || icnt ) ;
            17 end if ;
            18 end loop ;
            19 close ctest ;
            20 dbms_output.put_line( 'loop end icnt = ' || icnt ) ;
            21 end ;
            22 /
            mod 100 icnt = 100
            mod 100 icnt = 200
            mod 100 icnt = 300
            <<略>>
            mod 100 icnt = 29800
            mod 100 icnt = 29900
            mod 100 icnt = 30000
            loop end icnt = 30000

            PL/SQLプロシージャが正常に完了しました。