在 Java 17 上使用 Spark 3.3.0 运行单元测试失败并出现 IllegalAccessError:类 StorageUtils 无法访问类 sun.nio.ch.DirectBuffer

问题描述 投票:0回答:3

根据release notes,特别是Build and Run Spark on Java 17 (SPARK-33772),Spark 现在支持在 Java 17 上运行。

但是,在运行使用 Spark (3.3.0) 的单元测试时,将 Java 17 (Temurin-17.0.3+7) 与 Maven (3.8.6) 和 maven-surefire-plugin (3.0.0-M7) 结合使用失败:

java.lang.IllegalAccessError: class org.apache.spark.storage.StorageUtils$ (in unnamed module @0x1e7ba8d9) cannot access class sun.nio.ch.DirectBuffer (in module java.base) because module java.base does not export sun.nio.ch to unnamed module @0x1e7ba8d9

栈是:

java.lang.IllegalAccessError: class org.apache.spark.storage.StorageUtils$ (in unnamed module @0x1e7ba8d9) cannot access class sun.nio.ch.DirectBuffer (in module java.base) because module java.base does not export sun.nio.ch to unnamed module @0x1e7ba8d9
  at org.apache.spark.storage.StorageUtils$.<init>(StorageUtils.scala:213)
  at org.apache.spark.storage.StorageUtils$.<clinit>(StorageUtils.scala)
  at org.apache.spark.storage.BlockManagerMasterEndpoint.<init>(BlockManagerMasterEndpoint.scala:114)
  at org.apache.spark.SparkEnv$.$anonfun$create$9(SparkEnv.scala:353)
  at org.apache.spark.SparkEnv$.registerOrLookupEndpoint$1(SparkEnv.scala:290)
  at org.apache.spark.SparkEnv$.create(SparkEnv.scala:339)
  at org.apache.spark.SparkEnv$.createDriverEnv(SparkEnv.scala:194)
  at org.apache.spark.SparkContext.createSparkEnv(SparkContext.scala:279)
  at org.apache.spark.SparkContext.<init>(SparkContext.scala:464)
  at org.apache.spark.SparkContext$.getOrCreate(SparkContext.scala:2704)
  at org.apache.spark.sql.SparkSession$Builder.$anonfun$getOrCreate$2(SparkSession.scala:953)
  at scala.Option.getOrElse(Option.scala:189)
  at org.apache.spark.sql.SparkSession$Builder.getOrCreate(SparkSession.scala:947)
  [...]

问题 Java 17 解决方案的 Spark - java.lang.NoClassDefFoundError:无法初始化类 org.apache.spark.storage.StorageUtils 仅在 2 个月前被问到,但这早于 Spark 3.3.0 版本,因此早于对 Java 17 的官方支持。

为什么我不能用 Java 17 运行我的 Spark 3.3.0 测试,我们该如何解决它?

java apache-spark java-17
3个回答
7
投票

即使Spark现在支持Java 17,它仍然引用了JDK内部类

sun.nio.ch.DirectBuffer

  // In Java 8, the type of DirectBuffer.cleaner() was sun.misc.Cleaner, and it was possible
  // to access the method sun.misc.Cleaner.clean() to invoke it. The type changed to
  // jdk.internal.ref.Cleaner in later JDKs, and the .clean() method is not accessible even with
  // reflection. However sun.misc.Unsafe added a invokeCleaner() method in JDK 9+ and this is
  // still accessible with reflection.
  private val bufferCleaner: DirectBuffer => Unit = [...]

在Java模块系统下,这个类的访问是有限制的。 Java 9 迁移指南 说:

如果您必须使用默认情况下不可访问的内部 API,则可以使用 --add-exports 命令行选项打破封装。

我们需要打开对我们模块的访问。为此,我们将此配置添加到插件中:

<plugin>
  <groupId>org.apache.maven.plugins</groupId>
  <artifactId>maven-surefire-plugin</artifactId>
  <version>3.0.0-M7</version>
  <configuration>
    <argLine>--add-exports java.base/sun.nio.ch=ALL-UNNAMED</argLine>
  </configuration>
</plugin>

基于与一位 Spark 开发人员的讨论,Spark 添加了以下内容以执行其所有内部单元测试。

这些选项用于传递所有 Spark UT,但也许你不需要所有。

--add-opens=java.base/java.lang=ALL-UNNAMED
--add-opens=java.base/java.lang.invoke=ALL-UNNAMED
--add-opens=java.base/java.lang.reflect=ALL-UNNAMED
--add-opens=java.base/java.io=ALL-UNNAMED
--add-opens=java.base/java.net=ALL-UNNAMED
--add-opens=java.base/java.nio=ALL-UNNAMED
--add-opens=java.base/java.util=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent=ALL-UNNAMED
--add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED
--add-opens=java.base/sun.nio.ch=ALL-UNNAMED
--add-opens=java.base/sun.nio.cs=ALL-UNNAMED
--add-opens=java.base/sun.security.action=ALL-UNNAMED
--add-opens=java.base/sun.util.calendar=ALL-UNNAMED

也有人评论:

但是,使用spark-shell、spark-sql、spark-submit时这些Options不需要显式添加


3
投票

基于上面的讨论,我正在使用:

%SPARK_HOME%\bin\spark-submit.cmd --driver-java-options "--add-exports java.base/sun.nio.ch=ALL-UNNAMED" spark_ml_heart.py

使用单个

--add-exports
在 Java 17 上的 Spark 3.2.1 上运行 Python 脚本。

您可能需要完整版

--add-exports

%SPARK_HOME%\bin\spark-submit.cmd --driver-java-options "--add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.invoke=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED --add-opens=java.base/java.io=ALL-UNNAMED --add-opens=java.base/java.net=ALL-UNNAMED --add-opens=java.base/java.nio=ALL-UNNAMED --add-opens=java.base/java.util=ALL-UNNAMED --add-opens=java.base/java.util.concurrent=ALL-UNNAMED --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED --add-opens=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/sun.nio.cs=ALL-UNNAMED --add-opens=java.base/sun.security.action=ALL-UNNAMED --add-opens=java.base/sun.util.calendar=ALL-UNNAMED" spark_ml_heart.py

1
投票

修复其中一些错误后,我在使用 KryoSerializer 时遇到错误:

java.lang.IllegalArgumentException: Unable to create serializer "com.esotericsoftware.kryo.serializers.FieldSerializer" for class: java.nio.HeapByteBuffer

我通过将@Greg Kopff 提到的一些 VM 参数添加到我的 pom.xml(我正在使用 maven)来解决这个问题:

    <plugin>
        <groupId>org.scalatest</groupId>
        <artifactId>scalatest-maven-plugin</artifactId>
        <version>${scalatest-maven-plugin.version}</version>
        <configuration>
            <argLine>
                --add-opens=java.base/java.lang.invoke=ALL-UNNAMED
                --add-opens=java.base/java.nio=ALL-UNNAMED
                --add-opens=java.base/java.util=ALL-UNNAMED
                --add-opens=java.base/sun.nio.ch=ALL-UNNAMED
            </argLine>
        </configuration>
    </plugin>
© www.soinside.com 2019 - 2024. All rights reserved.