-
Notifications
You must be signed in to change notification settings - Fork 729
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
Investigate limiting System.identityHashcode() to positive numbers #3062
Comments
Are there open/closed issues where consumers of OpenJ9 were having issues related to incorrectly assuming hashcodes were positive? |
Yes, there are libraries (Hessian?) that assume that the hashcode is positive and use it to index into arrays. Some adopters have run into blocking issues as their stack depends on libraries that have this assumption. Providing this as an option eases the migration path. |
The likely place to hack for this is https://github.com/eclipse/openj9/blob/2f295ea91cdd97ff37eb5d164fc63014ea718bc7/runtime/oti/ObjectHash.hpp#L177 Changing it here will ensure the option + mask is only rechecked until the object is moved. All the fast paths that read the hashcode from moved objects shouldn't require any modification. Will attempt to add an -XX option and investigate the performance implications of this change. |
Maybe if the result of the -XX option is stored in a private static final boolean variable and if that variable is checked in the identityHashCode at the spot @DanHeidinga pointed out, then the JIT would be able to fold away the extra test at compile time via it's usual static final optimizations. |
I assume you were thinking of an -XX option to work around this particular migration issue in terms of different JCL behavior. One question is whether you prefer different -XX options for different JCL issues such as this one (assuming there are more of them we will find down the line) or have one option that changes all the JCL behaviours needed to make migration easier. |
@vijaysun-omr I've looked for how to push the option as low in the code as possible - down to the native hash operation - to ensure there is only 1 place that needs to be modified and that there's never a chance of an inconsistent hashcode being returned.
That's an interesting idea. I think we should start with a single option. If we end up with multiple migration related options, they can be grouped later under a single -XX:EasyMigration (or similar) option. |
Thanks @DanHeidinga I am in agreement with your approach to wait for the "second" migration related option before considering grouping under one option. Is there a PR for the change to the native hash ? |
When you are ready, please open an issue at the OpenJ9-docs repo using this template: https://github.com/eclipse/openj9-docs/issues/new?template=new-documentation-change.md |
Add command line option "-XX:[+|-]PositiveIdentityHash" to enable/disable forcing identity hash code to be a positive value. Fixes: eclipse-openj9#3062 Signed-off-by: Graham Chapman <[email protected]>
Thanks @gacholio for coding this up. For those following along, the next step is to check a microbench for any regressions and then we'll proceed with the merge provided the results are ok |
Doc issue: eclipse-openj9/openj9-docs#109 |
Fixes JetBrains/compose-multiplatform#991 Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2): -1366493741 192407236 when we compare them, we can catch an overflow: `-1366493741 - 1924072363` will be more than 0 see eclipse-openj9/openj9#3062 That leads to wrong behaviour of IdentityArraySet, and so of Recomposer: ``` alreadyComposed.add(composition) println(alreadyComposed.contains(composition)) // will print `false` ``` The crash: ``` Caused by: java.lang.IllegalStateException: pending composition has not been applied at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448) at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635) at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793) at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426) at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at java.base/java.lang.Thread.run(Thread.java:853) ``` The reproducer: ``` import androidx.compose.runtime.AbstractApplier import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MonotonicFrameClock import androidx.compose.runtime.currentRecomposeScope import androidx.compose.runtime.rememberCompositionContext import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.withRunningRecomposer import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield import java.util.concurrent.Executors.newSingleThreadExecutor import kotlin.random.Random fun main() { runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) { repeat(300) { num -> println("test $num") withRunningRecomposer { val composition = Composition(TestApplier, it) composition.setContent { val scope = currentRecomposeScope Test(Random.nextInt()) Test(Random.nextInt()) LaunchedEffect(Unit) { scope.invalidate() } } yield() yield() } } } } @composable fun Test(test: Int) { val latestContent = rememberUpdatedState(test) latestContent.value val parent = rememberCompositionContext() LaunchedEffect(Unit) { val composition1 = Composition(TestApplier, parent) composition1.setContent { latestContent.value } val composition2 = Composition(TestApplier, parent) composition2.setContent { latestContent.value } } } internal object TestApplier: AbstractApplier<Unit>(Unit) { override fun insertTopDown(index: Int, instance: Unit) = Unit override fun insertBottomUp(index: Int, instance: Unit) = Unit override fun remove(index: Int, count: Int) = Unit override fun move(from: Int, to: Int, count: Int) = Unit override fun onClear() = Unit } object TestClock : MonotonicFrameClock { override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { return onFrame(System.nanoTime()) } } ``` java --version: ``` openjdk 15.0.2 2021-01-19 OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7) Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled) OpenJ9 - 345e1b09e OMR - 741e94ea8 JCL - 863b523566 based on jdk-15.0.2+7) ``` OS: Ubuntu 20.0.4 Test: manual (see the reproducer) Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
Fixes JetBrains/compose-multiplatform#991 Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2): -1366493741 192407236 when we compare them, we can catch an overflow: `-1366493741 - 1924072363` will be more than 0 see eclipse-openj9/openj9#3062 That leads to wrong behaviour of IdentityArraySet, and so of Recomposer: ``` alreadyComposed.add(composition) println(alreadyComposed.contains(composition)) // will print `false` ``` The crash: ``` Caused by: java.lang.IllegalStateException: pending composition has not been applied at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448) at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635) at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793) at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426) at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at java.base/java.lang.Thread.run(Thread.java:853) ``` The reproducer: ``` import androidx.compose.runtime.AbstractApplier import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MonotonicFrameClock import androidx.compose.runtime.currentRecomposeScope import androidx.compose.runtime.rememberCompositionContext import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.withRunningRecomposer import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield import java.util.concurrent.Executors.newSingleThreadExecutor import kotlin.random.Random fun main() { runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) { repeat(300) { num -> println("test $num") withRunningRecomposer { val composition = Composition(TestApplier, it) composition.setContent { val scope = currentRecomposeScope Test(Random.nextInt()) Test(Random.nextInt()) LaunchedEffect(Unit) { scope.invalidate() } } yield() yield() } } } } @composable fun Test(test: Int) { val latestContent = rememberUpdatedState(test) latestContent.value val parent = rememberCompositionContext() LaunchedEffect(Unit) { val composition1 = Composition(TestApplier, parent) composition1.setContent { latestContent.value } val composition2 = Composition(TestApplier, parent) composition2.setContent { latestContent.value } } } internal object TestApplier: AbstractApplier<Unit>(Unit) { override fun insertTopDown(index: Int, instance: Unit) = Unit override fun insertBottomUp(index: Int, instance: Unit) = Unit override fun remove(index: Int, count: Int) = Unit override fun move(from: Int, to: Int, count: Int) = Unit override fun onClear() = Unit } object TestClock : MonotonicFrameClock { override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { return onFrame(System.nanoTime()) } } ``` java --version: ``` openjdk 15.0.2 2021-01-19 OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7) Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled) OpenJ9 - 345e1b09e OMR - 741e94ea8 JCL - 863b523566 based on jdk-15.0.2+7) ``` OS: Ubuntu 20.0.4 Test: manual (see the reproducer) Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
Fixes JetBrains/compose-multiplatform#991 Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2): -1366493741 192407236 when we compare them, we can catch an overflow: `-1366493741 - 1924072363` will be more than 0 see eclipse-openj9/openj9#3062 That leads to wrong behaviour of IdentityArraySet, and so of Recomposer: ``` alreadyComposed.add(composition) println(alreadyComposed.contains(composition)) // will print `false` ``` The crash: ``` Caused by: java.lang.IllegalStateException: pending composition has not been applied at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448) at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635) at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793) at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426) at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at java.base/java.lang.Thread.run(Thread.java:853) ``` The reproducer: ``` import androidx.compose.runtime.AbstractApplier import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MonotonicFrameClock import androidx.compose.runtime.currentRecomposeScope import androidx.compose.runtime.rememberCompositionContext import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.withRunningRecomposer import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield import java.util.concurrent.Executors.newSingleThreadExecutor import kotlin.random.Random fun main() { runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) { repeat(300) { num -> println("test $num") withRunningRecomposer { val composition = Composition(TestApplier, it) composition.setContent { val scope = currentRecomposeScope Test(Random.nextInt()) Test(Random.nextInt()) LaunchedEffect(Unit) { scope.invalidate() } } yield() yield() } } } } @composable fun Test(test: Int) { val latestContent = rememberUpdatedState(test) latestContent.value val parent = rememberCompositionContext() LaunchedEffect(Unit) { val composition1 = Composition(TestApplier, parent) composition1.setContent { latestContent.value } val composition2 = Composition(TestApplier, parent) composition2.setContent { latestContent.value } } } internal object TestApplier: AbstractApplier<Unit>(Unit) { override fun insertTopDown(index: Int, instance: Unit) = Unit override fun insertBottomUp(index: Int, instance: Unit) = Unit override fun remove(index: Int, count: Int) = Unit override fun move(from: Int, to: Int, count: Int) = Unit override fun onClear() = Unit } object TestClock : MonotonicFrameClock { override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { return onFrame(System.nanoTime()) } } ``` java --version: ``` openjdk 15.0.2 2021-01-19 OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7) Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled) OpenJ9 - 345e1b09e OMR - 741e94ea8 JCL - 863b523566 based on jdk-15.0.2+7) ``` OS: Ubuntu 20.0.4 Test: manual (see the reproducer) Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
Fixes JetBrains/compose-multiplatform#991 Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2): -1366493741 192407236 when we compare them, we can catch an overflow: `-1366493741 - 1924072363` will be more than 0 see eclipse-openj9/openj9#3062 That leads to wrong behaviour of IdentityArraySet, and so of Recomposer: ``` alreadyComposed.add(composition) println(alreadyComposed.contains(composition)) // will print `false` ``` The crash: ``` Caused by: java.lang.IllegalStateException: pending composition has not been applied at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448) at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635) at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793) at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426) at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at java.base/java.lang.Thread.run(Thread.java:853) ``` The reproducer: ``` import androidx.compose.runtime.AbstractApplier import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MonotonicFrameClock import androidx.compose.runtime.currentRecomposeScope import androidx.compose.runtime.rememberCompositionContext import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.withRunningRecomposer import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield import java.util.concurrent.Executors.newSingleThreadExecutor import kotlin.random.Random fun main() { runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) { repeat(300) { num -> println("test $num") withRunningRecomposer { val composition = Composition(TestApplier, it) composition.setContent { val scope = currentRecomposeScope Test(Random.nextInt()) Test(Random.nextInt()) LaunchedEffect(Unit) { scope.invalidate() } } yield() yield() } } } } @composable fun Test(test: Int) { val latestContent = rememberUpdatedState(test) latestContent.value val parent = rememberCompositionContext() LaunchedEffect(Unit) { val composition1 = Composition(TestApplier, parent) composition1.setContent { latestContent.value } val composition2 = Composition(TestApplier, parent) composition2.setContent { latestContent.value } } } internal object TestApplier: AbstractApplier<Unit>(Unit) { override fun insertTopDown(index: Int, instance: Unit) = Unit override fun insertBottomUp(index: Int, instance: Unit) = Unit override fun remove(index: Int, count: Int) = Unit override fun move(from: Int, to: Int, count: Int) = Unit override fun onClear() = Unit } object TestClock : MonotonicFrameClock { override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { return onFrame(System.nanoTime()) } } ``` java --version: ``` openjdk 15.0.2 2021-01-19 OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7) Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled) OpenJ9 - 345e1b09e OMR - 741e94ea8 JCL - 863b523566 based on jdk-15.0.2+7) ``` OS: Ubuntu 20.0.4 Test: manual (see the reproducer) Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
Fixes JetBrains/compose-multiplatform#991 Eclipse OpenJ9 can return negative hashcodes (at least 15.0.2): -1366493741 192407236 when we compare them, we can catch an overflow: `-1366493741 - 1924072363` will be more than 0 see eclipse-openj9/openj9#3062 That leads to wrong behaviour of IdentityArraySet, and so of Recomposer: ``` alreadyComposed.add(composition) println(alreadyComposed.contains(composition)) // will print `false` ``` The crash: ``` Caused by: java.lang.IllegalStateException: pending composition has not been applied at androidx.compose.runtime.CompositionImpl.drainPendingModificationsForCompositionLocked(Composition.kt:448) at androidx.compose.runtime.CompositionImpl.recompose(Composition.kt:635) at androidx.compose.runtime.Recomposer.performRecompose(Recomposer.kt:793) at androidx.compose.runtime.Recomposer.access$performRecompose(Recomposer.kt:106) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:460) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2$2.invoke(Recomposer.kt:426) at androidx.compose.desktop.examples.example1.TestClock.withFrameNanos(Main.jvm.kt:85) at androidx.compose.runtime.Recomposer$runRecomposeAndApplyChanges$2.invokeSuspend(Recomposer.kt:426) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106) at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1130) at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:630) at java.base/java.lang.Thread.run(Thread.java:853) ``` The reproducer: ``` import androidx.compose.runtime.AbstractApplier import androidx.compose.runtime.Composable import androidx.compose.runtime.Composition import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.MonotonicFrameClock import androidx.compose.runtime.currentRecomposeScope import androidx.compose.runtime.rememberCompositionContext import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.withRunningRecomposer import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.runBlocking import kotlinx.coroutines.yield import java.util.concurrent.Executors.newSingleThreadExecutor import kotlin.random.Random fun main() { runBlocking(newSingleThreadExecutor().asCoroutineDispatcher() + TestClock) { repeat(300) { num -> println("test $num") withRunningRecomposer { val composition = Composition(TestApplier, it) composition.setContent { val scope = currentRecomposeScope Test(Random.nextInt()) Test(Random.nextInt()) LaunchedEffect(Unit) { scope.invalidate() } } yield() yield() } } } } @composable fun Test(test: Int) { val latestContent = rememberUpdatedState(test) latestContent.value val parent = rememberCompositionContext() LaunchedEffect(Unit) { val composition1 = Composition(TestApplier, parent) composition1.setContent { latestContent.value } val composition2 = Composition(TestApplier, parent) composition2.setContent { latestContent.value } } } internal object TestApplier: AbstractApplier<Unit>(Unit) { override fun insertTopDown(index: Int, instance: Unit) = Unit override fun insertBottomUp(index: Int, instance: Unit) = Unit override fun remove(index: Int, count: Int) = Unit override fun move(from: Int, to: Int, count: Int) = Unit override fun onClear() = Unit } object TestClock : MonotonicFrameClock { override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R { return onFrame(System.nanoTime()) } } ``` java --version: ``` openjdk 15.0.2 2021-01-19 OpenJDK Runtime Environment AdoptOpenJDK (build 15.0.2+7) Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.24.0, JRE 15 Linux amd64-64-Bit Compressed References 20210121_172 (JIT enabled, AOT enabled) OpenJ9 - 345e1b09e OMR - 741e94ea8 JCL - 863b523566 based on jdk-15.0.2+7) ``` OS: Ubuntu 20.0.4 Test: manual (see the reproducer) Test: ./gradlew :compose:runtime:runtime:test (without the fix we have failing Identity*Test on this JDK)
OpenJ9 allows both positive and negative identity hashcodes. This is problematic for programs that incorrectly assume hashcodes can only be positive.
Investigate adding an option to limit hashcode to be positive and determine the performance overhead of this.
Note, any such option would limit the range of hashcodes and may lead to more collisions.
The text was updated successfully, but these errors were encountered: