tomcat - 可能的 Spring Boot 或 Spring Security 内存泄漏

标签 tomcat spring-security jetty spring-boot

我一直在浸泡测试下运行一个带有 spring security 的 spring boot 应用程序,发现它逐渐填满了它的内存分配。

我启动了应用程序:

java -Xmx128m -XX:+PrintGCDetails -XX:+PrintGCTimeStamps -Xloggc:gc.log -verbose:gc -jar target/myapp-0.0.1-SNAPSHOT.jar

这样我就可以得到 teh gc 信息并限制内存以更快地达到我的 OutOfMemoryError 情况(半小时,而不是两周)

我已经使用 tomcat 和 jetty 作为容器运行了它,然后运行了一个 bash 脚本,该脚本在服务器上触发了大量的 cURL 以模拟生产中的负载类型。我将 jmap 指向进程并在崩溃前不久得到以下结果(只显示前 40 个结果,以及 tomcat 运行)

 num     #instances         #bytes  class name
----------------------------------------------
   1:        395984       32564344  [C
   2:        388697        9328728  java.lang.String
   3:         61258        5915088  [B
   4:        100297        4814256  java.util.HashMap
   5:         50892        4478496  org.apache.catalina.session.StandardSession
   6:         58774        3656824  [Ljava.util.HashMap$Node;
   7:         84773        3390920  java.util.TreeMap$Entry
   8:         51522        3339304  [Ljava.util.Hashtable$Entry;
   9:         51834        3317376  java.util.concurrent.ConcurrentHashMap
  10:        102111        3267552  java.util.HashMap$Node
  11:         96256        3080192  java.util.concurrent.ConcurrentHashMap$Node
  12:         24101        2754560  [Ljava.util.concurrent.ConcurrentHashMap$Node;
  13:         51472        2470656  java.util.Hashtable
  14:         55102        2204080  java.util.LinkedHashMap$Entry
  15:         83020        1992480  java.util.ArrayList
  16:         34353        1923768  java.util.LinkedHashMap
  17:         59156        1892992  org.springframework.boot.loader.util.AsciiBytes
  18:         29574        1656144  org.springframework.boot.loader.jar.JarEntryData
  19:         18029        1586552  java.lang.reflect.Method
  20:         28391        1562080  [Ljava.lang.Object;
  21:         37178        1487120  java.lang.ref.SoftReference
  22:         47648        1446600  [I
  23:         52337        1256088  java.lang.Long
  24:         26134        1254432  java.util.TreeMap
  25:         50904        1221696  java.beans.PropertyChangeSupport
  26:         11777        1214464  java.lang.Class
  27:         23748        1139904  org.springframework.security.oauth2.provider.OAuth2Request
  28:         35994         863856  java.util.Collections$UnmodifiableRandomAccessList
  29:         50904         814464  java.beans.PropertyChangeSupport$PropertyChangeListenerMap
  30:         50892         814272  org.apache.catalina.session.StandardSessionFacade
  31:         49748         795968  java.util.HashSet
  32:         24066         770112  java.util.Collections$UnmodifiableMap
  33:         23748         759936  org.springframework.security.oauth2.provider.OAuth2Authentication
  34:         23748         759936  org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationDetails
  35:         26000         624000  javax.management.openmbean.CompositeDataSupport
  36:         12015         576664  [Ljava.lang.String;
  37:         16319         522208  com.sun.org.apache.xerces.internal.xni.QName
  38:         15288         489216  java.lang.ref.WeakReference
  39:         26448         423168  java.util.LinkedHashSet
  40:         26011         416176  java.util.TreeMap$KeySet

如您所见,有大量 tomcat StandardSessions 正在运行,但也有很多 OAuth2Authentication 实例(远远超过我预期的 2 或 3 个)。这两者的数量一直在增长,直到发生 OutOfMemoryError。两者都没有被收集。

我实现了下面提供的 spring 安全配置

@Configuration
@ComponentScan("com.xxx.xxxxx")
public class TokenConfig {

    private static final String RESOURCE_ID = "touchAuth";

    @Configuration
    @EnableResourceServer
    protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {

        private static final org.slf4j.Logger LOG = LoggerFactory.getLogger(ResourceServerConfiguration.class);

        @Autowired
        TouchUserDetailsService touchUserDetailsService;

        @Autowired
        TouchUserAuthenticationFilter touchUserAuthenticationFilter;

        @Override
        public void configure(ResourceServerSecurityConfigurer resources) {
            resources
                    .resourceId(RESOURCE_ID);
        }

        @Override
        public void configure(HttpSecurity http) throws Exception {


            //@formatter: off

            http
                    .addFilterBefore(touchUserAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
                    .requestMatchers()
                        .antMatchers("/**/secure/**", "/basket/**", "/transactions/**", "/support-requests/**", "/devices/resources/**", "/devices/information/**", "/devices/support-request", "/retailers/resources/**", "/news-items-display/last")
                    .and()
                    .authorizeRequests()
                        .anyRequest()
                        .authenticated();


            //@formatter: on
        }


    }

    @Configuration
    @EnableAuthorizationServer
    protected static class AuthorizationServerConfiguration extends
            AuthorizationServerConfigurerAdapter {

        @Autowired
        DataSource dataSource;

        @Autowired
        private JdbcTokenStore jdbcTokenStore;

        @Override
        public void configure(AuthorizationServerEndpointsConfigurer endpoints)
                throws Exception {
            endpoints
                    .tokenStore(jdbcTokenStore);
        }

        @Override
        public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
            clients
                    .jdbc(dataSource);

        }

    }

    @Configuration
    @EnableWebSecurity
    protected static class SecurityConfig extends WebSecurityConfigurerAdapter{


        @Autowired
        private TxxxxxUserDetailsService txxxxxUserDetailsService;

        @Autowired
        DataSource dataSource;


        @Bean
        public JdbcTokenStore jdbcTokenStore() {
            JdbcTokenStore jdbcTokenStore = new JdbcTokenStore(dataSource);

            return jdbcTokenStore;
        }

        @Bean
        public AuthenticationManager authenticationManagerBean() throws Exception {

            DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();

            authenticationProvider.setUserDetailsService(touchUserDetailsService);
            List<AuthenticationProvider> authenticationProviders = new ArrayList<>();
            authenticationProviders.add(authenticationProvider);


            ProviderManager providerManager = new ProviderManager(authenticationProviders);
            return providerManager;
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .headers().disable()  // allow things to be displayed in iframes, for example.
                    .csrf().disable()
                    .authorizeRequests()
                    .antMatchers("/favicon.ico").permitAll()
                    .antMatchers("/css/*").permitAll()
                    .antMatchers("/images/*").permitAll()
                    .antMatchers("/error*").permitAll()
                    .antMatchers("/ping").permitAll()
                    .antMatchers("/info").permitAll()
                    .antMatchers("/").permitAll()
                    .antMatchers("/content/**").permitAll()
                    .antMatchers("/devices/commission").permitAll()
                    .antMatchers("/devices/device-import").permitAll()
                    .antMatchers("/devices/list").permitAll()
                    .antMatchers("/devices/modify").permitAll()
                    .antMatchers("/news-items-management/**").permitAll()
                    .antMatchers("/news-items-display/*").permitAll()
                    .antMatchers("/support-request-management/*").permitAll()
                    .anyRequest()
                    .authenticated();
        }
    }
}

我也有过滤器,下面也提供

@Component
public class TxxxxUserAuthenticationFilter
        extends AbstractAuthenticationProcessingFilter {

    private static final Logger LOG = LoggerFactory.getLogger(TxxxxUserAuthenticationFilter.class);

    public static final String FILTER_PROCESS_URL = "/login";

    public static final String X_STANDALONE = "X_STANDALONE";
    public static final String YES = "YES";

    protected TxxxxUserAuthenticationFilter() {
        super(FILTER_PROCESS_URL);
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException, IOException, ServletException {

        LOG.debug("attemptAuthentication invoked");
        String username = request.getParameter("username");
        String password = request.getParameter("password");

        if (username == null) {
            username = "";
        }

        if (password == null) {
            password = "";
        }

        username = username.trim();

        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        Authentication authentication = this.getAuthenticationManager().authenticate(authRequest);

        request.getSession().setAttribute("SESSION_AUTHENTICATED", authentication);

        return authentication;
    }

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {


        HttpServletRequest httpServletRequest = (HttpServletRequest) req;
        SecurityContext securityContext = (SecurityContext) httpServletRequest.getSession().getAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY);
        if (isLoginRequest(httpServletRequest)) {
            super.doFilter(req, res, chain);
            return;
        }

        // If the Authorization header is absent, the framework treats it as a non-oauth request and continues the security chain.
        if (securityContext != null && ((HttpServletRequest) req).getHeader("Authorization") == null) {
            securityContext.setAuthentication(null);
        }

        // Throw an exception if standalone device and a valid authentication is not found.
        if (requiresFormAuthentication(httpServletRequest) ) {

            Authentication authentication = (Authentication)httpServletRequest.getSession().getAttribute("SESSION_AUTHENTICATED");
            if(authentication == null || !authentication.isAuthenticated()) {
                throw new AuthenticationCredentialsNotFoundException("Access denied");
            }
        }

        chain.doFilter(req, res);

    }



    private boolean doesAuthExistsInContext(SecurityContext securityContext) {
        return securityContext != null && securityContext.getAuthentication() != null;
    }

    private boolean requiresFormAuthentication(HttpServletRequest httpServletRequest) {

        return  YES.equalsIgnoreCase(httpServletRequest.getHeader(X_STANDALONE));
    }

    private boolean isLoginRequest(HttpServletRequest req) {
        return FILTER_PROCESS_URL.equalsIgnoreCase(req.getRequestURI());
    }

    @Autowired
    public void setAuthenticationManagerBean (AuthenticationManager authenticationManager) {
        super.setAuthenticationManager(authenticationManager);
    }

}

有谁知道内存泄漏的根源是什么?为什么我打开了这么多 Tomcat(或 Jetty) session ?

显然,这里的空间有限,但如果需要任何进一步的信息,我很乐意提供帮助。

谢谢

最佳答案

您遇到的情况与我们这里相同。看看@https://github.com/spring-projects/spring-boot/issues/2084

关于tomcat - 可能的 Spring Boot 或 Spring Security 内存泄漏,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27162627/

相关文章:

jsp - JasperReport 查看器 JSP WebApp 错误

spring-mvc - 带有 Spring WebSocket 的 SockJS 客户端 - CORS

java - 转义 HTTP 请求中的斜杠

spring-mvc - Spring Boot 过滤器顺序 : WebLogic 12c vs Tomcat 8

java - getPrincipal() 方法返回用户名而不是 UserDetails

java - 如何告诉 jetty 将部分 jar 文件提取到其上下文临时位置

java - Jetty/Tomcat 需要用 Java 构建网页吗?

tomcat - 有没有办法在 J2EE 服务器启动时做一些事情?我正在使用汤姆猫

java - 从 Hibernate 更新获取 SQL 脚本

java - Win Server Tomcat 8.0 上的 War 文件