[spring-projects/spring-boot]当 GC 压力较大时,嵌套 jar 中的类可能会抛出 ClassNotFoundException

2024-06-26 644 views
9
概述

一个非常简单的基于 Spring Boot 的应用程序在第一次 HTTP 请求时锁定,并java.lang.NoClassDefFoundError记录在日志中。终止应用程序也会导致java.lang.NoClassDefFoundError日志中出现此信息。

下面的矩阵概述了版本和执行环境中发生锁定的情况。

Spring Boot Linux 视窗 OpenShift
2.5.14 作品 作品 作品
2.6.8 作品 作品 作品
2.6.9+ 作品 作品
2.7.0 作品 作品 作品
2.7.1+ 作品 作品
正常执行

正常执行的样子如下。

> oc run image-test --rm -it --image=registry/angular-starter:1.0.0-SNAPSHOT --image-pull-policy="Always" --command -- /bin/sh
If you don't see a command prompt, try pressing enter.
sh-4.4$ java -jar /app/lib/starter-angular-app-1.0.0-SNAPSHOT-exec.jar &
[1] 7
  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::                (v2.6.8)

2022-07-24 17:11:13.850  INFO 7 --- [           main] com.baml.starter.angular.web.WebApp      : Starting WebApp using Java 11.0.15 on image-test with PID 7 (/app/lib/starter-angular-app-1.0.0-SNAPSHOT-exec.jar started by ? in /home/app)
2022-07-24 17:11:13.853  INFO 7 --- [           main] com.baml.starter.angular.web.WebApp      : No active profile set, falling back to 1 default profile: "default"
2022-07-24 17:11:19.854  INFO 7 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080
2022-07-24 17:11:19.863  INFO 7 --- [           main] com.baml.starter.angular.web.WebApp      : Started WebApp in 7.61 seconds (JVM running for 9.215)

sh-4.4$ curl -v http://localhost:8080/api/status
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /api/status HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.61.1
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Type: application/hal+json
< Content-Length: 86
< 
* Connection #0 to host localhost left intact
{"_links":{"self":{"href":"/api/status"}},"apiVersion":"1.0.0-SNAPSHOT","status":"UP"}
sh-4.4$ kill %1
[1]+  Done(143)               java -jar /app/lib/starter-angular-app-1.0.0-SNAPSHOT-exec.jar
执行受阻

卡住的请求如下所示。我不得不按下^C来中止curl运行。

> oc run image-test --rm -it --image=registry/angular-starter:1.0.0-SNAPSHOT --image-pull-policy="Always" --command -- /bin/sh
If you don't see a command prompt, try pressing enter.
sh-4.4$ java -jar /app/lib/starter-angular-app-1.0.0-SNAPSHOT-exec.jar &
[1] 7
  .   ____          _            __ _ _
/\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot ::                (v2.6.9)

2022-07-24 16:53:02.884  INFO 7 --- [           main] com.baml.starter.angular.web.WebApp      : Starting WebApp using Java 11.0.15 on image-test with PID 7 (/app/lib/starter-angular-app-1.0.0-SNAPSHOT-exec.jar started by ? in /home/app)
2022-07-24 16:53:02.887  INFO 7 --- [           main] com.baml.starter.angular.web.WebApp      : No active profile set, falling back to 1 default profile: "default"
2022-07-24 16:53:09.586  INFO 7 --- [           main] o.s.b.web.embedded.netty.NettyWebServer  : Netty started on port 8080
2022-07-24 16:53:09.679  INFO 7 --- [           main] com.baml.starter.angular.web.WebApp      : Started WebApp in 8.394 seconds (JVM running for 9.951)

sh-4.4$ curl -v http://localhost:8080/api/status
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 8080 (#0)
> GET /api/status HTTP/1.1
> Host: localhost:8080
> User-Agent: curl/7.61.1
> Accept: */*
> 
Exception in thread "reactor-http-epoll-2" java.lang.NoClassDefFoundError: io/netty/buffer/PoolArena$1
        at io.netty.buffer.PoolArena.freeChunk(PoolArena.java:248)
        at io.netty.buffer.PoolThreadCache$MemoryRegionCache.freeEntry(PoolThreadCache.java:432)
        at io.netty.buffer.PoolThreadCache$MemoryRegionCache.free(PoolThreadCache.java:396)
        at io.netty.buffer.PoolThreadCache$MemoryRegionCache.free(PoolThreadCache.java:388)
        at io.netty.buffer.PoolThreadCache.free(PoolThreadCache.java:254)
        at io.netty.buffer.PoolThreadCache.free(PoolThreadCache.java:245)
        at io.netty.buffer.PoolThreadCache.free(PoolThreadCache.java:218)
        at io.netty.buffer.PooledByteBufAllocator$PoolThreadLocalCache.onRemoval(PooledByteBufAllocator.java:542)
        at io.netty.buffer.PooledByteBufAllocator$PoolThreadLocalCache.onRemoval(PooledByteBufAllocator.java:503)
        at io.netty.util.concurrent.FastThreadLocal.remove(FastThreadLocal.java:257)
        at io.netty.util.concurrent.FastThreadLocal.removeAll(FastThreadLocal.java:67)
        at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:1050)
        at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
        at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
        at java.base/java.lang.Thread.run(Thread.java:829)
Caused by: java.lang.ClassNotFoundException: io.netty.buffer.PoolArena$1
        at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:476)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:589)
        at org.springframework.boot.loader.LaunchedURLClassLoader.loadClass(LaunchedURLClassLoader.java:151)
        at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:522)
        ... 15 more
^C
sh-4.4$ kill %1
Exception in thread "SpringApplicationShutdownHook" java.lang.NoClassDefFoundError: ch/qos/logback/classic/spi/ThrowableProxy
        at ch.qos.logback.classic.spi.LoggingEvent.<init>(LoggingEvent.java:119)
        at ch.qos.logback.classic.Logger.buildLoggingEventAndAppend(Logger.java:419)
        at ch.qos.logback.classic.Logger.filterAndLog_0_Or3Plus(Logger.java:383)
        at ch.qos.logback.classic.Logger.log(Logger.java:765)
        at org.apache.commons.logging.LogAdapter$Slf4jLocationAwareLog.warn(LogAdapter.java:447)
        at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1070)
        at org.springframework.boot.web.reactive.context.ReactiveWebServerApplicationContext.doClose(ReactiveWebServerApplicationContext.java:147)
        at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:1021)
        at org.springframework.boot.SpringApplicationShutdownHook.closeAndWait(SpringApplicationShutdownHook.java:145)
        at java.base/java.lang.Iterable.forEach(Iterable.java:75)
        at org.springframework.boot.SpringApplicationShutdownHook.run(SpringApplicationShutdownHook.java:114)
        at java.base/java.lang.Thread.run(Thread.java:829)

[1]+  Done(143)               java -jar /app/lib/starter-angular-app-1.0.0-SNAPSHOT-exec.jar

此特定应用程序使用WebFlux来处理请求。我尝试将其转换为WebMVCjava.lang.NoClassDefFoundError 。尽管在不同的类中,但它也卡住了。

我快速比较了 2.6.8 和 2.6.9。我发现org.springframework.boot.loader.jar软件包中有一些可能与此相关的更改。我指的是这个提交。仅供参考。

回答

7

感谢您的报告。不幸的是,我认为我们需要能够重现该问题,最好是在不运行 Openshift 的情况下进行诊断。您能否提供一个完整但最小的样本?除非这是特定于 OpenShift 的问题,否则应该可以通过在本地运行相同的映像来重现该问题。

6

我会看看我能做什么。这可能需要几天时间,因为在我的工作场所源代码只能朝一个方向移动:进入。

6

从 2.6.x 升级到 2.7.2 后出现同样的问题。降级到 2.7.0 后,它又可以正常工作了。但是我没有运行 OpenShift,而是只在 Docker (20.10.17) 上运行。

5

@MrWong99 如果您能分享一个我们可以运行的样本,我们将非常感激。

5

我尝试提取关键部分,而不会泄露任何公司代码。但是,它没有以这种格式进行测试。我们的应用程序有一个 REST 接口,包括一个静态网站,并有一个小型 websocket 端点。

pom.xml

...
    <properties>
        <springboot.version>2.7.2</springboot.version>
    </properties>

    <dependencyManagement>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-dependencies</artifactId>
                <version>${springboot.version}</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-logging</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-vault-config</artifactId>
            <version>3.1.1</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-actuator</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${springboot.version}</version>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
                <configuration>
                    <mainClass>my.main.Application</mainClass>
                    <layout>ZIP</layout>
                </configuration>
            </plugin>
        </plugins>
    </build>

Dockerfile

# Note: we don't use this image but a similar one
FROM adoptopenjdk:11.0.11_9-jdk-hotspot

ADD my-distribution-containing-app.tar.gz .
EXPOSE 8080

CMD [ "java", "-Xmx500m", "-Dloader.path=//custom/plugins/", "-jar", "app.jar", "--spring.config.import=application.yml" ]

application.yml 没有什么特殊之处,除了一些针对 spring.cloud.vault 和活动配置文件的设置。

6

@MrWong99 我们需要实际样本。我没有在您分享的代码片段中看到任何可能导致该行为的内容。

从 start.spring.io 创建的示例应用程序开始,添加相关部分,直到重现问题,这应该会为我们提供所需的信息。谢谢!

9

我基本上是这样做的:https://github.com/ayashkov/spring-boot-31853。不过有几件事:

  1. 我无法让此特定代码失败。仍在尝试修改它以触发失败。
  2. 整个事情似乎对最终 JAR 的组成极为敏感。例如,当我尝试对失败的代码进行某些更改(例如合并或重命名模块、删除 HAL 处理)时,它不再失败。

我会继续努力几天,希望取得更好的结果。

4

以下是可能与此相关的附加数据。我能够通过删除<layout>ZIP</layout>以下spring-boot-maven-plugin插件配置来阻止原始代码的破坏:

<configuration>
    <mainClass>com.baml.starter.angular.web.WebApp</mainClass>
    <layout>ZIP</layout>
    <classifier>exec</classifier>
</configuration>

换句话说,下面的方法可以正常工作:

<configuration>
    <mainClass>com.baml.starter.angular.web.WebApp</mainClass>
    <classifier>exec</classifier>
</configuration>
2

今天深入研究了一下,#29356 的修复导致了回归。这NoClassDefFoundError实际上是由JarFile.ensureOpen()抛出引起的IllegalStateException

看起来java.util.zip.ZipFile$CleanableResource$FinalizableResource实际上是在关闭 Jar,这使得调试起来相当困难。我想知道是否真的close()在所有上调用nestedJars是正确的。在我看来,JarFile在嵌套的 Jar 完成之前,外部的 Jar 可能会被 GC。

0

@ayashkov 非常感谢您将示例应用程序放在一起,它真的很有帮助!

0

我已经推送了一些可以修复此问题的补丁,但我并不完全有信心。如果它不起作用,我们可能需要恢复 #29356 的修复。

我们计划在周四发布版本,因此对于关注此问题的任何人来说,一旦CI 版本 1110完成,请尝试最新的 SNAPSHOT 以查看是否仍然遇到同样的问题。

2

不幸的是,我们现在再次面临 Windows 上的文件锁定问题。我将恢复 #29356 的修复