Bug4168625Test.java revision 2365:4c234c13f66a
1/*
2 * Copyright (c) 2007, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23/*
24    @test
25    @summary test Resource Bundle for bug 4168625
26    @build Bug4168625Class Bug4168625Getter Bug4168625Resource Bug4168625Resource3 Bug4168625Resource3_en Bug4168625Resource3_en_CA Bug4168625Resource3_en_IE Bug4168625Resource3_en_US Bug4168625Resource2_en_US Bug4168625Resource2
27    @run main/timeout=600 Bug4168625Test
28    @bug 4168625
29*/
30/*
31 *
32 *
33 * (C) Copyright IBM Corp. 1999 - All Rights Reserved
34 *
35 * This software is the confidential and proprietary information
36 * of Sun Microsystems, Inc. ("Confidential Information").  You
37 * shall not disclose such Confidential Information and shall use
38 * it only in accordance with the terms of the license agreement
39 * you entered into with Sun.
40 *
41 * The original version of this source code and documentation is
42 * copyrighted and owned by IBM. These materials are provided
43 * under terms of a License Agreement between IBM and Sun.
44 * This technology is protected by multiple US and International
45 * patents. This notice and attribution to IBM may not be removed.
46 *
47 */
48
49import java.util.*;
50import java.io.*;
51
52/**
53 *  This test tries to correct three efficiency problems with the caching
54 *  mechanism of ResourceBundle.  All tests assume that none of the bundles
55 *  have been previously loaded and cached.  It also allows concurrent loads
56 *  of resource bundles to be performed if the bundles are unrelated (ex. a
57 *  load of a local system resource by one thread while another thread is
58 *  doing a slow load over a network).
59 */
60public class Bug4168625Test extends RBTestFmwk {
61    public static void main(String[] args) throws Exception {
62        new Bug4168625Test().run(args);
63    }
64
65    /**
66     * Verify that getBundle will do something reasonable when part of the
67     * resource hierarchy is missing.
68     */
69    public void testMissingParent() throws Exception {
70        final Locale oldDefault = Locale.getDefault();
71        Locale.setDefault(new Locale("en", "US"));
72        try {
73            final Locale loc = new Locale("jf", "jf");
74            ResourceBundle bundle = ResourceBundle.getBundle("Bug4168625Resource2", loc);
75            final String s1 = bundle.getString("name");
76            if (!s1.equals("Bug4168625Resource2_en_US")) {
77                errln("getBundle did not find leaf bundle: "+bundle.getClass().getName());
78            }
79            final String s2 = bundle.getString("baseName");
80            if (!s2.equals("Bug4168625Resource2")) {
81                errln("getBundle did not set up proper inheritance chain");
82            }
83        } finally {
84            Locale.setDefault(oldDefault);
85        }
86    }
87
88    /**
89     *  Previous versions of ResourceBundle have had the following
90     *  caching behavior.  Assume the classes
91     *  Bug4168625Resource_fr_FR, Bug4168625Resource_fr,
92     *  Bug4168625Resource_en_US, and Bug4168625Resource_en don't
93     *  exist.  The class Bug4168625Resource does.  Assume the default
94     *  locale is en_US.
95     *  <P>
96     *  <pre>
97     *  getBundle("Bug4168625Resource", new Locale("fr", "FR"));
98     *      -->try to load Bug4168625Resource_fr_FR
99     *      -->try to load Bug4168625Resource_fr
100     *      -->try to load Bug4168625Resource_en_US
101     *      -->try to load Bug4168625Resource_en
102     *      -->load Bug4168625Resource
103     *      -->cache Bug4168625Resource as Bug4168625Resource
104     *      -->cache Bug4168625Resource as Bug4168625Resource_en
105     *      -->cache Bug4168625Resource as Bug4168625Resource_en_US
106     *      -->return Bug4168625Resource
107     *  getBundle("Bug4168625Resource", new Locale("fr", "FR"));
108     *      -->try to load Bug4168625Resource_fr_FR
109     *      -->try to load Bug4168625Resource_fr
110     *      -->find cached Bug4168625Resource_en_US
111     *      -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
112     *  </pre>
113     *  <P>
114     *  The second call causes two loads for Bug4168625Resource_fr_FR and
115     *  Bug4168625Resource_en which have already been tried and failed.  These
116     *  two loads should have been cached as Bug4168625Resource by the first
117     *  call.
118     *
119     *  The following, more efficient behavior is desired:
120     *  <P>
121     *  <pre>
122     *  getBundle("Bug4168625Resource", new Locale("fr", "FR"));
123     *      -->try to load Bug4168625Resource_fr_FR
124     *      -->try to load Bug4168625Resource_fr
125     *      -->try to load Bug4168625Resource_en_US
126     *      -->try to load Bug4168625Resource_en
127     *      -->load Bug4168625Resource
128     *      -->cache Bug4168625Resource as Bug4168625Resource
129     *      -->cache Bug4168625Resource as Bug4168625Resource_en
130     *      -->cache Bug4168625Resource as Bug4168625Resource_en_US
131     *      -->cache Bug4168625Resource as Bug4168625Resource_fr
132     *      -->cache Bug4168625Resource as Bug4168625Resource_fr_FR
133     *      -->return Bug4168625Resource
134     *  getBundle("Bug4168625Resource", new Locale("fr", "FR"));
135     *      -->find cached Bug4168625Resource_fr_FR
136     *      -->return Bug4168625Resource_en_US (which is realy Bug4168625Resource)
137     *  </pre>
138     *  <P>
139     *
140     */
141    public void testCacheFailures() throws Exception {
142        checkResourceLoading("Bug4168625Resource", new Locale("fr", "FR"));
143    }
144
145    /**
146     *  Previous versions of ResourceBundle have had the following
147     *  caching behavior.  Assume the current locale is locale is en_US.
148     *  The classes Bug4168625Resource_en_US, and Bug4168625Resource_en don't
149     *  exist.  The class Bug4168625Resource does.
150     *  <P>
151     *  <pre>
152     *  getBundle("Bug4168625Resource", new Locale("en", "US"));
153     *      -->try to load Bug4168625Resource_en_US
154     *      -->try to load Bug4168625Resource_en
155     *      -->try to load Bug4168625Resource_en_US
156     *      -->try to load Bug4168625Resource_en
157     *      -->load Bug4168625Resource
158     *      -->cache Bug4168625Resource as Bug4168625Resource
159     *      -->cache Bug4168625Resource as Bug4168625Resource_en
160     *      -->cache Bug4168625Resource as Bug4168625Resource_en_US
161     *      -->return Bug4168625Resource
162     *  </pre>
163     *  <P>
164     *  The redundant loads of Bug4168625Resource_en_US and Bug4168625Resource_en
165     *  should not occur.  The desired behavior is as follows:
166     *  <P>
167     *  <pre>
168     *  getBundle("Bug4168625Resource", new Locale("en", "US"));
169     *      -->try to load Bug4168625Resource_en_US
170     *      -->try to load Bug4168625Resource_en
171     *      -->load Bug4168625Resource
172     *      -->cache Bug4168625Resource as Bug4168625Resource
173     *      -->cache Bug4168625Resource as Bug4168625Resource_en
174     *      -->cache Bug4168625Resource as Bug4168625Resource_en_US
175     *      -->return Bug4168625Resource
176     *  </pre>
177     *  <P>
178     */
179    public void testRedundantLoads() throws Exception {
180        checkResourceLoading("Bug4168625Resource", Locale.getDefault());
181    }
182
183    /**
184     * Ensure that resources are only loaded once and are cached correctly
185     */
186    private void checkResourceLoading(String resName, Locale l) throws Exception {
187        final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
188        final Class c = loader.loadClass("Bug4168625Class");
189        Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
190        final String resClassName;
191        if (l.toString().length() > 0) {
192            resClassName = resName+"_"+l;
193        } else {
194            resClassName = resName;
195        }
196
197        Object bundle = test.getResourceBundle(resName, l);
198        loader.logClasses("Initial lookup of "+resClassName+" generated the following loads:");
199
200        final Vector lastLoad = new Vector(loader.loadedClasses.size());
201        boolean dups = false;
202        for (int i = loader.loadedClasses.size() - 1; i >= 0 ; i--) {
203            final Object item = loader.loadedClasses.elementAt(i);
204            loader.loadedClasses.removeElementAt(i);
205            if (loader.loadedClasses.contains(item)) {
206                logln("Resource loaded more than once: "+item);
207                dups = true;
208            } else {
209                lastLoad.addElement(item);
210            }
211        }
212        if (dups) {
213            errln("ResourceBundle loaded some classes multiple times");
214        }
215
216        loader.loadedClasses.removeAllElements();
217        bundle = test.getResourceBundle(resName, l);
218        loader.logClasses("Second lookup of "+resClassName+" generated the following loads:");
219
220        dups = false;
221        for (int i = 0; i < loader.loadedClasses.size(); i++) {
222            Object item = loader.loadedClasses.elementAt(i);
223            if (lastLoad.contains(item)) {
224                logln("ResourceBundle did not cache "+item+" correctly");
225                dups = true;
226            }
227        }
228        if (dups) {
229            errln("Resource bundle not caching some classes properly");
230        }
231    }
232
233    /**
234     *  Previous versions of ResourceBundle exhibited the following caching behavior.
235     *  Assume the class Bug4168625Resource_en exists. Bug4168625Resource_en_US does
236     *  not.  Two threads, ThreadA and ThreadB both try to get the same bundle.
237     *  <P>
238     *  <pre>
239     *  ThreadA.getBundle("Bug4168625Resource", new Locale("en", "US"));
240     *      A-->try to load Bug4168625Resource_en_US
241     *  ThreadB.getBundle("Bug4168625Resource", new Locale("en", "US"));
242     *      B-->try to load Bug4168625Resource_en_US
243     *      B-->load Bug4168625Resource_en (#1)
244     *      A-->load Bug4168625Resource_en (#2)
245     *      A-->cache Bug4168625Resource_en (#2) as Bug4168625Resource_en
246     *      A-->cache Bug4168625Resource_en (#2) as Bug4168625Resource_en_US
247     *      A-->return Bug4168625Resource_en (#2)
248     *      B-->cache Bug4168625Resource_en (#1) as Bug4168625Resource_en
249     *      B-->cache Bug4168625Resource_en (#1) as Bug4168625Resource_en_US
250     *      B-->return Bug4168625Resource_en (#1)
251     *  </pre>
252     *  <P>
253     *  Both threads try and fail to load Bug4168625Resource_en_US.  Both
254     *  threads load Bug4168625Resource_en.  Both threads get their own copy
255     *  of the Bug4168625Resource_en resource.
256     *
257     *  The desired behavior is as follows:
258     *  <P>
259     *  <pre>
260     *  ThreadA.getBundle("Bug4168625Resource", new Locale("en", "US"));
261     *      A-->try to load Bug4168625Resource_en_US
262     *  ThreadB.getBundle("Bug4168625Resource", new Locale("en", "US"));
263     *      B-->try to load Bug4168625Resource_en_US
264     *      B-->load Bug4168625Resource_en
265     *      A-->load Bug4168625Resource_en (block in ResourceBundle.getBundle)
266     *      B-->cache Bug4168625Resource_en as Bug4168625Resource_en
267     *      B-->cache Bug4168625Resource_en as Bug4168625Resource_en_US
268     *      A-->return Bug4168625Resource_en
269     *      B-->return Bug4168625Resource_en
270     *  </pre>
271     *  <P>
272     *  Note that both threads return the same bundle object.
273     */
274    public void testConcurrentLoading1() throws Exception {
275        final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
276        final Class c = loader.loadClass("Bug4168625Class");
277        final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
278
279            //both threads want the same resource
280        ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US"));
281        ConcurrentLoadingThread thread2 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US"));
282
283        thread1.start();            //start thread 1
284        loader.waitForNotify(1);    //wait for thread1 to do getBundle & block in loader
285        thread2.start();            //start second thread
286        loader.waitForNotify(2, 1000);  //wait until thread2 blocks somewhere in getBundle
287        thread1.ping();             //continue both threads
288        thread2.ping();
289
290        thread1.join();             //wait unitl both threads complete
291        thread2.join();
292
293            //Now, examine the class loads that were done.
294        loader.logClasses("Classes loaded after completion of both threads:");
295
296        boolean dups = false;
297        for (int i = loader.loadedClasses.size() - 1; i >= 0 ; i--) {
298            final Object item = loader.loadedClasses.elementAt(i);
299            loader.loadedClasses.removeElementAt(i);
300            if (loader.loadedClasses.contains(item)) {
301                logln("Resource loaded more than once: "+item);
302                dups = true;
303            }
304        }
305        if (dups) {
306            errln("ResourceBundle loaded some classes multiple times");
307        }
308    }
309
310    private class ConcurrentLoadingThread extends Thread {
311        private Loader loader;
312        public Object bundle;
313        private Bug4168625Getter test;
314        private Locale locale;
315        private String resourceName = "Bug4168625Resource3";
316        public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l, String resourceName) {
317            this.loader = loader;
318            this.test = test;
319            this.locale = l;
320            this.resourceName = resourceName;
321        }
322        public ConcurrentLoadingThread(Loader loader, Bug4168625Getter test, Locale l) {
323            this.loader = loader;
324            this.test = test;
325            this.locale = l;
326        }
327        public void run() {
328            try {
329                logln(">>"+threadName()+">run");
330                bundle = test.getResourceBundle(resourceName, locale);
331            } catch (Exception e) {
332                errln("TEST CAUGHT UNEXPECTED EXCEPTION: "+e);
333            } finally {
334                logln("<<"+threadName()+"<run");
335            }
336        }
337        public synchronized void waitUntilPinged() {
338            logln(">>"+threadName()+">waitUntilPinged");
339            loader.notifyEveryone();
340            try {
341                wait(30000);    //wait 30 seconds max.
342            } catch (InterruptedException e) {
343                logln("Test deadlocked.");
344            }
345            logln("<<"+threadName()+"<waitUntilPinged");
346        }
347        public synchronized void ping() {
348            logln(">>"+threadName()+">ping "+threadName(this));
349            notifyAll();
350            logln("<<"+threadName()+"<ping "+threadName(this));
351        }
352    };
353
354    /**
355     * This test ensures that multiple resources can be loading at the same
356     * time as long as they don't depend on each other in some way.
357     */
358    public void testConcurrentLoading2() throws Exception {
359        final Loader loader = new Loader( new String[] { "Bug4168625Class" }, new String[] { "Bug4168625Resource3_en_US", "Bug4168625Resource3_en_CA" });
360        final Class c = loader.loadClass("Bug4168625Class");
361        final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
362
363        ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "CA"));
364        ConcurrentLoadingThread thread2 = new ConcurrentLoadingThread(loader, test, new Locale("en", "IE"));
365
366        thread1.start();            //start thread 1
367        loader.waitForNotify(1);    //wait for thread1 to do getBundle & block in loader
368        thread2.start();            //start second thread
369        thread2.join(1000);         //wait until thread2 blocks somewhere in getBundle
370
371            //Thread1 should be blocked inside getBundle at the class loader
372            //Thread2 should have completed its getBundle call and terminated
373        if (!thread1.isAlive() || thread2.isAlive()) {
374            errln("ResourceBundle.getBundle not allowing legal concurrent loads");
375        }
376
377        thread1.ping();             //continue thread1
378        thread1.join();
379        thread2.join();
380    }
381
382    /**
383     * This test ensures that a resource loads correctly (with all its parents)
384     * when memory is very low (ex. the cache gets purged during a load).
385     */
386    public void testLowMemoryLoad() throws Exception {
387        final String[] classToLoad = { "Bug4168625Class" };
388        final String[] classToWait = { "Bug4168625Resource3_en_US","Bug4168625Resource3_en","Bug4168625Resource3" };
389        final Loader loader = new Loader(classToLoad, classToWait);
390        final Class c = loader.loadClass("Bug4168625Class");
391        final Bug4168625Getter test = (Bug4168625Getter)c.newInstance();
392        causeResourceBundleCacheFlush();
393
394        ConcurrentLoadingThread thread1 = new ConcurrentLoadingThread(loader, test, new Locale("en", "US"));
395        thread1.start();            //start thread 1
396        loader.waitForNotify(1);    //wait for thread1 to do getBundle(en_US) & block in loader
397        causeResourceBundleCacheFlush();    //cause a cache flush
398        thread1.ping();             //kick thread 1
399        loader.waitForNotify(2);    //wait for thread1 to do getBundle(en) & block in loader
400        causeResourceBundleCacheFlush();    //cause a cache flush
401        thread1.ping();             //kick thread 1
402        loader.waitForNotify(3);    //wait for thread1 to do getBundle(en) & block in loader
403        causeResourceBundleCacheFlush();    //cause a cache flush
404        thread1.ping();             //kick thread 1
405        thread1.ping();             //kick thread 1
406        thread1.join(1000);         //wait until thread2 blocks somewhere in getBundle
407
408        ResourceBundle bundle = (ResourceBundle)thread1.bundle;
409        String s1 = bundle.getString("Bug4168625Resource3_en_US");
410        String s2 = bundle.getString("Bug4168625Resource3_en");
411        String s3 = bundle.getString("Bug4168625Resource3");
412        if ((s1 == null) || (s2 == null) || (s3 == null)) {
413            errln("Bundle not constructed correctly.  The parent chain is incorrect.");
414        }
415    }
416
417    /**
418     * A simple class loader that loads classes from the current
419     * working directory.  The loader will block the current thread
420     * of execution before it returns when it tries to load
421     * the class "Bug4168625Resource3_en_US".
422     */
423    private static final String CLASS_PREFIX = "";
424    private static final String CLASS_SUFFIX = ".class";
425
426    private static final class SimpleLoader extends ClassLoader {
427        private boolean network = false;
428
429        public SimpleLoader() {
430            this.network = false;
431        }
432        public SimpleLoader(boolean simulateNetworkLoad) {
433            this.network = simulateNetworkLoad;
434        }
435        public Class loadClass(final String className, final boolean resolveIt)
436                throws ClassNotFoundException {
437            Class result;
438            synchronized (this) {
439                result = findLoadedClass(className);
440                if (result == null) {
441                    if (network) {
442                        try {
443                             Thread.sleep(100);
444                        } catch (java.lang.InterruptedException e) {
445                        }
446                    }
447                    result = super.findSystemClass(className);
448                    if ((result != null) && resolveIt) {
449                        resolveClass(result);
450                    }
451                }
452            }
453            return result;
454        }
455    }
456
457    private final class Loader extends ClassLoader {
458        public final Vector loadedClasses = new Vector();
459        private String[] classesToLoad;
460        private String[] classesToWaitFor;
461
462        public Loader() {
463            classesToLoad = new String[0];
464            classesToWaitFor = new String[0];
465        }
466
467        public Loader(final String[] classesToLoadIn, final String[] classesToWaitForIn) {
468            classesToLoad = classesToLoadIn;
469            classesToWaitFor = classesToWaitForIn;
470        }
471
472        /**
473         * Load a class.  Files we can load take preference over ones the system
474         * can load.
475         */
476        private byte[] getClassData(final String className) {
477            boolean shouldLoad = false;
478            for (int i = classesToLoad.length-1; i >= 0; --i) {
479                if (className.equals(classesToLoad[i])) {
480                    shouldLoad = true;
481                    break;
482                }
483            }
484
485            if (shouldLoad) {
486                final String name = CLASS_PREFIX+className+CLASS_SUFFIX;
487                try {
488                    final InputStream fi = this.getClass().getClassLoader().getResourceAsStream(name);
489                    final byte[] result = new byte[fi.available()];
490                    fi.read(result);
491                    return result;
492                } catch (Exception e) {
493                    logln("Error loading test class: "+name);
494                    logln(e.toString());
495                    return null;
496                }
497            } else {
498                return null;
499            }
500        }
501
502        /**
503         * Load a class.  Files we can load take preference over ones the system
504         * can load.
505         */
506        public Class loadClass(final String className, final boolean resolveIt)
507                throws ClassNotFoundException {
508            Class result;
509            synchronized (this) {
510                logln(">>"+threadName()+">load "+className);
511                loadedClasses.addElement(className);
512
513                result = findLoadedClass(className);
514                if (result == null) {
515                    final byte[] classData = getClassData(className);
516                    if (classData == null) {
517                        //we don't have a local copy of this one
518                        logln("Loading system class: "+className);
519                        result = loadFromSystem(className);
520                    } else {
521                        result = defineClass(classData, 0, classData.length);
522                        if (result == null) {
523                            //there was an error defining the class
524                            result = loadFromSystem(className);
525                        }
526                    }
527                    if ((result != null) && resolveIt) {
528                        resolveClass(result);
529                    }
530                }
531            }
532            for (int i = classesToWaitFor.length-1; i >= 0; --i) {
533                if (className.equals(classesToWaitFor[i])) {
534                    rendezvous();
535                    break;
536                }
537            }
538            logln("<<"+threadName()+"<load "+className);
539            return result;
540        }
541
542        /**
543         * Delegate loading to the system loader
544         */
545        private Class loadFromSystem(String className) throws ClassNotFoundException {
546            return super.findSystemClass(className);
547        }
548
549        public void logClasses(String title) {
550            logln(title);
551            for (int i = 0; i < loadedClasses.size(); i++) {
552                logln("    "+loadedClasses.elementAt(i));
553            }
554            logln("");
555        }
556
557        public int notifyCount = 0;
558        public int waitForNotify(int count) {
559            return waitForNotify(count, 0);
560        }
561        public synchronized int waitForNotify(int count, long time) {
562            logln(">>"+threadName()+">waitForNotify");
563            if (count > notifyCount) {
564                try {
565                    wait(time);
566                } catch (InterruptedException e) {
567                }
568            } else {
569                logln("  count("+count+") > notifyCount("+notifyCount+")");
570            }
571            logln("<<"+threadName()+"<waitForNotify");
572            return notifyCount;
573        }
574        private synchronized void notifyEveryone() {
575            logln(">>"+threadName()+">notifyEveryone");
576            notifyCount++;
577            notifyAll();
578            logln("<<"+threadName()+"<notifyEveryone");
579        }
580        private void rendezvous() {
581            final Thread current = Thread.currentThread();
582            if (current instanceof ConcurrentLoadingThread) {
583                ((ConcurrentLoadingThread)current).waitUntilPinged();
584            }
585        }
586    }
587
588    private static String threadName() {
589        return threadName(Thread.currentThread());
590    }
591
592    private static String threadName(Thread t) {
593        String temp = t.toString();
594        int ndx = temp.indexOf("Thread[");
595        temp = temp.substring(ndx + "Thread[".length());
596        ndx = temp.indexOf(',');
597        temp = temp.substring(0, ndx);
598        return temp;
599    }
600
601    /** Fill memory to force all SoftReferences to be GCed */
602    private void causeResourceBundleCacheFlush() {
603        logln("Filling memory...");
604        int allocationSize = 1024;
605        Vector memoryHog = new Vector();
606        try {
607            while (true) {
608                memoryHog.addElement(new byte[allocationSize]);
609                allocationSize *= 2;
610            }
611        } catch (Throwable e) {
612            logln("Caught "+e+" filling memory");
613        } finally{
614            memoryHog = null;
615            System.gc();
616        }
617        logln("last allocation size: " + allocationSize);
618    }
619
620    /**
621     *  NOTE: this problem is not externally testable and can only be
622     *  verified through code inspection unless special code to force
623     *  a task switch is inserted into ResourceBundle.
624     *  The class Bug4168625Resource_sp exists.  It's parent bundle
625     *  (Bug4168625Resource) contains a resource string with the tag
626     *  "language" but Bug4168625Resource_sp does not.
627     *  Assume two threads are executing, ThreadA and ThreadB and they both
628     *  load a resource Bug4168625Resource with from sp locale.
629     *  ResourceBundle.getBundle adds a bundle to the bundle cache (in
630     *  findBundle) before it sets the bundle's parent (in getBundle after
631     *  returning from findBundle).
632     *  <P>
633     *  <pre>
634     *  ThreadA.getBundle("Bug4168625Resource", new Locale("sp"));
635     *      A-->load Bug4168625Resource_sp
636     *      A-->find cached Bug4168625Resource
637     *      A-->cache Bug4168625Resource_sp as Bug4168625Resource_sp
638     *  ThreadB.getBundle("Bug4168625Resource", new Locale("sp"));
639     *      B-->find cached Bug4168625Resource_sp
640     *      B-->return Bug4168625Resource_sp
641     *  ThreadB.bundle.getString("language");
642     *      B-->try to find "language" in Bug4168625Resource_sp
643     *      B-->Bug4168625Resource_sp does not have a parent, so return null;
644     *  ThreadB.System.out.println("Some unknown country");
645     *      A-->set parent of Bug4168625Resource_sp to Bug4168625Resource
646     *      A-->return Bug4168625Resource_sp (the same bundle ThreadB got)
647     *  ThreadA.bundle.getString("language");
648     *      A-->try to find "language" in Bug4168625Resource_sp
649     *      A-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp)
650     *      A-->return the string
651     *  ThreadA.System.out.println("Langauge = "+country);
652     *  ThreadB.bundle.getString("language");
653     *      B-->try to find "language" in Bug4168625Resource_sp
654     *      B-->try to find "language" in Bug4168625Resource (parent of Bug4168625Resource_sp)
655     *      B-->return the string
656     *  ThreadB.System.out.println("Langauge = "+country);
657     *  </pre>
658     *  <P>
659     *  Note that the first call to getString() by ThreadB returns null, but the second
660     *  returns a value.  Thus to ThreadB, the bundle appears to change.  ThreadA gets
661     *  the expected results right away.
662     */
663}
664