Skip to content

Commit ebf280d

Browse files
committed
Avoid sun.misc.Cleaner in JDK 9+ while retaining Java 8 compatibility. Other related changes to get JDK 11 working, to test
1 parent 0558d02 commit ebf280d

File tree

6 files changed

+116
-27
lines changed

6 files changed

+116
-27
lines changed

common/unsafe/src/main/java/org/apache/spark/unsafe/Platform.java

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919

2020
import java.lang.reflect.Constructor;
2121
import java.lang.reflect.Field;
22+
import java.lang.reflect.InvocationTargetException;
2223
import java.lang.reflect.Method;
2324
import java.nio.ByteBuffer;
2425

25-
import sun.misc.Cleaner;
2626
import sun.misc.Unsafe;
2727

2828
public final class Platform {
@@ -67,6 +67,59 @@ public final class Platform {
6767
unaligned = _unaligned;
6868
}
6969

70+
// Access fields and constructors once and store them, for performance:
71+
72+
private static final Constructor<?> DBB_CONSTRUCTOR;
73+
private static final Field DBB_CLEANER_FIELD;
74+
static {
75+
try {
76+
Class<?> cls = Class.forName("java.nio.DirectByteBuffer");
77+
Constructor<?> constructor = cls.getDeclaredConstructor(Long.TYPE, Integer.TYPE);
78+
constructor.setAccessible(true);
79+
Field cleanerField = cls.getDeclaredField("cleaner");
80+
cleanerField.setAccessible(true);
81+
DBB_CONSTRUCTOR = constructor;
82+
DBB_CLEANER_FIELD = cleanerField;
83+
} catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException e) {
84+
throw new IllegalStateException(e);
85+
}
86+
}
87+
88+
private static final Method CLEANER_CREATE_METHOD;
89+
static {
90+
// The implementation of Cleaner changed from JDK 8 to 9
91+
int majorVersion = Integer.parseInt(System.getProperty("java.version").split("\\.")[0]);
92+
String cleanerClassName;
93+
if (majorVersion < 9) {
94+
cleanerClassName = "sun.misc.Cleaner";
95+
} else {
96+
cleanerClassName = "jdk.internal.ref.Cleaner";
97+
}
98+
try {
99+
Class<?> cleanerClass = Class.forName(cleanerClassName);
100+
Method createMethod = cleanerClass.getMethod("create", Object.class, Runnable.class);
101+
// Accessing jdk.internal.ref.Cleaner should actually fail by default in JDK 9+,
102+
// unfortunately, unless the user has allowed access with something like
103+
// --add-opens java.base/java.lang=ALL-UNNAMED If not, we can't really use the Cleaner
104+
// hack below. It doesn't break, just means the user might run into the default JVM limit
105+
// on off-heap memory and increase it or set the flag above. This tests whether it's
106+
// available:
107+
try {
108+
createMethod.invoke(null, null, null);
109+
} catch (IllegalAccessException e) {
110+
// Don't throw an exception, but can't log here?
111+
createMethod = null;
112+
} catch (InvocationTargetException ite) {
113+
// shouldn't happen; report it
114+
throw new IllegalStateException(ite);
115+
}
116+
CLEANER_CREATE_METHOD = createMethod;
117+
} catch (ClassNotFoundException | NoSuchMethodException e) {
118+
throw new IllegalStateException(e);
119+
}
120+
121+
}
122+
70123
/**
71124
* @return true when running JVM is having sun's Unsafe package available in it and underlying
72125
* system having unaligned-access capability.
@@ -159,18 +212,18 @@ public static long reallocateMemory(long address, long oldSize, long newSize) {
159212
* MaxDirectMemorySize limit (the default limit is too low and we do not want to require users
160213
* to increase it).
161214
*/
162-
@SuppressWarnings("unchecked")
163215
public static ByteBuffer allocateDirectBuffer(int size) {
164216
try {
165-
Class<?> cls = Class.forName("java.nio.DirectByteBuffer");
166-
Constructor<?> constructor = cls.getDeclaredConstructor(Long.TYPE, Integer.TYPE);
167-
constructor.setAccessible(true);
168-
Field cleanerField = cls.getDeclaredField("cleaner");
169-
cleanerField.setAccessible(true);
170217
long memory = allocateMemory(size);
171-
ByteBuffer buffer = (ByteBuffer) constructor.newInstance(memory, size);
172-
Cleaner cleaner = Cleaner.create(buffer, () -> freeMemory(memory));
173-
cleanerField.set(buffer, cleaner);
218+
ByteBuffer buffer = (ByteBuffer) DBB_CONSTRUCTOR.newInstance(memory, size);
219+
if (CLEANER_CREATE_METHOD != null) {
220+
try {
221+
DBB_CLEANER_FIELD.set(buffer,
222+
CLEANER_CREATE_METHOD.invoke(null, buffer, (Runnable) () -> freeMemory(memory)));
223+
} catch (IllegalAccessException | InvocationTargetException e) {
224+
throw new IllegalStateException(e);
225+
}
226+
}
174227
return buffer;
175228
} catch (Exception e) {
176229
throwException(e);

core/src/main/scala/org/apache/spark/storage/StorageUtils.scala

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import java.nio.{ByteBuffer, MappedByteBuffer}
2222
import scala.collection.Map
2323
import scala.collection.mutable
2424

25+
import sun.misc.Unsafe
2526
import sun.nio.ch.DirectBuffer
2627

2728
import org.apache.spark.internal.Logging
@@ -193,6 +194,35 @@ private[spark] class StorageStatus(
193194

194195
/** Helper methods for storage-related objects. */
195196
private[spark] object StorageUtils extends Logging {
197+
198+
// In Java 8, the type of DirectBuffer.cleaner() was sun.misc.Cleaner, and it was possible
199+
// to access the method sun.misc.Cleaner.clean() to invoke it. The type changed to
200+
// jdk.internal.ref.Cleaner in later JDKs, and the .clean() method is not accessible even with
201+
// reflection. However sun.misc.Unsafe added a invokeCleaner() method in JDK 9+ and this is
202+
// still accessible with reflection.
203+
private val bufferCleaner: DirectBuffer => Unit =
204+
if (System.getProperty("java.version").split("\\.").head.toInt < 9) {
205+
// scalastyle:off classforname
206+
val cleanerMethod = Class.forName("sun.misc.Cleaner").getMethod("clean")
207+
// scalastyle:on classforname
208+
(buffer: DirectBuffer) => {
209+
// Careful to avoid the return type of .cleaner(), which changes with JDK
210+
val cleaner: AnyRef = buffer.cleaner()
211+
if (cleaner != null) {
212+
cleanerMethod.invoke(cleaner)
213+
}
214+
}
215+
} else {
216+
// scalastyle:off classforname
217+
val cleanerMethod =
218+
Class.forName("sun.misc.Unsafe").getMethod("invokeCleaner", classOf[ByteBuffer])
219+
// scalastyle:on classforname
220+
val unsafeField = classOf[Unsafe].getDeclaredField("theUnsafe")
221+
unsafeField.setAccessible(true)
222+
val unsafe = unsafeField.get(null).asInstanceOf[Unsafe]
223+
(buffer: DirectBuffer) => cleanerMethod.invoke(unsafe, buffer)
224+
}
225+
196226
/**
197227
* Attempt to clean up a ByteBuffer if it is direct or memory-mapped. This uses an *unsafe* Sun
198228
* API that will cause errors if one attempts to read from the disposed buffer. However, neither
@@ -204,14 +234,8 @@ private[spark] object StorageUtils extends Logging {
204234
def dispose(buffer: ByteBuffer): Unit = {
205235
if (buffer != null && buffer.isInstanceOf[MappedByteBuffer]) {
206236
logTrace(s"Disposing of $buffer")
207-
cleanDirectBuffer(buffer.asInstanceOf[DirectBuffer])
237+
bufferCleaner(buffer.asInstanceOf[DirectBuffer])
208238
}
209239
}
210240

211-
private def cleanDirectBuffer(buffer: DirectBuffer) = {
212-
val cleaner = buffer.cleaner()
213-
if (cleaner != null) {
214-
cleaner.clean()
215-
}
216-
}
217241
}

examples/src/main/scala/org/apache/spark/examples/LogQuery.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,13 @@ object LogQuery {
3232
| GTB7.4; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648; .NET CLR
3333
| 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR
3434
| 3.5.30729; Release=ARP)" "UD-1" - "image/jpeg" "whatever" 0.350 "-" - "" 265 923 934 ""
35-
| 62.24.11.25 images.com 1358492167 - Whatup""".stripMargin.lines.mkString,
35+
| 62.24.11.25 images.com 1358492167 - Whatup""".stripMargin.split('\n').mkString,
3636
"""10.10.10.10 - "FRED" [18/Jan/2013:18:02:37 +1100] "GET http://images.com/2013/Generic.jpg
3737
| HTTP/1.1" 304 306 "http:/referall.com" "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1;
3838
| GTB7.4; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.04506.648; .NET CLR
3939
| 3.5.21022; .NET CLR 3.0.4506.2152; .NET CLR 1.0.3705; .NET CLR 1.1.4322; .NET CLR
4040
| 3.5.30729; Release=ARP)" "UD-1" - "image/jpeg" "whatever" 0.352 "-" - "" 256 977 988 ""
41-
| 0 73.23.2.15 images.com 1358492557 - Whatup""".stripMargin.lines.mkString
41+
| 0 73.23.2.15 images.com 1358492557 - Whatup""".stripMargin.split('\n').mkString
4242
)
4343

4444
def main(args: Array[String]) {

mllib-local/src/test/scala/org/apache/spark/ml/linalg/MatricesSuite.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -862,10 +862,10 @@ class MatricesSuite extends SparkMLFunSuite {
862862
mat.toString(0, 0)
863863
mat.toString(Int.MinValue, Int.MinValue)
864864
mat.toString(Int.MaxValue, Int.MaxValue)
865-
var lines = mat.toString(6, 50).lines.toArray
865+
var lines = mat.toString(6, 50).split('\n')
866866
assert(lines.size == 5 && lines.forall(_.size <= 50))
867867

868-
lines = mat.toString(5, 100).lines.toArray
868+
lines = mat.toString(5, 100).split('\n')
869869
assert(lines.size == 5 && lines.forall(_.size <= 100))
870870
}
871871

mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -511,10 +511,10 @@ class MatricesSuite extends SparkFunSuite {
511511
mat.toString(0, 0)
512512
mat.toString(Int.MinValue, Int.MinValue)
513513
mat.toString(Int.MaxValue, Int.MaxValue)
514-
var lines = mat.toString(6, 50).lines.toArray
514+
var lines = mat.toString(6, 50).split('\n')
515515
assert(lines.size == 5 && lines.forall(_.size <= 50))
516516

517-
lines = mat.toString(5, 100).lines.toArray
517+
lines = mat.toString(5, 100).split('\n')
518518
assert(lines.size == 5 && lines.forall(_.size <= 100))
519519
}
520520

pom.xml

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -133,7 +133,7 @@
133133
<orc.version>1.5.3</orc.version>
134134
<orc.classifier>nohive</orc.classifier>
135135
<hive.parquet.version>1.6.0</hive.parquet.version>
136-
<jetty.version>9.3.24.v20180605</jetty.version>
136+
<jetty.version>9.4.12.v20180830</jetty.version>
137137
<javaxservlet.version>3.1.0</javaxservlet.version>
138138
<chill.version>0.9.3</chill.version>
139139
<ivy.version>2.4.0</ivy.version>
@@ -2016,7 +2016,7 @@
20162016
<groupId>net.alchim31.maven</groupId>
20172017
<artifactId>scala-maven-plugin</artifactId>
20182018
<!-- 3.3.1 won't work with zinc; fails to find javac from java.home -->
2019-
<version>3.2.2</version>
2019+
<version>3.4.4</version>
20202020
<executions>
20212021
<execution>
20222022
<id>eclipse-add-source</id>
@@ -2281,7 +2281,19 @@
22812281
<plugin>
22822282
<groupId>org.apache.maven.plugins</groupId>
22832283
<artifactId>maven-shade-plugin</artifactId>
2284-
<version>3.1.0</version>
2284+
<version>3.2.0</version>
2285+
<dependencies>
2286+
<dependency>
2287+
<groupId>org.ow2.asm</groupId>
2288+
<artifactId>asm</artifactId>
2289+
<version>7.0</version>
2290+
</dependency>
2291+
<dependency>
2292+
<groupId>org.ow2.asm</groupId>
2293+
<artifactId>asm-commons</artifactId>
2294+
<version>7.0</version>
2295+
</dependency>
2296+
</dependencies>
22852297
</plugin>
22862298
<plugin>
22872299
<groupId>org.apache.maven.plugins</groupId>
@@ -2296,7 +2308,7 @@
22962308
<plugin>
22972309
<groupId>org.apache.maven.plugins</groupId>
22982310
<artifactId>maven-dependency-plugin</artifactId>
2299-
<version>3.0.2</version>
2311+
<version>3.1.1</version>
23002312
<executions>
23012313
<execution>
23022314
<id>default-cli</id>

0 commit comments

Comments
 (0)