Skip to content

Java 17 运行 Apache Spark 的 "sun.nio.ch.DirectBuffer" 访问错误解决方案 ​

问题描述 ​

在 Java 17 环境中运行 Apache Spark 3.3.0 及以上版本时,可能会遇到以下错误:

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

这个错误通常发生在尝试创建 SparkSession 或执行 Spark 操作时,原因是 Java 17 的模块系统加强了访问控制,而 Spark 使用了部分内部 API(如 sun.nio.ch.DirectBuffer)。

解决方案 ​

以下是针对不同环境和构建工具的解决方案:

通用 JVM 参数方案 ​

最直接的解决方案是添加必要的 JVM 参数来开放内部模块的访问权限:

bash
--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
--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED

注意

虽然只需 --add-opens=java.base/sun.nio.ch=ALL-UNNAMED 就能解决直接错误,但建议添加全套参数以确保 Spark 其他功能正常工作。

Maven 项目配置 ​

运行应用程序 ​

在 .mvn/jvm.config 文件中添加:

--add-opens=java.base/sun.nio.ch=ALL-UNNAMED

运行测试 ​

在 pom.xml 中配置 Maven Surefire 插件:

xml
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-surefire-plugin</artifactId>
    <configuration>
        <argLine>
            --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
            --add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED
        </argLine>
    </configuration>
</plugin>

Gradle 项目配置 ​

运行应用程序 ​

在 build.gradle 中:

groovy
application {
    applicationDefaultJvmArgs = [
        "--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",
        "--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED"
    ]
}

运行测试 ​

在 build.gradle.kts 中:

kotlin
tasks.test {
    val sparkJava17CompatibleJvmArgs = listOf(
        "--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",
        "--add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED"
    )
    jvmArgs = sparkJava17CompatibleJvmArgs
}

IDE 配置 ​

IntelliJ IDEA ​

  1. 打开运行/调试配置
  2. 在 "VM options" 字段中添加:
    --add-opens=java.base/sun.nio.ch=ALL-UNNAMED
完整 VM 选项(推荐)
--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 --add-opens=java.security.jgss/sun.security.krb5=ALL-UNNAMED

Visual Studio Code ​

在 .vscode/launch.json 中添加:

json
{
  "version": "0.2.0",
  "configurations": [
    {
      "type": "java",
      "name": "Main",
      "request": "launch",
      "mainClass": "com.spark.Main",
      "vmArgs": "--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"
    }
  ]
}

环境变量方案 ​

Docker 环境 ​

在 Dockerfile 中添加:

dockerfile
ENV JDK_JAVA_OPTIONS="--add-opens=java.base/sun.nio.ch=ALL-UNNAMED"

命令行环境 ​

bash
export JAVA_OPTS='--add-exports java.base/sun.nio.ch=ALL-UNNAMED'

SBT 项目配置 ​

在 build.sbt 中添加:

scala
Test / javaOptions ++= Seq(
  "base/java.lang", "base/java.lang.invoke", "base/java.lang.reflect", "base/java.io", "base/java.net", "base/java.nio",
  "base/java.util", "base/java.util.concurrent", "base/java.util.concurrent.atomic",
  "base/sun.nio.ch", "base/sun.nio.cs", "base/sun.security.action",
  "base/sun.util.calendar", "security.jgss/sun.security.krb5",
).map("--add-opens=java." + _ + "=ALL-UNNAMED")

或者在项目根目录创建 .jvmopts 文件:

--add-exports java.base/sun.nio.ch=ALL-UNNAMED

替代方案 ​

如果无法添加 JVM 参数(如在某些云端环境中),可以考虑以下替代方案:

  1. 降级到 Java 11:这是最稳定的解决方案,因为 Spark 对 Java 11 的支持更加成熟
  2. 升级 Spark 版本:Spark 3.3.2+ 版本对 Java 17 的兼容性有所改善

技术背景 ​

这个问题的根本原因是 Java 9 引入的模块系统(JPMS)。在 Java 17 中,对内部 API 的访问控制更加严格,而 Spark 使用了 sun.nio.ch.DirectBuffer 等内部类来进行高性能 I/O 操作。

--add-opens 参数告诉 JVM 允许特定模块向未命名模块(如 Spark)开放指定的包,从而绕过模块系统的访问限制。

兼容性说明

虽然 Spark 官方文档声明支持 Java 17,但在实际使用中可能需要额外的配置。建议在生产环境中使用 Java 11,或等待 Spark 后续版本对 Java 17 更完善的支持。

总结 ​

Java 17 运行 Apache Spark 时的 "sun.nio.ch.DirectBuffer" 访问错误可以通过添加适当的 JVM 参数来解决。根据您的开发环境和构建工具选择合适的配置方式,确保 Spark 能够正常访问所需的内部 API。

对于生产环境,建议评估使用 Java 11 的可行性,或者密切关注 Spark 社区对 Java 17 支持的后续改进。