JDK-4987749 : ArrayOutOfBounds Exception on LinkedList with Mutliple Thread WITH -server
  • Type: Bug
  • Component: hotspot
  • Sub-Component: compiler
  • Affected Version: 5.0
  • Priority: P3
  • Status: Resolved
  • Resolution: Fixed
  • OS: linux
  • CPU: x86
  • Submitted: 2004-02-03
  • Updated: 2004-04-06
  • Resolved: 2004-04-06
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other Other
1.4.2_19-revFixed 1.4.2_20Fixed
Related Reports
Relates :  
Description

Name: jl125535			Date: 02/03/2004


FULL PRODUCT VERSION :
Tested VMs:
 *      1.4.1_03-b02 - does not fail
 *      1.4.2_02-b03 - fails
 *      1.4.2_02-b28 - fails
 *      1.4.2_03-b02 - fails

java version "1.5.0-beta2"
Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0-beta2-b36)
Java HotSpot(TM) Client VM (build 1.5.0-beta2-b36, mixed mode)

FULL OS VERSION :
Redhat Linux 8, kernel v 2.4.24
Windows 2000 SP2
Windows XP SP1

EXTRA RELEVANT SYSTEM CONFIGURATION :
Tested hardware:
 * Linux - Desktop Intel P-II 333Mhz, 396MB RAM PC33, Legend Mobo LX440
 * Windows 2000 - Desktop  AMD Athlon 2500+, 512MB RAM PC4000, Asus Mobo
 * Windows XP -  AMD Athlon 1Ghz on a Sony Vaio laptop, 512MB ram

A DESCRIPTION OF THE PROBLEM :
When operating on LinkedLists in a multithread environment such as a J2EE server (JBoss in our case), with the "-server" option turned on, there the toArray(Object []) method of the LinkedList class throws occasional ArrayOutOfBounds errors. There are no synchronization issues since the variables are local to each thread. This problem DOES NOT happen on the "-client" HotSpot VM. This error DOES NOT OCCUR on JVM 1.4.1 under Linux.

In our understanding of the server VM document (http://wwws.sun.com/software/solaris/java/wp-hotspot/#pgfId=1082013), this can be related to the following optimization:

"Range check elimination -- The Java programming language specification requires array bounds checking to be performed with each array access. An index bounds check can be eliminated when the compiler can prove that an index used for an array access is within bounds."


STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Have multiple threads operate on local LinkedList variables USING the toArray method

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
Starting
Outer Run Starting: 0
Outer Run Waiting: 0
Outer Run Finished: 0
Outer Run Starting: 1
Outer Run Waiting: 1
Outer Run Finished: 1
[...]
Outer Run Starting: 9
Outer Run Waiting: 9
Outer Run Finished: 9
Done

ACTUAL -
The following exception is thrown very often:

java.lang.ArrayIndexOutOfBoundsException: 591
        at java.util.LinkedList.toArray(LinkedList.java:657)
        at Test$RunThread.run(Tin theest.java:153)

ERROR MESSAGES/STACK TRACES THAT OCCUR :
java.lang.ArrayIndexOutOfBoundsException: 591
        at java.util.LinkedList.toArray(LinkedList.java:657)
        at Test$RunThread.run(Test.java:153)

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.LinkedList;

/**
 * Test case to show ArrayIndexOutOfBoundsException bug on
 * toArray(Object[]) call to a LinkedList. This only happens in
 * a multithreaded environment and running on a server JVM.
 *
 * Tested VMs:
 *      1.4.1_03-b02 - does not fail
 *      1.4.2_02-b03 - fails
 *      1.4.2_02-b28 - fails
 *      1.4.2_03-b02 - fails
 *
 * Tested OSs:
 *      Redhat Linux 8 with 2.4.24 kernel
 *      Windows 2000 SP 2
 *      Windows XP SP1
 *
 * Tested hardware:
 *      Intel P-II 333Mhz, 396MB RAM PC33, Legend Mobo LX440
 *      AMD Athlon 2500+, 512MB RAM PC4000, Asus Mobo
 *      AMD Athlon 1Ghz on a Sony Vaio
 *
 * To execute this test run:
 *      java.exe -server Test
 *
 * A log file will be created along with standard output. If this
 * test does not fail for you right away increase the number of runs
 * and threads and try again.
 *
 */
public class Test {
    
    //main entry
    public static void main(String[] args)  throws IOException {
        Test test = new Test();
        test.test();
        
    }//main()
    
    //main run method
    public void test() throws IOException {
        
        //modify the following params to increase the number
        //of inner and outer iterations as well as the thread count
        int runs = 1000;
        int threads = 100;
        int outerRuns = 20;
        
        
        //create counter and synchronizer
        Counter cnt = new Counter("test.log");
        cnt.status("Starting");
        
        try {
            
            //iterate through outer runs
            for(int x = 0; x < 10; x++) {
                cnt.status("Outer Run Starting: " + x);
                
                //create and initialize all threads
                RunThread[] rt = new RunThread[threads];
                for(int i = 0; i < threads; i++) {
                    rt[i] = new RunThread("Thread " + i, runs, cnt);
                    rt[i].start();
                }//for
                
                //tell all threads they can start working
                cnt.run();
                cnt.status("Outer Run Waiting: " + x);
                
                //wait for about 30 seconds for all of them to complete
                int c = 0;
                while(cnt.count > 0) {
                    c++;
                    try { Thread.sleep(200); }
                    catch(Throwable e) { e.printStackTrace(); }
                    
                    if(c > 1500) {
                        cnt.stop();
                        cnt.status("Waited too long, aborting");
                    }//if
                }//while
                
                //flag stop to all threads and get ready to
                //reinitialize
                cnt.stop();
                cnt.status("Outer Run Finished: " + x);
            }//for
        } catch(Throwable e) {
            
            //catch outer iteration exceptions
            cnt.status("Outer Failure:");
            cnt.error(e);
        
        }//try..catch
        
        //finish up, close log file
        cnt.status("Done");
        cnt.stop();
        cnt.done();
    }//main()
    
    
    //main test thread
    private class RunThread extends Thread {
        protected int runs;
        protected String name;
        protected Counter cnt;
        
        public RunThread(String n, int r, Counter c) {
            name = n;
            runs = r;
            cnt = c;
            
        }//RunThread()

        
        //main inner iteration method
        public void run() {
            
            //tell counter we are ready to start
            cnt.inc(1);
                        
            //wait for counter to say its ok to start
            while(!cnt.isRunning()) {
                try { sleep(100); }
                catch(Throwable e) { e.printStackTrace(); }
            }//while
            
            //initialize local vars
            int lsize = -1;
            LinkedList lst = new LinkedList();
            Integer[] last = null;
            
            try {
                
                //main inner iteration loop
                for(int i = 0; i < runs && cnt.isRunning(); i++) {
                    
                    //add to list
                    lst.add(new Integer(i+1));
                    
                    //record size prior to toArray call
                    lsize = lst.size();
                    last = new Integer[lst.size()];
                    
                    //place toArrayCall
                    last = (Integer[]) lst.toArray(last);
                                
                }//for
                
                //this is a sucessfull complete, tell counter we are done
                cnt.inc(-1);
            } catch(Throwable e) {
                
                //this is an error complete, still tell counter we are done
                cnt.inc(-1);
                
                //try to log the error
                try {
                    
                    //make sure no one else is writing to the log
                    //at the same time
                    synchronized(cnt) {
                        
                        //record failed thread, sizes, error, and list contents
                        cnt.status("");
                        cnt.status(name + ": Failed");
                        cnt.status("Last Size: " + lsize);
                        cnt.status("Reported Size: " + lst.size());
                        cnt.status("Feed Size: " + last.length);
                        cnt.error(e);
                        cnt.status("Dump: " + lst.toString());
                        cnt.status("");
                        
                    }//syncronized
                    
                } catch(Throwable ee) { ee.printStackTrace(); }
            }//try..catch
        }
    }
    
    
    //main counter and synchronizer, takes care of waiting and logs
    protected class Counter {
        int count = 0;
        boolean running = false;
        PrintWriter buff; //log buffer
        
        //initialize log file
        public Counter(String file) throws IOException {
            buff = new PrintWriter(new BufferedWriter(new FileWriter(file)));
            
        }//Counter()
        
        //run state methods
        public synchronized void run() { running = true; }
        public synchronized void stop() { running = false; }
        public synchronized boolean isRunning() { return running; }
        
        //running count increment/decrement method
        public synchronized void inc(int i) { count += i; }
        
        //status output method
        public synchronized void status(String str) throws IOException {
            System.out.println(str);
            buff.println(str);
            buff.flush();
            
        }//status();
        
        //error output method
        public synchronized void error(Throwable e) {
            e.printStackTrace();
            e.printStackTrace(buff);
            buff.flush();
        }//status();
        
        //finishing method, log file close
        public synchronized void done() {
            buff.flush();
            buff.close();
            
        }//done()
    }//Counter
}//Test

---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
1. Use the "-client" VM instead - in our case with J2EE server, there is performance degradation.
2. Use the ListIterator and create the output array manually. Much slower than the toArray(Object) method of LinkedList if using custom objects.
(Incident Review ID: 233971) 
======================================================================

Comments
CONVERTED DATA BugTraq+ Release Management Values COMMIT TO FIX: tiger-beta2 FIXED IN: tiger-beta2 INTEGRATED IN: tiger-b46 tiger-beta2
14-06-2004

SUGGESTED FIX See webrev at http://analemma.sfbay.sun.com/net/prt-archiver.sfbay/export2/archived_workspaces/main/c2_baseline/2004/20040329084950.rasbold.c2_baseline/workspace/webrevs/webrev-2004.03.29/index.html ###@###.### 2004-03-29
29-03-2004

EVALUATION In the code compiled for toArray, the oopMap at the loop safepoint is wrong. The compiler fails to make an entry for "head"; if the object that is "head" moves during a GC, the loop end test fails to trigger. Thus, the generated code overflows the destination array, rather than exiting. This failure is restricted only to x86 architectures, as it due to the use of the UseCISCSpill option of the register allocator. The most direct fix is to make buildOopMaps coorectly handle cisc spill instructions. ###@###.### 2004-02-23
23-02-2004