RFR: 8038468: java/lang/instrument/ParallelTransformerLoader.sh fails with ClassCircularityError

David Holmes david.holmes at oracle.com
Sun Oct 26 22:42:12 UTC 2014


On 25/10/2014 10:23 AM, Yumin Qi wrote:
>
> On 10/24/2014 4:57 PM, serguei.spitsyn at oracle.com wrote:
>> Yumin,
>>
>> On 10/24/14 4:08 PM, Yumin Qi wrote:
>>> Serguei,
>>>
>>>   Thanks for your comments.
>>>   This test happens intermittently, but now it can repeat with 8/9.
>>>   Loading TestClass1 in main thread while loading TestClass2 in
>>> TestThread in parallel. They both will call transform since
>>> TestClass[1-3] are loaded via agent. When loading TestClass2, it will
>>> call loading TestClass3 in TestThread.
>>>   Note in the main thread, for loop:
>>>
>>>                   for (int i = 0; i < kNumIterations; i++)
>>>                 {
>>>                         // load some classes from multiple threads
>>> (this thread and one other)
>>>                         Thread testThread = new TestThread(2);
>>>                         testThread.start();
>>>                         loadClasses(1);
>>>
>>>                         // log that it completed and reset for the
>>> next iteration
>>>                         testThread.join();
>>>                         System.out.print(".");
>>> ParallelTransformerLoaderAgent.generateNewClassLoader();
>>>                 }
>>>
>>> The loader got renewed after testThread.join(). So both threads are
>>> using the exact same class loader.
>>
>> You are right, thanks.
>> It means that all three classes (TesClass1, TestClass2 and TestClass3)
>> are loaded by the same class loader in each iteration.
>>
>> However, I see more cases when the TestClass3 gets loaded.
>> It happens in a CFLH event when any other class (not TestClass*) in
>> the system is loaded.
>> The class loading thread can be any, not only "main" or "TestClass"
>> thread.
>> I suspect this test case mostly targets class loading that happens on
>> other threads.
>> It is because of the lines:
>>                         // In 160_03 and older, transform() is called
>>                         // with the "system_loader_lock" held and that
>>                         // prevents the bootstrap class loaded from
>>                         // running in parallel. If we add a slight sleep
>>                         // delay here when the transform() call is not
>>                         // main or TestThread, then the deadlock in
>>                         // 160_03 and older is much more reproducible.
>>                         if (!tName.equals("main") &&
>> !tName.equals("TestThread")) {
>>                             System.out.println("Thread '" + tName +
>>                                 "' has called transform()");
>>                             try {
>>                                 Thread.sleep(500);
>>                             } catch (InterruptedException ie) {
>>                             }
>>                         }
>>
>> What about the following?
>>
>> In the ParallelTransformerLoaderAgent.java  make this change:
>>               if (!tName.equals("main"))
>>                   => if (tName.equals("TestThread"))
>>
>> Does such updated test still failing?
>>
> It still fails though it takes longer time to fail. The other question,
> from D. H. is that the exception stack trace is from 'main' thread. That
> still not known how that happened.

It happens because the exception in thrown in the main thread - and that 
to me is the big mystery. I've previously expressed this in email to 
Yumin but have added it to the bug report and can add it here:

I've had a look at the test code and the bug report and I'm still quite 
confused by the analysis.

First I think we can disregard the DestroyJavaVM part of this. The main 
thread encounters the CCE and so main terminates and we see the 
"Exception in thread "main" ..." on stderr. That thread detaches and 
re-attaches as the DestroyJavaVM thread. When the TestThread terminates 
the DestroyJavaVMThread initiates shutdown and that causes loading of 
the Shutdown class and that triggers execution of transform(). But this 
is all well after the CCE happens in main.

The problem occurs when we are loading the system classes. I can imagine 
that the main thread is trying to execute loadClasses(1) which will use 
the agent defined classloader to try and load TestClass1. In invoking 
Class.forName it seems that some of the URLClassLoader internal classes 
- ie JarLoader$2 - still need to be loaded and so we start to load those.

Concurrently the TestThread wants to load TestClass2 and so it too has 
to ensure that all the URLClassLoader classes are loaded, but at some 
point the transform() is encountered and so it then starts to load 
TestClass3. This in turn still needs all of the URLClassLoader classes 
to complete loading. I'm unclear at what point in the loading process 
transform() gets invoked, but I can easily imagine a cycle (or even an 
infinite loop!) developing here.

But the CCE occurs in the main thread and the main thread doesn't load 
anything via transform() so I am at a loss to understand how it can 
encounter CCE.

---

There are numerous arbitrary changes that can be made to the test to 
avoid the problem, but that doesn't explain why the problem is occurring 
in the first place.

David
-----

>
>>> After create a new class loader, next loop will use the loader. This
>>> is why quite often on the stack trace we can see it resolves
>>> JarLoader$2.
>>>
>>> I am not quite understand the test case either. Loading TestClass3
>>> inside transform using the same classloader will cause  call to
>>> transform again and form a circle. Nonetheless, if we see TestClass2
>>> already loaded, the loop will end but that still is a risk.
>>
>> In fact, I don't like that the test loads the class TestClass3 at the
>> TestClass3 CFLH event.
>> However, it is interesting to know why we did not see (is it the
>> case?) this issue before.
>> Also, it is interesting why the test stops failing with you fix
>> (replacing loader with SystemClassLoader).
>>
> Since TestClass[1-3] are designed loaded by custom class loader, the
> system class loader will not find it --- ClassNotFoundException thrown.
>
>> The test case was added by Dan.
>> We may want to ask him to clarify the test case purpose.
>> (added Dan to the to-list)
>>
> Looking forward to getting Dan's comments.
>
>> Thanks,
>> Serguei
>>
>>>
>>> Thanks
>>> Yumin
>>>
>>> On 10/24/2014 1:20 PM, serguei.spitsyn at oracle.com wrote:
>>>> Hi Yumin,
>>>>
>>>> Below is some analysis to make sure I understand the test scenario
>>>> correctly.
>>>>
>>>> The ParallelTransformerLoaderApp.main() executes a 1000 iteration loop.
>>>> At each iteration it does:
>>>>   - creates and starts a new TestThread
>>>>   - loads TestClass1 with the current class loader:
>>>> ParallelTransformerLoaderAgent.getClassLoader()
>>>>   - changes the current class loader with new one:
>>>> ParallelTransformerLoaderAgent.generateNewClassLoader()
>>>>
>>>> The TestThread loads the TestClass2 concurrently with the main thread.
>>>>
>>>> At the CFLH events, the ParallelTransformerLoaderAgent does the
>>>> class retransformation.
>>>> If the thread loading the class is not "main", it loads the class
>>>> TestClass3
>>>> with the current class loader
>>>> ParallelTransformerLoaderAgent.getClassLoader().
>>>>
>>>> Sometimes, the TestClass2 and TestClass3 are loaded by the same
>>>> class loader recursively.
>>>> It happens if the class loader has not been changed between loading
>>>> TestClass2 and TestClass3 classes.
>>>>
>>>> I'm not convinced yet the test is incorrect.
>>>> And it is not clear why do we get a ClassCircularityError.
>>>>
>>>> Please, let me know if the above understanding is wrong.
>>>> I also see the reply from David and share his concerns.
>>>>
>>>> It is not clear if this failure is a regression.
>>>> Did we observe this issue before?
>>>> If - NOT then when and why had this failure started to appear?
>>>>
>>>> Unfortunately, it is impossible to look at the test run history at
>>>> the moment.
>>>> The Aurora is at a maintenance.
>>>>
>>>> Thanks,
>>>> Serguei
>>>>
>>>> On 10/13/14 3:58 PM, Yumin Qi wrote:
>>>>> bug: https://bugs.openjdk.java.net/browse/JDK-8038468
>>>>> webrev:*http://cr.openjdk.java.net/~minqi/8038468/webrev00/
>>>>>
>>>>> the bug marked as confidential so post the webrev internally.
>>>>>
>>>>> Problem: The test case tries to load a class from the same jar via
>>>>> agent in the middle of loading another class from the jar via same
>>>>> class loader in same thread. The call happens in transform which is
>>>>> a rare case --- in middle of loading class, loading another class.
>>>>> The result is a CircularityError. When first class is in loading,
>>>>> in vm we put JarLoader$2 on place holder table, then we start the
>>>>> defineClass, which calls transform, begins loading the second class
>>>>> so go along the same routine for loading JarLoader$2 first, found
>>>>> it already in placeholder table. A CircularityError is thrown.
>>>>> Fix: The test case should not call loading class with same class
>>>>> loader in same thread from same jar in 'transform' method. I modify
>>>>> it loading with system class loader and we expect see
>>>>> ClassNotFoundException. Detail see bug comments.
>>>>>
>>>>> Thanks
>>>>> Yumin *
>>>>
>>>
>>
>


More information about the hotspot-runtime-dev mailing list