-
Notifications
You must be signed in to change notification settings - Fork 28.9k
[SPARK-24421][BUILD][CORE] Accessing sun.misc.Cleaner in JDK11 #22993
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
Conversation
…. Other related changes to get JDK 11 working, to test
|
Test build #98653 has finished for PR 22993 at commit
|
|
Test build #98654 has finished for PR 22993 at commit
|
|
Test build #4422 has finished for PR 22993 at commit
|
|
Test build #98686 has finished for PR 22993 at commit
|
|
what settings we need to allow |
| private static final Method CLEANER_CREATE_METHOD; | ||
| static { | ||
| // The implementation of Cleaner changed from JDK 8 to 9 | ||
| int majorVersion = Integer.parseInt(System.getProperty("java.version").split("\\.")[0]); |
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 there a defined fixed format for this?
we are doing some java version check and found very different format from different JDK sources (Oracle vs OpenJDK vs IBM ...)
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.
From Java 9, here is a new definition. In the following example, I found a few exceptions.
I confirmed it can work for OpenJDK, OpenJ9, and IBM JDK 8 by running the following code
public class Version {
public static void main(String[] args){
System.out.println("jave.specification.version=" + System.getProperty("java.specification.version"));
System.out.println("jave.version=" + System.getProperty("java.version"));
System.out.println("jave.version.split(\".\")[0]=" + System.getProperty("java.version").split("\\.")[0]);
}
}
OpenJDK
$ ../OpenJDK-8/java -version
java version "1.8.0_162"
Java(TM) SE Runtime Environment (build 1.8.0_162-b12)
Java HotSpot(TM) 64-Bit Server VM (build 25.162-b12, mixed mode)
$ ../OpenJDK-8/java Version
jave.specification.version=1.8
jave.version=1.8.0_162
jave.version.split(".")[0]=1
$ ../OpenJDK-9/java -version
openjdk version "9"
OpenJDK Runtime Environment (build 9+181)
OpenJDK 64-Bit Server VM (build 9+181, mixed mode)
$ ../OpenJDK-9/java Version
jave.specification.version=9
jave.version=9
jave.version.split(".")[0]=9
$ ../OpenJDK-11/java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment 18.9 (build 11.0.1+13)
OpenJDK 64-Bit Server VM 18.9 (build 11.0.1+13, mixed mode)
$ ../OpenJDK-11/java Version
jave.specification.version=11
jave.version=11.0.1
jave.version.split(".")[0]=11
OpenJ9
$ ../OpenJ9-8/java -version
openjdk version "1.8.0_192"
OpenJDK Runtime Environment (build 1.8.0_192-b12)
Eclipse OpenJ9 VM (build openj9-0.11.0, JRE 1.8.0 Windows 10 amd64-64-Bit Compressed References 20181019_105 (JIT enabled, AOT enabled)
OpenJ9 - 090ff9dc
OMR - ea548a66
JCL - 51609250b5 based on jdk8u192-b12)
$ ../OpenJ9-8/java Version
jave.specification.version=1.8
jave.version=1.8.0_192
jave.version.split(".")[0]=1
$ ../OpenJ9-9/java -version
openjdk version "9.0.4-adoptopenjdk"
OpenJDK Runtime Environment (build 9.0.4-adoptopenjdk+12)
Eclipse OpenJ9 VM (build openj9-0.9.0, JRE 9 Windows 8.1 amd64-64-Bit Compressed References 20180814_161 (JIT enabled, AOT enabled)
OpenJ9 - 24e53631
OMR - fad6bf6e
JCL - feec4d2ae based on jdk-9.0.4+12)
$ ../OpenJ9-9/java Version
jave.specification.version=9
jave.version=9.0.4-adoptopenjdk
jave.version.split(".")[0]=9
$ ../OpenJ9-11/java -version
openjdk version "11.0.1" 2018-10-16
OpenJDK Runtime Environment AdoptOpenJDK (build 11.0.1+13)
Eclipse OpenJ9 VM AdoptOpenJDK (build openj9-0.11.0, JRE 11 Windows 10 amd64-64-Bit Compressed References 20181020_83 (JIT enabled, AOT enabled)
OpenJ9 - 090ff9dc
OMR - ea548a66
JCL - f62696f378 based on jdk-11.0.1+13)
$ ../OpenJ9-11/java Version
jave.specification.version=11
jave.version=11.0.1
jave.version.split(".")[0]=11
IBM JDK
$ ../IBMJDK-8/java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build pwa6480-20150129_02)
IBM J9 VM (build 2.8, JRE 1.8.0 Windows 8.1 amd64-64 Compressed References 20150116_231420 (JIT enabled, AOT enabled)
J9VM - R28_Java8_GA_20150116_2030_B231420
JIT - tr.r14.java_20150109_82886.02
GC - R28_Java8_GA_20150116_2030_B231420_CMPRSS
J9CL - 20150116_231420)
JCL - 20150123_01 based on Oracle jdk8u31-b12
$ ../IBMJDK-8/java Version
jave.specification.version=1.8
jave.version=1.8.0
jave.version.split(".")[0]=1
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 should be safe, but I recall that some early-access Java 9 builds had a version like "9-ea". Maybe I should make the regex grab the leading digits only to be safe.
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 like it could be java.version=1.8.0_192 or java.version=11.0.1
ie. first integer or second integer (1.8 => 8?)
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.
That's right, but Java versions <= 8 are always 1.8, 1.7, etc. We're just detecting Java 9+ here, and for Java 8 and earlier, the first digit is 1 and so will compare as less than 9.
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.
How about using java.specification.version and parse it as double? Then check if greater than 2.
|
@felixcheung the JVM flag would be something like I don't know of a better solution right now, and that one's not bad, but I will write up provisional release notes in the JIRA that explain this. |
|
Test build #98700 has finished for PR 22993 at commit
|
|
|
||
| private static final Method CLEANER_CREATE_METHOD; | ||
| static { | ||
| // The implementation of Cleaner changed from JDK 8 to 9 |
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.
Why do we need to reference java 9, its dead right and it will have no LTS AFAIK.
Shouldnt we only support LTS?
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.
Yes, this comment is just noting that the change happened between Java 8 and 9. We are targeting support for Java 11. However I expect virtually all of the changes that we have to make are due to changes in Java 9. (And I have no reason to believe Java 9-10 wouldn't work; we want them to work too)
| } else { | ||
| // scalastyle:off classforname | ||
| val cleanerMethod = | ||
| Class.forName("sun.misc.Unsafe").getMethod("invokeCleaner", classOf[ByteBuffer]) |
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.
Should we use Utils forname method here?
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 wasn't sure whether it was good or not to use the Spark classloader, as that method does. If there's any good reason to, sure. This is kind of a special situation, but don't know if it must use Class directly.
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.
Probably it is safer not to mix strategies (https://www.javaworld.com/article/2077344/core-java/find-a-way-out-of-the-classloader-maze.html). Utils forName method first checks the thread context class loader and from what I see this method is called in numerous places. Spark creates custom classloaders AFAIK so the question is from which classloader chain you want this class loading to happen. I think both should work, it is not a custom class or anything.
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.
The problem is, we can't change the usage in Platform.java, so we'd be mixing strategies here. There are already usages of Class.forName for this reason. I don't really object to changing one instance here; I have no reason to believe either of them fails. But is that meeting your goal?
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.
ok, in general I cant think of something failing at the moment, just wanted to provide some feedback that this mixes strategies.
| // still accessible with reflection. | ||
| private val bufferCleaner: DirectBuffer => Unit = | ||
| // Split java.version on non-digit chars: | ||
| if (System.getProperty("java.version").split("\\D+").head.toInt < 9) { |
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 this java detection method used elsewhere? Maybe other parts of the codebase could benefit from this.
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.
In the Platform.java class we can't rely on any other libs or methods, so I wrote something like this. I just followed suit here. My reasoning was that this is a simple check of major version number, specific to 8 vs 9. I don't mind using a library method here, but we can't in the same context in Platform.java.
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 to using java.specification.version, I admit I had not seen that used before, not in Spark at least. Is it more reliable? I had always seen java.version parsed. Here we really just care about the initial digit in any event.
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.
Apache commons is an example:
import org.apache.commons.lang3.JavaVersion;
import org.apache.commons.lang3.SystemUtils;
SystemUtils.isJavaVersionAtLeast(JavaVersion.JAVA_9);
That dependency already exists in the project, so it is for free, and that method is based on that property:
https://commons.apache.org/proper/commons-lang/apidocs/org/apache/commons/lang3/JavaVersion.html
Their implementation is here:
https://github.com/apache/commons-lang/blob/c4d0dbcb56b8980b1b3b7c85d00ad6540788c08e/src/main/java/org/apache/commons/lang3/JavaVersion.java#L228
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.
That's fine. We can't use it in Platform, and have to make sure this dependency is managed up to a version that handles later Java versions (may already be). So we have diverging approaches for related code here, but, they don't strictly do the same thing nor do the two changes here have to go hand-in-hand. I'm not against it either but that's why I was hesitant about it.
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.
It is at least a source of inspiration for the implementation though, even if you dont rely on this dependency. It is updated by the way up to current versions from what I checked.
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 like it. I made both changes. Actually there's just one other location where java.version is parsed where we can use lang3, so I just changed that too. That's more convincing as it's more consistent.
|
Test build #98802 has finished for PR 22993 at commit
|
|
Test build #98804 has finished for PR 22993 at commit
|
|
Any more comments? I hope this much is safe and gets Java 11 compiling and most tests passing. |
|
Merged to master |
…. Other related changes to get JDK 11 working, to test ## What changes were proposed in this pull request? - Access `sun.misc.Cleaner` (Java 8) and `jdk.internal.ref.Cleaner` (JDK 9+) by reflection (note: the latter only works if illegal reflective access is allowed) - Access `sun.misc.Unsafe.invokeCleaner` in Java 9+ instead of `sun.misc.Cleaner` (Java 8) In order to test anything on JDK 11, I also fixed a few small things, which I include here: - Fix minor JDK 11 compile issues - Update scala plugin, Jetty for JDK 11, to facilitate tests too This doesn't mean JDK 11 tests all pass now, but lots do. Note also that the JDK 9+ solution for the Cleaner has a big caveat. ## How was this patch tested? Existing tests. Manually tested JDK 11 build and tests, and tests covering this change appear to pass. All Java 8 tests should still pass, but this change alone does not achieve full JDK 11 compatibility. Closes apache#22993 from srowen/SPARK-24421. Authored-by: Sean Owen <[email protected]> Signed-off-by: Sean Owen <[email protected]>
| Cleaner cleaner = Cleaner.create(buffer, () -> freeMemory(memory)); | ||
| cleanerField.set(buffer, cleaner); | ||
| ByteBuffer buffer = (ByteBuffer) DBB_CONSTRUCTOR.newInstance(memory, size); | ||
| if (CLEANER_CREATE_METHOD != null) { |
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.
See #23424 ; I now think this was an error.
… if Cleaner can't be set ## What changes were proposed in this pull request? In Java 9+ we can't use sun.misc.Cleaner by default anymore, and this was largely handled in #22993 However I think the change there left a significant problem. If a DirectByteBuffer is allocated using the reflective hack in Platform, now, we by default can't set a Cleaner. But I believe this means the memory isn't freed promptly or possibly at all. If a Cleaner can't be set, I think we need to use normal APIs to allocate the direct ByteBuffer. According to comments in the code, the downside is simply that the normal APIs will check and impose limits on how much off-heap memory can be allocated. Per the original review on #22993 this much seems fine, as either way in this case the user would have to add a JVM setting (increase max, or allow the reflective access). ## How was this patch tested? Existing tests. This resolved an OutOfMemoryError in Java 11 from TimSort tests without increasing test heap size. (See #23419 (comment) ) This suggests there is a problem and that this resolves it. Closes #23424 from srowen/SPARK-24421.2. Authored-by: Sean Owen <[email protected]> Signed-off-by: Sean Owen <[email protected]>
…. Other related changes to get JDK 11 working, to test ## What changes were proposed in this pull request? - Access `sun.misc.Cleaner` (Java 8) and `jdk.internal.ref.Cleaner` (JDK 9+) by reflection (note: the latter only works if illegal reflective access is allowed) - Access `sun.misc.Unsafe.invokeCleaner` in Java 9+ instead of `sun.misc.Cleaner` (Java 8) In order to test anything on JDK 11, I also fixed a few small things, which I include here: - Fix minor JDK 11 compile issues - Update scala plugin, Jetty for JDK 11, to facilitate tests too This doesn't mean JDK 11 tests all pass now, but lots do. Note also that the JDK 9+ solution for the Cleaner has a big caveat. ## How was this patch tested? Existing tests. Manually tested JDK 11 build and tests, and tests covering this change appear to pass. All Java 8 tests should still pass, but this change alone does not achieve full JDK 11 compatibility. Closes apache#22993 from srowen/SPARK-24421. Authored-by: Sean Owen <[email protected]> Signed-off-by: Sean Owen <[email protected]>
… if Cleaner can't be set ## What changes were proposed in this pull request? In Java 9+ we can't use sun.misc.Cleaner by default anymore, and this was largely handled in apache#22993 However I think the change there left a significant problem. If a DirectByteBuffer is allocated using the reflective hack in Platform, now, we by default can't set a Cleaner. But I believe this means the memory isn't freed promptly or possibly at all. If a Cleaner can't be set, I think we need to use normal APIs to allocate the direct ByteBuffer. According to comments in the code, the downside is simply that the normal APIs will check and impose limits on how much off-heap memory can be allocated. Per the original review on apache#22993 this much seems fine, as either way in this case the user would have to add a JVM setting (increase max, or allow the reflective access). ## How was this patch tested? Existing tests. This resolved an OutOfMemoryError in Java 11 from TimSort tests without increasing test heap size. (See apache#23419 (comment) ) This suggests there is a problem and that this resolves it. Closes apache#23424 from srowen/SPARK-24421.2. Authored-by: Sean Owen <[email protected]> Signed-off-by: Sean Owen <[email protected]>
…. Other related changes to get JDK 11 working, to test ## What changes were proposed in this pull request? - Access `sun.misc.Cleaner` (Java 8) and `jdk.internal.ref.Cleaner` (JDK 9+) by reflection (note: the latter only works if illegal reflective access is allowed) - Access `sun.misc.Unsafe.invokeCleaner` in Java 9+ instead of `sun.misc.Cleaner` (Java 8) In order to test anything on JDK 11, I also fixed a few small things, which I include here: - Fix minor JDK 11 compile issues - Update scala plugin, Jetty for JDK 11, to facilitate tests too This doesn't mean JDK 11 tests all pass now, but lots do. Note also that the JDK 9+ solution for the Cleaner has a big caveat. ## How was this patch tested? Existing tests. Manually tested JDK 11 build and tests, and tests covering this change appear to pass. All Java 8 tests should still pass, but this change alone does not achieve full JDK 11 compatibility. Closes apache#22993 from srowen/SPARK-24421. Authored-by: Sean Owen <[email protected]> Signed-off-by: Sean Owen <[email protected]> (cherry picked from commit 722369e)
…. Other related changes to get JDK 11 working, to test ## What changes were proposed in this pull request? - Access `sun.misc.Cleaner` (Java 8) and `jdk.internal.ref.Cleaner` (JDK 9+) by reflection (note: the latter only works if illegal reflective access is allowed) - Access `sun.misc.Unsafe.invokeCleaner` in Java 9+ instead of `sun.misc.Cleaner` (Java 8) In order to test anything on JDK 11, I also fixed a few small things, which I include here: - Fix minor JDK 11 compile issues - Update scala plugin, Jetty for JDK 11, to facilitate tests too This doesn't mean JDK 11 tests all pass now, but lots do. Note also that the JDK 9+ solution for the Cleaner has a big caveat. ## How was this patch tested? Existing tests. Manually tested JDK 11 build and tests, and tests covering this change appear to pass. All Java 8 tests should still pass, but this change alone does not achieve full JDK 11 compatibility. Closes apache#22993 from srowen/SPARK-24421. Authored-by: Sean Owen <[email protected]> Signed-off-by: Sean Owen <[email protected]> (cherry picked from commit 722369e)
…. Other related changes to get JDK 11 working, to test
What changes were proposed in this pull request?
sun.misc.Cleaner(Java 8) andjdk.internal.ref.Cleaner(JDK 9+) by reflection (note: the latter only works if illegal reflective access is allowed)sun.misc.Unsafe.invokeCleanerin Java 9+ instead ofsun.misc.Cleaner(Java 8)In order to test anything on JDK 11, I also fixed a few small things, which I include here:
This doesn't mean JDK 11 tests all pass now, but lots do. Note also that the JDK 9+ solution for the Cleaner has a big caveat.
How was this patch tested?
Existing tests. Manually tested JDK 11 build and tests, and tests covering this change appear to pass. All Java 8 tests should still pass, but this change alone does not achieve full JDK 11 compatibility.