New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
8308762: Metaspace leak with Instrumentation.retransform #14780
Conversation
Fix a small leak in constant pool merging during retransformation of a class. If this class has a catch block with Throwable, the class Throwable is pre-resolved in the constant pool, while all the other classes are in a unresolved state. So the constant pool merging process was considering the entry with pre-resolved class as different compared to the destination and create a new entry. We now try to consider it as equal specially for Methodref/Fieldref.
👋 Welcome back jpbempel! A progress list of the required criteria for merging this PR into |
Webrevs
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I'm having a lot of trouble trying to understand the fix in the context of the problem description as outlined in the bug report. The issue pertained only to the treatment of Throwable due to it being pre-resolved by the verifier, but your fix is looking at Field and MethodRefs ??
There are also remaining comments about resolved and unresolved class entries deliberately not being considered the same.
return false; // not a mismatch; not our special case | ||
} | ||
|
||
char *s1 = klass_name_at(index1)->as_C_string(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
as_C_string
needs an active ResourceMark
- not sure where it is coming from even in the existing code. There should at least be a comment that an active ResourceMark is needed.
@@ -1276,6 +1276,33 @@ void ConstantPool::unreference_symbols() { | |||
} | |||
} | |||
|
|||
// Returns true if the current mismatch is due to a resolved/unresolved | |||
// class pair. Otherwise, returns false. | |||
bool ConstantPool::is_unresolved_class_mismatch(int index1, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has this been moved verbatim from jvmtiRedefineClasses.cpp?
There are a couple of style nits with that existing code that don't fit this file:
- parameters should line up ie. const under int
- no comment on the closing brace of the method body.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Has this been moved verbatim from jvmtiRedefineClasses.cpp?
yes
There are a couple of style nits with that existing code that don't fit this file:
- parameters should line up ie. const under int
- no comment on the closing brace of the method body.
Will adjust accordingly
For the Klass in itself, there is this method
Sorry I don't understand what you mean, can you elaborate please? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if compare_entry_to can just handle this case directly and not have this function.
char *s2 = cp2->klass_name_at(index2)->as_C_string(); | ||
if (strcmp(s1, s2) != 0) { | ||
return false; // strings don't match; not our special case | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
klass_name_at() returns a Symbol* that's in the SymbolTable so this never needed to do a strcmp. It can test for equality.
bool match = compare_entry_to(recur1, cp2, recur2); | ||
match |= is_unresolved_class_mismatch(recur1, cp2, recur2); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is changing the definition of a "match" such that in all cases an unresolved class entry is considered equal to a resolved class entry - but only for these Field and MethodRefs. I don't see how this connects back to the original problem. Nor do I see why this is alway a correct thing to do.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree that it may be not the right place to call is_unresolved_class_mismatch method because we capture all cases calling compare_entry_to. For a JVM_CONSTANT_Class, compare_entry_to is recovered by the call to is_unresolved_class_mismatch in VM_RedefineClasses::merge_constant_pools. The idea is to do the same for other reference to the class in FieldRefs and (Interface)MethodRefs because otherwise the entry does not match with pre-resolved Throwable class.
In
and in
these comments both pertain to the problem at hand: the merge reverts the resolved class entry to be unresolved; unresolved entries are not considered matches for resolved ones, hence we grow the constant pool. Your fix is addressing the second part of the issue by forcing a match, but as per my other comment, it is not clear to me how the placement of that fix addresses the issue originally reported with the catch block, nor is it obvious to me that we should always consider resolved and unresolved class entries to be equal. |
ConstantPool merging is still really complicated and JVM_CONSTANT_Class/UnresolvedClass (and UnresolvedClassInError) may have changed to not track with what RedefineClasses does. If I'm reading this correctly, we are always calling find_matching_entry() and compare_entries_to() between the merged_cp and scratch_cp. Since we unresolve classes in merge_cp, and scratch_cp hasn't resolved anything yet, how are we getting this mismatch? Can you post a stack trace for us? |
And If if I am reading this correctly from
In a case of But the same problem rises again when dealing with |
I have try to explain in the comment to David above. Tell me if there is something that is still not clear. |
Hi, I now see why there's a resolved class in the scratch constant pool (ie the constant pool for the new class bytes). It looks like javac has preresolved it for us, which you said but I didn't know why. The merged constant pool always has an unresolved class copy of that class entry, because we create the merged constant pool with all unresolved classes. I now believe that ConstantPool::compare_entry_to should always compare class and resolved class as equivalent. Since the klass_name_at() function is the same call for both unresolved and resolved class, you could change the tag at the beginning of compare_entry_to() to JVM_CONSTANT_UnresolvedClass if it's resolved, like we do for error classes. Then remove the is_unresolved_class_mismatch function entirely (the comment at that call might be useful to explain why you're changing the tag in compare_entry_to). This code is only used for RedefineClasses, but even if it wasn't this comparison is the right thing to do. For special measure, you could #if INCLUDE_JVMTI around this and the operand functions that call it or file an RFE to do so for further cleanup (compare_entry_to, find_matching_entry, find_matching_operand, compare_operand_to). |
The preresolving is done by the verifier. https://bugs.openjdk.org/browse/JDK-8308762?focusedCommentId=14584707&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-14584707 |
I see. I was looking at jcod output but that doesn't distinguish resolved vs. unresolved class. At any rate, the verifier will re-resolve the class when it verifies the merged constant pool. It's still safe to consider these types as same. |
Also there is a nice test harness for class redefinition in the test/hotspot/jtreg/serviceability/jvmti/RedefineClasses tests that you might be able to use to add a test for this. |
remove is_unresolved_class_mismatch
@jpbempel Please do not rebase or force-push to an active PR as it invalidates existing review comments. Note for future reference, the bots always squash all changes into a single commit automatically as part of the integration. See OpenJDK Developers’ Guide for more information. |
Thanks @coleenp for the hints about fixing this issue.
Thanks |
// to ensure proper comparison. | ||
t1 = JVM_CONSTANT_UnresolvedClass; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All consequences of this change are not clear to me yet.
The lines 1307-1314 become not needed anymore.
Also, should the same be done for t2 as well?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t2 could be a resolved class, and can be compared with unresolved class because the function klass_name_at() works for both. It might be a good idea to change both of them though, although not necessary imo.
You're right 1307-1314 are not reached anymore. Neither is the ClassIndex case but not to remove as part of this change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I didn't see this update. The change looks good to me. You could do the cleanup that Serguei suggests with t2 and removing JVM_CONSTANT_Class case. I thought maybe they should be left in case we want to generalize this compare_entry_to function someday, that's why I didn't suggest removing it.
// to ensure proper comparison. | ||
t1 = JVM_CONSTANT_UnresolvedClass; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
t2 could be a resolved class, and can be compared with unresolved class because the function klass_name_at() works for both. It might be a good idea to change both of them though, although not necessary imo.
You're right 1307-1314 are not reached anymore. Neither is the ClassIndex case but not to remove as part of this change.
@jpbempel This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 8 new commits pushed to the
Please see this link for an up-to-date comparison between the source branch of this pull request and the As you do not have Committer status in this project an existing Committer must agree to sponsor your change. Possible candidates are the reviewers of this PR (@dholmes-ora, @coleenp) but any other Committer may sponsor as well. ➡️ To flag this PR as ready for integration with the above commit message, type |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have a suggestion for your test and copyright fix.
@@ -0,0 +1,127 @@ | |||
/* | |||
* Copyright (c) 2016, 2023, Oracle and/or its affiliates. All rights reserved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please remove the 2016 copyright on the new test.
t.printStackTrace(); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you look in that directory, there's an even simpler way to redefine classes without the Transformer and agent code. It looks like this: RedefineClassHelper.redefineClass(RedefineRunningMethods_B.class, evenNewerB);
// with the same string value. Since Pass 0 reverted any | ||
// class entries to unresolved class entries in *merge_cp_p, | ||
// we go with the unresolved class entry. | ||
continue; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry for the piecemeal review. There's another comment that mentions this comment that should be removed. It starts with this:
-
// The find_matching_entry() call above could fail to find a match
unresolved t2 too, cleanup JVM_CONSTANT_Class useless case
@coleenp I have made the changes: cleanup code and rewrite the unit test |
Hey @coleenp could you review my last changes? |
Please @coleenp review my last changes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on the previous discussion and comments this simple change seems quite reasonable to me.
One query on the test though.
Thanks
* @test | ||
# @bug 8308762 | ||
* @library /test/lib | ||
* @summary Test that redefinition of class containing Throwable refs does not leak constant pool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Exactly how is this test tracking whether there is a leak or not? Is it simply setting metaspace size small enough that the 500 iterations would exhaust metaspace if there were a leak?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it simply setting metaspace size small enough that the 500 iterations would exhaust metaspace if there were a leak?
yes exactly. with a leak you end up with OutOfMemoryError: metaspace
running the test
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm sorry for the delay in reviewing this. This looks great!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good. Thanks.
Thanks! |
/sponsor |
Going to push as commit df4a25b.
Your commit was automatically rebased without conflicts. |
@dholmes-ora @jpbempel Pushed as commit df4a25b. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Fix a small leak in constant pool merging during retransformation of a class. If this class has a catch block with
Throwable
, the classThrowable
is pre-resolved in the constant pool, while all the other classes are in a unresolved state. So the constant pool merging process was considering the entry with pre-resolved class as different compared to the destination and create a new entry. We now try to consider it as equal specially for Methodref/Fieldref.Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/14780/head:pull/14780
$ git checkout pull/14780
Update a local copy of the PR:
$ git checkout pull/14780
$ git pull https://git.openjdk.org/jdk.git pull/14780/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 14780
View PR using the GUI difftool:
$ git pr show -t 14780
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/14780.diff
Webrev
Link to Webrev Comment