可能的Spring Boot或Spring Security Memory Leak

时间:2014-11-27 03:30:39

标签: tomcat spring-security jetty spring-boot

我在浸泡测试中运行了一个带弹簧安全性的spring启动应用程序,发现它逐渐填满了内存分配。

我启动了应用程序:

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

这样我就可以获取信息并限制内存更快地达到我的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。两者都没有被收集。

我实现了一个弹簧安全配置,在下面提供

@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)会话?

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

感谢

1 个答案:

答案 0 :(得分:1)

你和我们在这里经历的一样。看看@ https://github.com/spring-projects/spring-boot/issues/2084