在这个博客文章系列的第一篇文章中,我们已经习惯了利用SDKMAN安装Maven。正如甲骨文官方的GraalVM Docker镜像是基于 oraclelinux:7-slim
,我们需要先安装 unzip
和 zip
。为了正常工作,SDKMAN需要两者:
FROM oracle/graalvm-ce:20.0.0-java11
For SDKMAN to work we need unzip & zip
RUN yum install -y unzip zip
RUN \
Install SDKMAN
curl -s “https://get.sdkman.io” | bash; \
source “$HOME/.sdkman/bin/sdkman-init.sh”; \
Install Maven
sdk install maven; \
Install GraalVM Native Image
gu install native-image;
RUN source “$HOME/.sdkman/bin/sdkman-init.sh” && mvn --version
RUN native-image --version
Always use source sdkman-init.sh before any command, so that we will be able to use ‘mvn’ command
ENTRYPOINT bash -c “source $HOME/.sdkman/bin/sdkman-init.sh && $0”
我们不应该忘记为Docker映像的用户启用 mvn
命令。因此,我们创建了一个稍微有趣的入口点,它总是在命令前面加上 source$HOME/.sdkman/bin/sdkman-init.sh
。定义了Dockerfile之后,我们应该用以下内容构建我们的镜像:
docker build . --tag=graalvm-ce:20.0.0-java11-mvn-native-image
构建完成后,我们可以在Docker容器中启动GraalVM本机映像编译。但是请稍候,下面的命令继承了第二个Docker卷定义 --volume“$HOME”/.m2:/root/.m2
。为什么?因为我真的想避免每次启动Docker容器时一遍又一遍地下载所有SpringMaven依赖项。通过这种装载,我们只需使用机器上缓存的Maven存储库:
docker run -it --rm \
–volume $(pwd):/build \
–workdir /build \
–volume “$HOME”/.m2:/root/.m2 \
graalvm-ce:20.0.0-java11-mvn-native-image ./compile.sh
第一个列 --volume$(pwd):/build
只是将Spring Boot应用程序的源代码(包括用于GraalVM本机映像编译的 .compile.sh
脚本)装载到Docker容器中。运行这个Docker构建,经过几分钟的繁重编译后,生成的 spring-boot-graal
本机应用程序应该已经准备好了。
防止java.lang.OutOfMemoryError错误
当我开始尝试Spring Boot应用程序的GraalVM本机映像编译时,我经常体验到 docker run
命令似乎需要很长时间才能完成。最后,一个 java.lang.OutOfMemoryError
错误被抛出到日志中,如下所示:
14:06:34.609 [ForkJoinPool-2-worker-3] DEBUG io.netty.handler.codec.compression.ZlibCodecFactory - -Dio.netty.noJdkZlibEncoder: false
Exception in thread “native-image pid watcher”
Exception: java.lang.OutOfMemoryError thrown from the UncaughtExceptionHandler in thread “native-image pid watcher”
在这种情况下,Docker引擎很可能无法使用足够的内存。在Mac的Docker安装中,默认值只有2.00GB。正如stackoverflow q&a的评论所述,您必须给Docker更多的内存,因为GraalVM本机映像编译过程实际上是RAM密集型的。分配Docker引擎大约9到12GB的RAM,我可以在Docker容器中进行编译:
如果一切正常,您应该在 /target/native-image
目录中找到本机编译的Spring Boot应用程序 spring-boot-graal 。因此,要运行我们的应用程序,只需使用 ./target/native-image/spring-boot-graal
:
$ ./spring-boot-graal
zsh: exec format error: ./spring-boot-graal
噢!原来这不管用!为什么?我们真的需要记住,我们是从Java应用程序编译本机可执行文件! 所以他们现在完全依赖平台了! 我们的Docker容器的基本映像将与我们的主机操作系统大不相同。我想这对所有的Java人来说都是新鲜事!从一开始我们就被告知Java是独立于平台的,这要归功于它的虚拟机。这个问题只有在这一点上我们才真正明白,我们开始在Docker容器中编译我们的应用程序。
这个问题的解决方案很简单