RFR(S): 8173743: Failures during class definition can lead to memory leaks in metaspace

Volker Simonis volker.simonis at gmail.com
Wed Feb 8 19:42:17 UTC 2017


Hi Dmitry,

thanks for looking at my change and your valuable comments. I hope
I've addressed them all. Please find my detailed comments inline:

On Tue, Feb 7, 2017 at 11:53 AM, Dmitry Dmitriev
<dmitry.dmitriev at oracle.com> wrote:
> Hello Volker,
>
> Few additional test comments:
> 1) 168         String self = ManagementFactory.getRuntimeMXBean().getName();
>  169         String pid = self.substring(0, self.indexOf('@'));
>  170         System.out.println("Our pid is = " + pid);
>
> I think it's is better to use getProcessId() method from
> jdk.test.lib.process.ProcessTools testlibrary
> package(test/lib/jdk/test/lib/process/ProcessTools.java) to get pid().
>

Sure. I've first written this as a stand alone test, but now that it
is a JTreg test I can change it.

> 2)  214             Scanner s = new Scanner(result);
>
> Can you please put code for Scanner in the try-with-resources block and
> remove 's.close();' on line 226?
>

Done.

> 3) In case of error getClassStats() prints information about exception and
> returns 0. Test will fail later at this check "if (count != ...) throw new
> RuntimeException(res);". Probably test should fail(raise an exception) in
> getClassStats() method in case of the error to avoid confusion?
>

Good point. Done.

> 4) Can you please move "(args.length > 1 ? Integer.parseInt(args[1]) : 10)"
> outside of the for loop in lines 243,277,304,362 to improve readability?
> I.e. do something like that:
> static final int DEFAULT_DEFINE_ATTEMPTS = 10;
> ...
> public static void main(String[] args) throws Exception {
>     int defineAttempts = args.length > 1 ? Integer.parseInt(args[1]) :
> DEFAULT_DEFINE_ATTEMPTS;
> ...
>     for (int i = 0; i < defineAttempts; i++) {
>
> In this case 'defineAttemps' also can be used on lines 332,333 and 'length'
> can be removed.
>

Done.

> 5)  338                 Thread.sleep(100); // Give the new thread a chance
> to start
>
> I don't feel happy with sleep :) For 10 threads we get 1 second sleep in
> total. It is possible to use different mechanism to ensure that threads are
> start(another CountDownLatch)?
>

Done.

> 6) I think that you can reduce some code duplication in the main method by
> moving following construction to the separate method:
>  261             count = getClassStats("DefineClass");
>  262             // At least after System.gc() the failed loading attempts
> should leave no instances around!
>  263             String res = "Should have 2 DefineClass instances and we
> have: " + count;
>  264             System.out.println(res);
>  265             if (count != 2) throw new RuntimeException(res);
>
> E.g.
>
> void printClassStats(int expectedCount, boolean reportError) {
>     count = getClassStats("DefineClass");
>     String res = "Should have " + expectedCount + " DefineClass instances
> and we have: " + count;
>     System.out.println(res);
>     if (reportError && count != expectedCount) {
>         throw new RuntimeException(res);
>     }
> }
>
> In this case following piece of code:
>  254             int count = getClassStats("DefineClass");
>  255             // We expect to have two instances of DefineClass here: the
> initial version in which we are
>  256             // executing and one another version which was loaded into
> our own classloader 'MyClassLoader'.
>  257             // All the subsequent attempts to reload DefineClass into
> our 'MyClassLoader' should have failed.
>  258             System.out.println("Should have 2 DefineClass instances and
> we have: " + count);
>  259             System.gc();
>  260             System.out.println("System.gc()");
>  261             count = getClassStats("DefineClass");
>  262             // At least after System.gc() the failed loading attempts
> should leave no instances around!
>  263             String res = "Should have 2 DefineClass instances and we
> have: " + count;
>  264             System.out.println(res);
>  265             if (count != 2) throw new RuntimeException(res);
>
> will be transformed to the following:
>     // We expect to have two instances of DefineClass here: the initial
> version in which we are
>     // executing and one another version which was loaded into our own
> classloader 'MyClassLoader'.
>     // All the subsequent attempts to reload DefineClass into our
> 'MyClassLoader' should have failed.
>     printClassStats(2, false);
>     System.gc();
>     System.out.println("System.gc()");
>     // At least after System.gc() the failed loading attempts should leave
> no instances around!
>     printClassStats(2, true);
>

Yes, that's a nice refactoring! Done.

> Thank you!
>

I've uploaded a new webrev which contains the test changes for all
suggestions I've received until now:

http://cr.openjdk.java.net/~simonis/webrevs/2017/8173743.v2/

Thanks,
Volker

> Dmitry
>
>
> On 06.02.2017 21:40, Volker Simonis wrote:
>>
>> Hi,
>>
>> can somebody please review the following change:
>>
>> http://cr.openjdk.java.net/~simonis/webrevs/2017/8173743.v1/
>> https://bugs.openjdk.java.net/browse/JDK-8173743
>>
>> It fixes some problems during class definitions where instance klasses
>> can leak into the metaspace in cases where the class definition fails
>> after the class was successfully loaded from the bytecode stream.
>>
>> There are actually two cases to consider:
>>
>> 1. If we load the same class several times into the same class loader,
>> we will get LinkageErrors after the first successful load. But
>> nevertheless we will first construct a new instanceKlass for each new
>> load attempt and this instanceKlass will never be deleted.
>>
>> 2. If we have a parallel capable class loader, set
>> -XX:-UnsyncloadClass and/or -XX:+AllowParallelDefineClass and load a
>> class from several threads at the same time in parallel, it can happen
>> that we create several instance klasses for the same class. At the end
>> only one of them will be added to the system dictionary, but all the
>> other ones will never be deleted. Notice that if we run this scenario
>> without setting either of -XX:-UnsyncloadClass or
>> -XX:+AllowParallelDefineClass, this scenario will degrade into the
>> case above and we will get LinkageErrors for all but the first
>> successful load.
>>
>> The change comes with a regression test which checks for the two cases
>> just describe and also for the failing class redefinition case, which
>> currently doesn't produce a memory leak.
>>
>> I've already committed this to the hs-demo-submit/hotspot/ forest and
>> it went through without a problem. So in theory it should have passed
>> the internal JPRT tests although I'm not sure if the test set of the
>> "demo-submit" forest and the real hotspot repo are exactly the same
>> (CC'ed Tim and Brian).
>>
>> Thank you and best regards,
>> Volker
>
>


More information about the hotspot-runtime-dev mailing list