How to prevent memory leaks caused by threads in Tomcat9 and Netty when using Lettuce with Spring?

huangapple go评论53阅读模式
英文:

How to prevent memory leaks caused by threads in Tomcat9 and Netty when using Lettuce with Spring?

问题

I am using io.lettuce.core.api.StatefulRedisConnection from GenericObjectPoolConfig to connect Paas Redis.

Configure:

@Bean
public GenericObjectPoolConfig redisPoolConfig() {
    GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
    poolConfig.setMaxTotal(redisProperties.getPool().getMaxTotal());
    poolConfig.setMaxIdle(redisProperties.getPool().getMaxIdle());
    poolConfig.setMinIdle(redisProperties.getPool().getMinIdle());
    poolConfig.setMaxWaitMillis(redisProperties.getPool().getMaxWaitMillis());
    poolConfig.setMinEvictableIdleTimeMillis(redisProperties.getPool().getMinEvictableIdleTimeMillis());
    return poolConfig;
}

@Bean(destroyMethod = "close")
public GenericObjectPool<StatefulRedisConnection<String, Object>> redisObjectPool(RedisClient redisClient, GenericObjectPoolConfig genericObjectPoolConfig) {
    RedisCodecForObject redisCodecForObject = new RedisCodecForObject(Object.class, redisProperties.getKeyPrefix());
    ObjectMapper om = new ObjectMapper();
    om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
    TypeResolverBuilder<?> typeResolver = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL) {
        {
            init(JsonTypeInfo.Id.CLASS, null);
            inclusion(JsonTypeInfo.As.WRAPPER_ARRAY);
        }
    };
    om.setDefaultTyping(typeResolver);
    redisCodecForObject.setObjectMapper(om);
    return ConnectionPoolSupport.createGenericObjectPool(() -> redisClient.connect(redisCodecForObject), genericObjectPoolConfig);
}

@Bean(destroyMethod = "close")
public StatefulRedisConnection<String, Object> objectRedisConnection(GenericObjectPool<StatefulRedisConnection<String, Object>> redisObjectPool) throws Exception {
    StatefulRedisConnection<String, Object> connection = redisObjectPool.borrowObject();
    return connection;
}

SEVERE [main] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class [ch.qos.logback.ext.spring.web.LogbackConfigListener] java.lang.IllegalStateException: Web app root system property already set to different value: 'webapp.root' = [/app/tomcat/webapps/ROOT/] instead of [/app/tomcat/webapps/demo/] - Choose unique values for the 'webAppRootKey' context-param in your web.xml files!

WARNING [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-app] appears to have started a thread named [lettuce-timer-3-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread: java.lang.Thread.sleep(Native Method) io.netty.util.HashedWheelTimer$Worker.waitForNextTick(HashedWheelTimer.java:569) io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:465) io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30) java.lang.Thread.run(Thread.java:750) 02-Jun-2023 19:35:11.842 WARNING [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-app] appears to have started a thread named [parallel-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread: sun.misc.Unsafe.park(Native Method) java.util.concurrent.locks.LockSupport.park(LockSupport.java:175) java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039) java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081) java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809) java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074) java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134) java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) java.lang.Thread.run(Thread.java:750) 02-Jun-2023 19:35:11.842 SEVERE [main] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [my-app] created a ThreadLocal with key of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal@381d724e]) and a value of type [io.netty.util.internal.InternalThreadLocalMap] (value [io.netty.util.internal.InternalThreadLocalMap@4736bb9f]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

My local development environment started Tomcat without the following warning. However, while starting Tomcat by Jenkins, I am getting the below warning logs (currently, this warning doesn't affect system access and usage):

Here are some configurations in pom.xml:

<dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
    <version>1.5.2.RELEASE</version>
</dependency>

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-pool2</artifactId>
    <version>2.9.0</version>
</dependency>

<dependency>
    <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.1.0.RELEASE</version>
</dependency>

How can I close these threads to prevent memory leaks?

I tried some methods to prevent, such as io.netty exception when implementing Spring Session with Redis. But it does not work for me.

英文:

I am using io.lettuce.core.api.StatefulRedisConnection from GenericObjectPoolConfig to connect Paas Redis.

Configure:

    @Bean
public GenericObjectPoolConfig redisPoolConfig() {
GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig();
poolConfig.setMaxTotal(redisProperties.getPool().getMaxTotal());
poolConfig.setMaxIdle(redisProperties.getPool().getMaxIdle());
poolConfig.setMinIdle(redisProperties.getPool().getMinIdle());
poolConfig.setMaxWaitMillis(redisProperties.getPool().getMaxWaitMillis());
poolConfig.setMinEvictableIdleTimeMillis(redisProperties.getPool().getMinEvictableIdleTimeMillis());
return poolConfig;
}
@Bean(destroyMethod = &quot;close&quot;)
public GenericObjectPool&lt;StatefulRedisConnection&lt;String, Object&gt;&gt; redisObjectPool(RedisClient redisClient, GenericObjectPoolConfig genericObjectPoolConfig) {
RedisCodecForObject redisCodecForObject = new RedisCodecForObject(Object.class, redisProperties.getKeyPrefix());
ObjectMapper om = new ObjectMapper();
om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
TypeResolverBuilder &lt;?&gt; typeResolver = new ObjectMapper.DefaultTypeResolverBuilder(ObjectMapper.DefaultTyping.NON_FINAL) {
{
init(JsonTypeInfo.Id.CLASS, null);
inclusion(JsonTypeInfo.As.WRAPPER_ARRAY);
}
};
om.setDefaultTyping(typeResolver);
redisCodecForObject.setObjectMapper(om);
return ConnectionPoolSupport.createGenericObjectPool(() -&gt; redisClient.connect(redisCodecForObject), genericObjectPoolConfig);
}
@Bean(destroyMethod = &quot;close&quot;)
public StatefulRedisConnection&lt;String, Object&gt; objectRedisConnection(GenericObjectPool&lt;StatefulRedisConnection&lt;String, Object&gt;&gt; redisObjectPool) throws Exception {
StatefulRedisConnection&lt;String, Object&gt; connection = redisObjectPool.borrowObject();
return connection;
}

> SEVERE [main] org.apache.catalina.core.StandardContext.listenerStart
> Exception sending context initialized event to listener instance of
> class [ch.qos.logback.ext.spring.web.LogbackConfigListener]
> java.lang.IllegalStateException: Web app root system property already set to different value:
> 'webapp.root' = [/app/tomcat/webapps/ROOT/] instead of [/app/tomcat/webapps/demo/] -
> Choose unique values for the 'webAppRootKey' context-param in your web.xml files!
>
>
> WARNING [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-app] appears to have started a thread named [lettuce-timer-3-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
> java.lang.Thread.sleep(Native Method)
> io.netty.util.HashedWheelTimer$Worker.waitForNextTick(HashedWheelTimer.java:569)
> io.netty.util.HashedWheelTimer$Worker.run(HashedWheelTimer.java:465)
> io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
> java.lang.Thread.run(Thread.java:750)
> 02-Jun-2023 19:35:11.842 WARNING [main] org.apache.catalina.loader.WebappClassLoaderBase.clearReferencesThreads The web application [my-app] appears to have started a thread named [parallel-1] but has failed to stop it. This is very likely to create a memory leak. Stack trace of thread:
> sun.misc.Unsafe.park(Native Method)
> java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
> java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(AbstractQueuedSynchronizer.java:2039)
> java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1081)
> java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
> java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
> java.lang.Thread.run(Thread.java:750)
> 02-Jun-2023 19:35:11.842 SEVERE [main] org.apache.catalina.loader.WebappClassLoaderBase.checkThreadLocalMapForLeaks The web application [my-app] created a ThreadLocal with key of type [java.lang.ThreadLocal] (value [java.lang.ThreadLocal@381d724e]) and a value of type [io.netty.util.internal.InternalThreadLocalMap] (value [io.netty.util.internal.InternalThreadLocalMap@4736bb9f]) but failed to remove it when the web application was stopped. Threads are going to be renewed over time to try and avoid a probable memory leak.

My local development environment started Tomcat without the following warning. However, While start tomcat by jenkins, I am getting below warning logs(Currently, this warning doesn't affect system access and usage):

Here are some configuration in pom.xml :

    &lt;dependency&gt;
&lt;groupId&gt;org.springframework.data&lt;/groupId&gt;
&lt;artifactId&gt;spring-data-redis&lt;/artifactId&gt;
&lt;version&gt;1.5.2.RELEASE&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;org.apache.commons&lt;/groupId&gt;
&lt;artifactId&gt;commons-pool2&lt;/artifactId&gt;
&lt;version&gt;2.9.0&lt;/version&gt;
&lt;/dependency&gt;
&lt;dependency&gt;
&lt;groupId&gt;io.lettuce&lt;/groupId&gt;
&lt;artifactId&gt;lettuce-core&lt;/artifactId&gt;
&lt;version&gt;6.1.0.RELEASE&lt;/version&gt;
&lt;/dependency&gt;

How can i close these threads to prevent memory leak.

i tried some methods to prevent, such as io.netty exception when implementing Spring Session with Redis. but it does not work for me.

答案1

得分: 0

reference1 - logback

reference2 - lettuce client

cause I: 由于在Tomcat启动期间容器未能加载LogbackConfigListener,导致触发了与关闭Redis组件和连接以及Tomcat容器内其他组件(destroyMethod)相关的方法的执行。

error info contains two parts:
【a thread named [lettuce-timer-3-1] but has failed to stop it】
【a thread named [parallel-1] but has failed to stop it】

cause II: 目前情况:Demo Tomcat配置了默认访问路径为""指向"demo-portal"。server.xml文件添加了以下配置。默认情况下,Logback将Web应用程序的根路径设置为系统属性'webapp.root',该属性设置为"/app/tomcat/webapps/ROOT/"。这允许应用程序在Logback配置文件中引用它。但是,这在加载LogbackConfigListener时会导致错误。

<Host name="localhost" appBase="webapps" unpackWARs="true" autoDeploy="true">
    <Context path="" docBase="demo-portal" reloadable="true"/>
</Host>

/**

  • 设置Web应用程序根目录的系统属性。
  • 系统属性的键可以在{@code web.xml}中的'webAppRootKey'上下文参数中定义。默认值为'webapp.root'。
  • 可用于支持带有{@code System.getProperty}值的工具,如在日志文件位置中使用的log4j的'${key}'语法。

  • @param servletContext Web应用程序的Servlet上下文
  • @throws IllegalStateException 如果系统属性已设置,或者WAR文件未展开
  • @see #WEB_APP_ROOT_KEY_PARAM
  • @see #DEFAULT_WEB_APP_ROOT_KEY
  • @see WebAppRootListener
  • @see Log4jWebConfigurer
    */
    public static void setWebAppRootSystemProperty(ServletContext servletContext) throws IllegalStateException {
    Assert.notNull(servletContext, "ServletContext must not be null");
    String root = servletContext.getRealPath("/");
    if (root == null) {
    throw new IllegalStateException(
    "Cannot set web app root system property when WAR file is not expanded");
    }
    String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM);
    String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY);
    String oldValue = System.getProperty(key);
    if (oldValue != null && !StringUtils.pathEquals(oldValue, root)) {
    throw new IllegalStateException(
    "Web app root system property already set to different value: '" +
    key + "' = [" + oldValue + "] instead of [" + root + "] - " +
    "Choose unique values for the 'webAppRootKey' context-param in your web.xml files!");
    }
    System.setProperty(key, root);
    servletContext.log("Set web app root system property: '" + key + "' = [" + root + "]");
    }

solutions:
step1: 更改Redis配置:在应用程序关闭时为ClientResources和RedisClient bean添加destroyMethod方法。

@Bean(destroyMethod = "shutdown")
public ClientResources clientResources() {
    return DefaultClientResources.builder()
            //重新连接间隔:5秒
            .reconnectDelay(Delay.constant(Duration.ofSeconds(5)))
            .build();
}

@Bean(destroyMethod = "shutdown")
public RedisClient redisClient(RedisURI redisURI, ClientResources clientResources) {
    ClientOptions clientOptions = ClientOptions.builder()
            //错误自动重试
            .autoReconnect(true)
            .pingBeforeActivateConnection(true)
            .build();

    RedisClient redisClient = RedisClient.create(clientResources, redisURI);
    redisClient.setOptions(clientOptions);

    return redisClient;
}

step2: 修复加载LogbackConfigListener失败的问题:Logback不再将Web应用程序根路径设置为系统属性。

<context-param>
        <param-name>logbackExposeWebAppRoot</param-name>
        <param-value>false</param-value>
</context-param>
英文:

reference1 - logback

reference2 - lettuce client

cause I:Due to the failure of the container to load the LogbackConfigListener during Tomcat startup, it triggers the execution of methods related to shutting down the Redis component and connections, as well as other components within the Tomcat container (destroyMethod).

error info contains two parts:
【a thread named [lettuce-timer-3-1] but has failed to stop it】

【a thread named [parallel-1] but has failed to stop it】

cause II:AS-IS : The Demo Tomcat is configured to have the default access path ""
pointing to "demo-portal". The server.xml file has the following configuration added. By default, Logback sets the web application root path as the system property 'webapp.root', which is set to "/app/tomcat/webapps/ROOT/". This allows the application to reference it in the Logback configuration file. However, this causes an error when loading LogbackConfigListener.

> <Host name="localhost" appBase="webapps" unpackWARs="true"
> autoDeploy="true">
> <Context path="" docBase="demo-portal" reloadable="true"/> </Host>

/**
* Set a system property to the web application root directory.
* The key of the system property can be defined with the &quot;webAppRootKey&quot;
* context-param in {@code web.xml}. Default is &quot;webapp.root&quot;.
* &lt;p&gt;Can be used for tools that support substition with {@code System.getProperty}
* values, like log4j&#39;s &quot;${key}&quot; syntax within log file locations.
* @param servletContext the servlet context of the web application
* @throws IllegalStateException if the system property is already set,
* or if the WAR file is not expanded
* @see #WEB_APP_ROOT_KEY_PARAM
* @see #DEFAULT_WEB_APP_ROOT_KEY
* @see WebAppRootListener
* @see Log4jWebConfigurer
*/
public static void setWebAppRootSystemProperty(ServletContext servletContext) throws IllegalStateException {
Assert.notNull(servletContext, &quot;ServletContext must not be null&quot;);
String root = servletContext.getRealPath(&quot;/&quot;);
if (root == null) {
throw new IllegalStateException(
&quot;Cannot set web app root system property when WAR file is not expanded&quot;);
}
String param = servletContext.getInitParameter(WEB_APP_ROOT_KEY_PARAM);
String key = (param != null ? param : DEFAULT_WEB_APP_ROOT_KEY);
String oldValue = System.getProperty(key);
if (oldValue != null &amp;&amp; !StringUtils.pathEquals(oldValue, root)) {
throw new IllegalStateException(
&quot;Web app root system property already set to different value: &#39;&quot; +
key + &quot;&#39; = [&quot; + oldValue + &quot;] instead of [&quot; + root + &quot;] - &quot; +
&quot;Choose unique values for the &#39;webAppRootKey&#39; context-param in your web.xml files!&quot;);
}
System.setProperty(key, root);
servletContext.log(&quot;Set web app root system property: &#39;&quot; + key + &quot;&#39; = [&quot; + root + &quot;]&quot;);
}

soluctions:
step1: change redis configuration:add the destroyMethod method for the ClientResources and RedisClient bean when application closing.

 @Bean(destroyMethod = &quot;shutdown&quot;)
public ClientResources clientResources() {
return DefaultClientResources.builder()
//reconnection interval:5s
.reconnectDelay(Delay.constant(Duration.ofSeconds(5)))
.build();
}
@Bean(destroyMethod = &quot;shutdown&quot;)
public RedisClient redisClient(RedisURI redisURI, ClientResources clientResources) {
ClientOptions clientOptions = ClientOptions.builder()
//error auto retry 
.autoReconnect(true)
.pingBeforeActivateConnection(true)
.build();
RedisClient redisClient = RedisClient.create(clientResources, redisURI);
redisClient.setOptions(clientOptions);
return redisClient;
}

step2: Fix for loading LogbackConfigListener failure: Logback no longer sets the web application root path as a system property.

&lt;context-param&gt;
&lt;param-name&gt;logbackExposeWebAppRoot&lt;/param-name&gt;
&lt;param-value&gt;false&lt;/param-value&gt;
&lt;/context-param&gt;

huangapple
  • 本文由 发表于 2023年6月5日 12:49:20
  • 转载请务必保留本文链接:https://go.coder-hub.com/76403553.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定