Skip to content

8382332: Large regression (~50%) in SPECjbb2015 when using JFR after JEP 500#30922

Open
mgronlun wants to merge 9 commits intoopenjdk:masterfrom
mgronlun:8382332
Open

8382332: Large regression (~50%) in SPECjbb2015 when using JFR after JEP 500#30922
mgronlun wants to merge 9 commits intoopenjdk:masterfrom
mgronlun:8382332

Conversation

@mgronlun
Copy link
Copy Markdown

@mgronlun mgronlun commented Apr 24, 2026

Greetings,

please see JDK-8382332 for detailed information about this issue.

Testing: jdk_jfr, tier1-6, SpecJbb2015

Thanks
Markus



Progress

  • Change must not contain extraneous whitespace
  • Commit message must refer to an issue
  • Change must be properly reviewed (2 reviews required, with at least 1 Reviewer, 1 Author)

Issue

  • JDK-8382332: Large regression (~50%) in SPECjbb2015 when using JFR after JEP 500 (Bug - P2)

Reviewers

Reviewing

Using git

Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk.git pull/30922/head:pull/30922
$ git checkout pull/30922

Update a local copy of the PR:
$ git checkout pull/30922
$ git pull https://git.openjdk.org/jdk.git pull/30922/head

Using Skara CLI tools

Checkout this PR locally:
$ git pr checkout 30922

View PR using the GUI difftool:
$ git pr show -t 30922

Using diff file

Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/30922.diff

Using Webrev

Link to Webrev Comment

@bridgekeeper
Copy link
Copy Markdown

bridgekeeper Bot commented Apr 24, 2026

👋 Welcome back mgronlun! A progress list of the required criteria for merging this PR into master will be added to the body of your pull request. There are additional pull request commands available for use with this pull request.

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Apr 24, 2026

❗ This change is not yet ready to be integrated.
See the Progress checklist in the description for automated requirements.

@openjdk openjdk Bot added security security-dev@openjdk.org hotspot hotspot-dev@openjdk.org core-libs core-libs-dev@openjdk.org labels Apr 24, 2026
@openjdk
Copy link
Copy Markdown

openjdk Bot commented Apr 24, 2026

@mgronlun The following labels will be automatically applied to this pull request:

  • core-libs
  • hotspot
  • security

When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing lists. If you would like to change these labels, use the /label pull request command.

@openjdk
Copy link
Copy Markdown

openjdk Bot commented Apr 24, 2026

The total number of required reviews for this PR has been set to 2 based on the presence of this label: hotspot. This can be overridden with the /reviewers command.

@openjdk openjdk Bot added the rfr Pull request is ready for review label Apr 24, 2026
@mgronlun
Copy link
Copy Markdown
Author

/label remove security

@mgronlun
Copy link
Copy Markdown
Author

/label add hotspot-jfr

@openjdk openjdk Bot removed the security security-dev@openjdk.org label Apr 24, 2026
@openjdk
Copy link
Copy Markdown

openjdk Bot commented Apr 24, 2026

@mgronlun
The security label was successfully removed.

@openjdk openjdk Bot added the hotspot-jfr hotspot-jfr-dev@openjdk.org label Apr 24, 2026
@openjdk
Copy link
Copy Markdown

openjdk Bot commented Apr 24, 2026

@mgronlun
The hotspot-jfr label was successfully added.

@mlbridge
Copy link
Copy Markdown

mlbridge Bot commented Apr 24, 2026

Webrevs

@AlanBateman
Copy link
Copy Markdown
Contributor

AlanBateman commented Apr 25, 2026

While there are many unused bits in the slot field, there is something a bit uncomfortable with encoding a the epoch generation into a final field. Did you consider adding an additional field, or even an injected field that is conditionally compiled in with #if INCLUDE_JFR ?

if (currentEpoch != (slot >> JfrEpochGeneration.EPOCH_SHIFT_INT)) {
int newRootFieldSlotEpoch =
currentEpoch << JfrEpochGeneration.EPOCH_SHIFT_INT | (slot & JfrEpochGeneration.VALUE_MASK_INT);
return JfrEpochGeneration.compareAndExchange(root, SLOT_OFFSET, slot, newRootFieldSlotEpoch);
Copy link
Copy Markdown
Member

@merykitty merykitty Apr 25, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Drive-by-comment: Can you make slot non-final? I'm afraid there is a chance the JIT will not produce correct code for accesses into this field if it lies about its finality. See JDK-8379875.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will leave the slot final, and instead inject a dedicated field for the epoch value.

@mgronlun
Copy link
Copy Markdown
Author

Adding an injected field for the epoch generation will be safer, of course. Sometimes, we try to preserve the footprint, and the encoding scheme might need to be implemented in existing fields to maintain it. If that is not considered a problem here, I will add an injected field.

@egahlin
Copy link
Copy Markdown
Member

egahlin commented Apr 26, 2026

I think it would be good to mention in the event description that only one event per field, per chunk, is recorded, and there might be more mutations, possibly with different stack traces.

Perhaps rename the label to make it clear, e.g. "Final Field Mutation Sample", or something similar.

@mgronlun
Copy link
Copy Markdown
Author

mgronlun commented Apr 26, 2026

Attempting to add an injected field did not work because you cannot reflect an injected field, not even using Unsafe. So I cannot read the value of an injected field from Java. Either we go with the encoding in the slot field, or I will add a separate non-final field "jfrEpoch" to java.lang.reflect.Field. Thoughts?

@mgronlun
Copy link
Copy Markdown
Author

mgronlun commented Apr 27, 2026

Updated with an alternative provided by @merykitty to make the slot non-final. As well as @egahlin's suggestions for better event descriptions.

If we don't want to use the "slot" field, we must introduce another non-final int field, explicitly (not injected), dedicated to holding the encoded epoch. It can be done, I have no preferences either way.

@AlanBateman
Copy link
Copy Markdown
Contributor

Attempting to add an injected field did not work because you cannot reflect an injected field, not even using Unsafe. So I cannot read the value of an injected field from Java. Either we go with the encoding in the slot field, or I will add a separate non-final field "jfrEpoch" to java.lang.reflect.Field. Thoughts?

I assume an injected field would be feasible if all the access were via JVM funtions rather than reflect/Unsafe but that would add complexity as it might end up needing instrinics. In that case, an explicit field would be better so the slot field is not overridden.

@mgronlun
Copy link
Copy Markdown
Author

Attempting to add an injected field did not work because you cannot reflect an injected field, not even using Unsafe. So I cannot read the value of an injected field from Java. Either we go with the encoding in the slot field, or I will add a separate non-final field "jfrEpoch" to java.lang.reflect.Field. Thoughts?

I assume an injected field would be feasible if all the access were via JVM funtions rather than reflect/Unsafe but that would add complexity as it might end up needing instrinics. In that case, an explicit field would be better so the slot field is not overridden.

Sounds like @AlanBateman suggests a dedicated field for the jfrEpoch - coming up.

@Label("Final Field Mutation")
@Label("Final Field Mutation Sample")
@Description("One event per field, per chunk, is recorded." +
"There might be more mutations, possibly with different stack traces")
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description is "developer facing" and I wonder if this will be understandable.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right - will reformulate it.

@mgronlun
Copy link
Copy Markdown
Author

mgronlun commented Apr 27, 2026

Attempting to add an injected field did not work because you cannot reflect an injected field, not even using Unsafe. So I cannot read the value of an injected field from Java. Either we go with the encoding in the slot field, or I will add a separate non-final field "jfrEpoch" to java.lang.reflect.Field. Thoughts?

I assume an injected field would be feasible if all the access were via JVM funtions rather than reflect/Unsafe but that would add complexity as it might end up needing instrinics. In that case, an explicit field would be better so the slot field is not overridden.

Yes, we can do an intrinsic too, if we really want to tuck this away. I can look into that alternative as well, since I already built similar intrinsics to read the jfrEpoch, which are currently in use for virtual threads.

@mgronlun
Copy link
Copy Markdown
Author

mgronlun commented Apr 27, 2026

Attempting to add an injected field did not work because you cannot reflect an injected field, not even using Unsafe. So I cannot read the value of an injected field from Java. Either we go with the encoding in the slot field, or I will add a separate non-final field "jfrEpoch" to java.lang.reflect.Field. Thoughts?

I assume an injected field would be feasible if all the access were via JVM funtions rather than reflect/Unsafe but that would add complexity as it might end up needing instrinics. In that case, an explicit field would be better so the slot field is not overridden.

Yes, we can do an intrinsic too, if we really want to tuck this away. I can look into that alternative as well, since I already built similar intrinsics to read the jfrEpoch, which are currently in use for virtual threads.

Ok, let's try this design, which can be generalized more broadly: we inject a field into those classes that need this support. The JfrEpochGeneration class will get an intrinsic to read and potentially cas-update any instance passed to it.

@openjdk openjdk Bot added the security security-dev@openjdk.org label May 1, 2026
@openjdk
Copy link
Copy Markdown

openjdk Bot commented May 1, 2026

@mgronlun security has been added to this pull request based on files touched in new commit(s).

@mgronlun
Copy link
Copy Markdown
Author

mgronlun commented May 1, 2026

Updated with a solution that uses injected fields and extendable intrinsics.

@mgronlun
Copy link
Copy Markdown
Author

mgronlun commented May 1, 2026

/label remove security

@openjdk openjdk Bot removed the security security-dev@openjdk.org label May 1, 2026
@openjdk
Copy link
Copy Markdown

openjdk Bot commented May 1, 2026

@mgronlun
The security label was successfully removed.

Copy link
Copy Markdown
Contributor

@AlanBateman AlanBateman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a pass over the updated implementation that uses the injected fields and it looks good.

// Epoch 4.
validate(secondRecording, 3);
secondRecording.close();
validate(firstRecording, 9);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't seen a test that has two recordings in use at the same time. The event isn't explicitly enabled for secondRecording, is the nesting relevant here? Also does the stop + start guaranteed to bump the epoch?

Copy link
Copy Markdown
Author

@mgronlun mgronlun May 7, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have many JFR tests that start multiple, nested recordings. Since a recording cannot turn off an event enabled by another recording, and there is only one recording stream in total, the second recording inherits the enabled event (the event setting is a union of 1 v 0), which means the event is also enabled for the subsequent recording.

Start and stop are both implemented as rotations, and rotations use a stop-the-world safepoint to bump the epoch.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasn't sure how it worked with concurrent recordings so your message is useful, thanks. I will note that I did read through the Recording API docs but couldn't find anything obvious that documents this.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's really an implementation detail.

int len = next_length(); // 0 or length in [1..5]
if (len == 0) break;
_position += len;
++actual;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a pre-existing bug, should it be tracked separately?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its a pre-existing bug, indeed. I will refactor it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Refactored into #31077

Copy link
Copy Markdown
Contributor

@AlanBateman AlanBateman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any other comments, thanks for taking this on.

@mgronlun
Copy link
Copy Markdown
Author

mgronlun commented May 7, 2026

I don't have any other comments, thanks for taking this on.

Many thanks for reviewing this, @AlanBateman. Cheers.

@vnkozlov
Copy link
Copy Markdown
Contributor

vnkozlov commented May 7, 2026

@mgronlun how important jdk.jfr.internal.JVM::getClassId?

While looking on external addresses referenced in C2 intrinsics we noticed that JFR's getClassId is not generated:

1214  456    b    4       jdk.jfr.jvm.TestJFRIntrinsic::getClassIdIntrinsic (22 bytes)
                              @ 1   jdk.jfr.internal.JVM::getClassId (0 bytes)   failed to inline: native method

_getClassId is missing in LibraryCallKit::try_to_inline() and C2Compiler::is_intrinsic_supported(). Even so we have
LibraryCallKit::inline_native_classID().

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

core-libs core-libs-dev@openjdk.org hotspot hotspot-dev@openjdk.org hotspot-jfr hotspot-jfr-dev@openjdk.org rfr Pull request is ready for review

Development

Successfully merging this pull request may close these issues.

5 participants