使用 Spring webServiceTemplate 和 jaxb 库会导致 «java.lang.ref.Finalizer» 的内存泄漏

Posted

技术标签:

【中文标题】使用 Spring webServiceTemplate 和 jaxb 库会导致 «java.lang.ref.Finalizer» 的内存泄漏【英文标题】:Using Spring webServiceTemplate and jaxb library leads to memory leak with «java.lang.ref.Finalizer» 【发布时间】:2021-12-13 10:40:14 【问题描述】:

我正在我的多线程应用程序(10 个线程)中执行 webServiceTemplate.marshalSendAndReceive,它会导致 Finalizer 类发生内存泄漏。我发现在调试时哪些类在其实现中使用了 finalize。在大多数情况下,它是 UnmarshallerImpl 和 HttpsURLConnectionImpl。 Jaxb2Marshaller 为每个请求创建 UnmarshallerImpl 的新实例,并且我发现,GC 无法删除这些对象,直到低优先级线程为每个实例执行 finalize()。 显然,FinalizerThread 无法及时处理这个对象的queuq,也没有释放内存。当我模拟调用 webServiceTemplate.marshalSendAndReceive 的方法时,内存泄漏问题消失了。有没有办法解决这个问题?

这是我的 WebServise 配置:

@Configuration
public class WebServiceConfig 
    private final Props props;
    private final ApplicationProps applicationProps;

    public WebServiceConfig(@NotNull final Props props, @NotNull final ApplicationProps applicationProps) 
        this.props = props;
        this.applicationProps = applicationProps;
    

    @Bean
    public Jaxb2Marshaller marshaller() 
        Jaxb2Marshaller marshaller = new Jaxb2Marshaller();
        marshaller.setPackagesToScan(applicationProps.getPackagesToScan());
        return marshaller;
    

    @Bean
    public WebServiceTemplate webServiceTemplate(final WebServiceTemplateBuilder builder, final AllTimeouts allTimeouts) 
        return builder
                .setMarshaller(marshaller())
                .setUnmarshaller(marshaller())
                .messageSenders(new BasicAuthHttpsConnectionMessageSender(
                        props.getUsername(),
                        props.getPassword(),
                        allTimeouts.getReadTimeout()))
                .build();
    




public class BasicAuthHttpsConnectionMessageSender extends HttpsUrlConnectionMessageSender 
    private final String b64Creds;

    public BasicAuthHttpsConnectionMessageSender(final String username,
                                                 final String password,
                                                 final Integer readTimeout) 
        byte[] message;
        message = String.format("%s:%s", username, password).getBytes(StandardCharsets.UTF_8);
        b64Creds = DatatypeConverter.printBase64Binary(message);
        setReadTimeout(Duration.ofMillis(readTimeout));
    

    @Override
    protected void prepareConnection(final HttpURLConnection connection) throws IOException 
        connection.setRequestProperty(HttpHeaders.AUTHORIZATION, String.format("Basic %s", b64Creds));
        super.prepareConnection(connection);
    

这是我的依赖:

dependencies 
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-web-services'
    implementation 'org.springframework.boot:spring-boot-starter-actuator'
    implementation group: 'javax.validation', name: 'validation-api', version: '2.0.1.Final'
    implementation group: 'ch.qos.logback', name: 'logback-classic', version: '1.2.3'
    implementation group: 'net.logstash.logback', name: 'logstash-logback-encoder', version: '4.10'
    compile('javax.xml.bind:jaxb-api:2.3.0')
    compile('javax.activation:activation:1.1')
    compile('org.glassfish.jaxb:jaxb-runtime:2.3.0')
    compile 'org.hibernate.validator:hibernate-validator:6.0.13.Final'
    compile group: 'org.postgresql', name: 'postgresql', version: '42.2.11'
    compile group: 'org.springframework.vault', name: 'spring-vault-core', version: '2.1.3.RELEASE'
    compile group: 'org.springframework.ws', name: 'spring-ws-security', version: '2.2.2.RELEASE'
    compile group: 'org.springframework.ws', name: 'spring-ws-support', version: '2.2.2.RELEASE'
    compile group: 'org.springframework.boot', name: 'spring-boot-configuration-processor', version: '2.1.8.RELEASE'
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'

【问题讨论】:

【参考方案1】:

请考虑将 webServiceTemplate(...) 中对 marshaller() 的调用替换为 Spring 注入的 bean:

    @Bean
    public WebServiceTemplate webServiceTemplate(
        final WebServiceTemplateBuilder builder,
        final AllTimeouts allTimeouts,
        final Jaxb2Marshaller marshaller) 

否则,对webServiceTemplate 的调用会导致创建新的编组器,跳过您的工厂方法Jaxb2Marshaller marshaller()

【讨论】:

【参考方案2】:

虽然 finalize() 方法在 Java 9 之后被弃用了,但是 jaxb 库仍然使用 finalize。所以如果我们使用 jaxb 就会发生内存泄漏。您可以通过this 了解更多信息。

【讨论】:

以上是关于使用 Spring webServiceTemplate 和 jaxb 库会导致 «java.lang.ref.Finalizer» 的内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

转Spring的WebServiceTemplate访问WebService的方法及其本质原理

Spring WS WebServiceTemplate:访问响应的内容或自定义unmarshaller

使用 HttpComponentsMessageSender 的具有基本身份验证的 WebServiceTemplate

使用 SOAP Web 服务错误(未注册编组器。检查 WebServiceTemplate 的配置)

Java笔记-解决WebServiceTemplate中No subject alternative names matching IP address xxx

Spring-WS为SOAP版本1.2设置SOAPAction头